* [PATCH 2/2] wifi: ath12k: skip unknown direct buffer ring module IDs
From: Nazar Mokrynskyi @ 2026-05-05 17:24 UTC (permalink / raw)
To: ath12k; +Cc: linux-wireless, jeff.johnson, Nazar Mokrynskyi
In-Reply-To: <20260505172415.566328-1-nazar@mokrynskyi.com>
The firmware may advertise direct buffer ring capabilities for module
IDs beyond what the driver currently knows about (WMI_DIRECT_BUF_MAX).
This happens with newer firmware versions that support additional ring
types not yet implemented in the driver.
The current code treats an unknown module_id as a fatal error, returning
-EINVAL and tearing down the entire driver initialization. This is
incorrect: the driver only needs to set up rings for types it uses
(SPECTRAL=0, CFR=1) and can safely ignore capability advertisements for
unknown types.
Change the unknown module_id handling to skip the entry with a debug
message rather than failing, allowing initialization to proceed.
Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1
Signed-off-by: Nazar Mokrynskyi <nazar@mokrynskyi.com>
---
drivers/net/wireless/ath/ath12k/wmi.c | 12 ++++--------
1 file changed, 4 insertions(+), 8 deletions(-)
diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c
index 484fdd3b1..0e776a8d8 100644
--- a/drivers/net/wireless/ath/ath12k/wmi.c
+++ b/drivers/net/wireless/ath/ath12k/wmi.c
@@ -4814,10 +4814,10 @@ static int ath12k_wmi_dma_ring_caps(struct ath12k_base *ab,
dir_buff_caps = ab->db_caps;
for (i = 0; i < dma_caps_parse->n_dma_ring_caps; i++) {
if (le32_to_cpu(dma_caps[i].module_id) >= WMI_DIRECT_BUF_MAX) {
- ath12k_warn(ab, "Invalid module id %d\n",
- le32_to_cpu(dma_caps[i].module_id));
- ret = -EINVAL;
- goto free_dir_buff;
+ ath12k_dbg(ab, ATH12K_DBG_WMI,
+ "Skipping unknown direct buf ring module id %d\n",
+ le32_to_cpu(dma_caps[i].module_id));
+ continue;
}
dir_buff_caps[i].id = le32_to_cpu(dma_caps[i].module_id);
@@ -4829,10 +4829,6 @@ static int ath12k_wmi_dma_ring_caps(struct ath12k_base *ab,
}
return 0;
-
-free_dir_buff:
- ath12k_wmi_free_dbring_caps(ab);
- return ret;
}
static void
--
2.43.0
^ permalink raw reply related
* [PATCH 1/2] wifi: ath12k: skip PCIe global reset on initial power-up
From: Nazar Mokrynskyi @ 2026-05-05 17:24 UTC (permalink / raw)
To: ath12k; +Cc: linux-wireless, jeff.johnson, Nazar Mokrynskyi
In-Reply-To: <20260505172415.566328-1-nazar@mokrynskyi.com>
ath12k_pci_sw_reset() unconditionally calls ath12k_pci_soc_global_reset()
regardless of whether the device is being powered up for the first time or
recovering from a previous run. The global reset drops the PCIe link and
relies on the host root complex to perform physical link retraining before
the MHI BHI register can be accessed.
When the device is passed through to a VM via VFIO, no physical link
retraining occurs after the reset since QEMU's virtual PCIe bridge does
not implement hardware LTSSM negotiation. As a result, all subsequent
MMIO reads return 0xffffffff and MHI initialization fails with -EREMOTEIO.
On initial power-up, vfio-pci has already performed a Function Level
Reset before handing the device to the guest driver, placing it in a
known clean state equivalent to what the global reset achieves. The global
reset is therefore redundant on power-up and only necessary on the
shutdown/recovery path where it tears down an already-running firmware.
Skip ath12k_pci_soc_global_reset() when power_on is true to allow MHI
initialization to succeed under VFIO passthrough without affecting bare
metal behavior.
Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1
Signed-off-by: Nazar Mokrynskyi <nazar@mokrynskyi.com>
---
drivers/net/wireless/ath/ath12k/pci.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/net/wireless/ath/ath12k/pci.c b/drivers/net/wireless/ath/ath12k/pci.c
index 375277ca2..a3d7aeb72 100644
--- a/drivers/net/wireless/ath/ath12k/pci.c
+++ b/drivers/net/wireless/ath/ath12k/pci.c
@@ -303,7 +303,8 @@ static void ath12k_pci_sw_reset(struct ath12k_base *ab, bool power_on)
ath12k_mhi_clear_vector(ab);
ath12k_pci_clear_dbg_registers(ab);
- ath12k_pci_soc_global_reset(ab);
+ if (!power_on)
+ ath12k_pci_soc_global_reset(ab);
ath12k_mhi_set_mhictrl_reset(ab);
}
--
2.43.0
^ permalink raw reply related
* [PATCH 0/2] wifi: ath12k: Fix operation under virtio
From: Nazar Mokrynskyi @ 2026-05-05 17:24 UTC (permalink / raw)
To: ath12k; +Cc: linux-wireless, jeff.johnson, Nazar Mokrynskyi
I've been struggling with ath12k and even ath11k for years:
https://forum.openwrt.org/t/qcn9274-crashes-the-system-during-driver-load/239491?u=nazar-pc
https://forum.openwrt.org/t/qcn9074-doesnt-initialize-on-x86-64/163288?u=nazar-pc
With these patches it is finally possible to use QCN9274 under KVM with virtio.
First patch avoids global reset, allowing module to power on properly. Similar
patch should probably be added for ath11k too. Without this patch I was getting
the following:
```
[ 6.856855] ath12k_pci 0000:01:00.0: BAR 0 [mem 0xfc600000-0xfc7fffff 64bit]: assigned
[ 6.879292] ath12k_pci 0000:01:00.0: MSI vectors: 16
[ 6.883931] ath12k_pci 0000:01:00.0: Hardware name: qcn9274 hw2.0
[ 6.990901] ath12k_pci 0000:01:00.0: link down error during global reset
[ 7.998602] ath12k_pci 0000:01:00.0: link failed to recover after global reset
[ 8.011545] mhi mhi0: BHI offset: 0xffffffff is out of range: 0x200000
[ 8.013095] ath12k_pci 0000:01:00.0: failed to set mhi state: INIT(0)
[ 8.014588] ath12k_pci 0000:01:00.0: failed to start mhi: -34
[ 8.016025] ath12k_pci 0000:01:00.0: failed to power up :-34
[ 8.050113] ath12k_pci 0000:01:00.0: failed to create soc 0 core: -34
[ 8.053079] ath12k_pci 0000:01:00.0: unable to create hw group
[ 8.090241] ath12k_pci 0000:01:00.0: failed to init core: -34
[ 8.554526] ath12k_pci 0000:01:00.0: probe with driver ath12k_pci failed with error -34
```
With the patch the process goes further:
```
[ 6.897009] ath12k_pci 0000:01:00.0: BAR 0 [mem 0xfc600000-0xfc7fffff 64bit]: assigned
[ 6.906501] ath12k_pci 0000:01:00.0: MSI vectors: 16
[ 6.909033] ath12k_pci 0000:01:00.0: Hardware name: qcn9274 hw2.0
[ 6.959630] mhi mhi0: Requested to power ON
[ 6.960899] mhi mhi0: Power on setup success
[ 7.128588] mhi mhi0: Wait for device to enter SBL or Mission mode
[ 7.659325] ath12k_pci 0000:01:00.0: qmi dma allocation failed (20971520 B type 1), will try later with small size
[ 7.670187] ath12k_pci 0000:01:00.0: memory type 10 not supported
[ 7.674957] kmodloader: done loading kernel modules from /etc/modules.d/*
[ 7.683773] ath12k_pci 0000:01:00.0: chip_id 0x0 chip_family 0xb board_id 0x1019 soc_id 0x401a2200
[ 7.688137] ath12k_pci 0000:01:00.0: fw_version 0x160484db fw_build_timestamp 2025-12-09 20:09 fw_build_id QC_IMAGE_VERSION_STRING=WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1
[ 10.611560] ath12k_pci 0000:01:00.0: Invalid module id 2
[ 10.616467] ath12k_pci 0000:01:00.0: failed to parse tlv -22
[ 10.747508] debugfs: File 'simulate_fw_crash' in directory 'pci-0000:01:00.0' already present!
[ 10.752188] debugfs: File 'device_dp_stats' in directory 'pci-0000:01:00.0' already present!
[ 10.770705] ieee80211 phy0: copying sband (band 1) due to VHT EXT NSS BW flag
[ 10.774844] debugfs: File 'ath12k' in directory 'phy0' already present!
[ 13.968334] ath12k_pci 0000:01:00.0: failed to send WMI_PDEV_SET_PARAM cmd
[ 13.971771] ath12k_pci 0000:01:00.0: failed to set ac override for ARP: -108
[ 13.975315] ath12k_pci 0000:01:00.0: fail to start mac operations in pdev idx 0 ret -108
[ 14.123699] ath12k_pci 0000:01:00.0: link down error during global reset
[ 14.167537] mhi mhi0: BHI offset: 0xffffffff is out of range: 0x200000
[ 14.169031] ath12k_pci 0000:01:00.0: failed to set mhi state: INIT(0)
[ 14.170504] ath12k_pci 0000:01:00.0: failed to start mhi: -34
```
Second patch fixes an issue I had in OpenWRT, which may have been caused by
firmware upgrade:
```
[ 10.611560] ath12k_pci 0000:01:00.0: Invalid module id 2
[ 10.616467] ath12k_pci 0000:01:00.0: failed to parse tlv -22
```
My understanding is that it is fine to ignore unknown capability of the
firmware, it should not be required if driver doesn't know about it anyway.
I noticed a very similar code in ath11k, but wasn't sure if the change is needed
there too, so skipped it. Though I have a strong suspicion the same firmware
mismatch might happen there too.
With both of those I can finally use QCN9274 under KVM virtual machine with
virtio, at least with `pci=noaer` on the host. Without `pci=noaer` the module
still boots and works properly, but any attempt to shut down or reboot
inevitably crashes the host, which I reported to vfio maintainers, but I'd
appreciate cooperation from ath12k maintainers too:
https://lore.kernel.org/kvm/70de9b8f-b7c7-481b-8cb1-931e3b82eb0e@mokrynskyi.com/
Nazar Mokrynskyi (2):
wifi: ath12k: skip PCIe global reset on initial power-up
wifi: ath12k: skip unknown direct buffer ring module IDs
drivers/net/wireless/ath/ath12k/pci.c | 3 ++-
drivers/net/wireless/ath/ath12k/wmi.c | 12 ++++--------
2 files changed, 6 insertions(+), 9 deletions(-)
--
2.43.0
^ permalink raw reply
* Re: [PATCH 2/2] wifi: ath11k: fix error path leaks in some WMI calls
From: Rameshkumar Sundaram @ 2026-05-05 17:23 UTC (permalink / raw)
To: Nicolas Escande, ath11k; +Cc: linux-wireless
In-Reply-To: <20260502071453.2379188-3-nico.escande@gmail.com>
On 5/2/2026 12:44 PM, Nicolas Escande wrote:
> This is the same pattern that was previously identified as problematic:
> direct 'return ath11k_wmi_cmd_send(...)' will leak the skb in the error
> path if it is not explicitly handled.
>
> Fixes: c417b247ba04 ("ath11k: implement hardware data filter")
> Fixes: 9cbd7fc9be82 ("ath11k: support MAC address randomization in scan")
> Fixes: ba9177fcef21 ("ath11k: Add basic WoW functionalities")
> Fixes: fec4b898f369 ("ath11k: Add WoW net-detect functionality")
> Fixes: c3c36bfe998b ("ath11k: support ARP and NS offload")
> Fixes: a16d9b50cfba ("ath11k: support GTK rekey offload")
> Fixes: 652f69ed9c1b ("ath11k: Add support for SAR")
> Fixes: 0f84a156aa3b ("ath11k: Handle keepalive during WoWLAN suspend and resume")
> Signed-off-by: Nicolas Escande <nico.escande@gmail.com>
> ---
> drivers/net/wireless/ath/ath11k/wmi.c | 112 ++++++++++++++++++++++----
> 1 file changed, 96 insertions(+), 16 deletions(-)
>
> diff --git a/drivers/net/wireless/ath/ath11k/wmi.c b/drivers/net/wireless/ath/ath11k/wmi.c
> index 024c2aad9fb4..dca6e011cc40 100644
> --- a/drivers/net/wireless/ath/ath11k/wmi.c
> +++ b/drivers/net/wireless/ath/ath11k/wmi.c
> @@ -9299,7 +9299,7 @@ int ath11k_wmi_hw_data_filter_cmd(struct ath11k *ar, u32 vdev_id,
> {
> struct wmi_hw_data_filter_cmd *cmd;
> struct sk_buff *skb;
> - int len;
> + int ret, len;
>
> len = sizeof(*cmd);
> skb = ath11k_wmi_alloc_skb(ar->wmi->wmi_ab, len);
> @@ -9324,7 +9324,13 @@ int ath11k_wmi_hw_data_filter_cmd(struct ath11k *ar, u32 vdev_id,
> "hw data filter enable %d filter_bitmap 0x%x\n",
> enable, filter_bitmap);
>
> - return ath11k_wmi_cmd_send(ar->wmi, skb, WMI_HW_DATA_FILTER_CMDID);
> + ret = ath11k_wmi_cmd_send(ar->wmi, skb, WMI_HW_DATA_FILTER_CMDID);
> + if (ret) {
> + ath11k_warn(ar->ab, "failed to send WMI_HW_DATA_FILTER_CMDID\n");
> + dev_kfree_skb(skb);
> + }
> +
> + return ret;
> }
>
{ .. }
> @@ -10053,7 +10127,13 @@ int ath11k_wmi_sta_keepalive(struct ath11k *ar,
> "sta keepalive vdev %d enabled %d method %d interval %d\n",
> arg->vdev_id, arg->enabled, arg->method, arg->interval);
>
> - return ath11k_wmi_cmd_send(wmi, skb, WMI_STA_KEEPALIVE_CMDID);
> + ret = ath11k_wmi_cmd_send(wmi, skb, WMI_STA_KEEPALIVE_CMDID);
> + if (ret) {
> + ath11k_warn(ar->ab, "failed to send WMI_STA_KEEPALIVE_CMDID\n");
> + dev_kfree_skb(skb);
> + }
> +
> + return ret;
> }
>
> bool ath11k_wmi_supports_6ghz_cc_ext(struct ath11k *ar)
Thanks for fixing these. One more instance of the same pattern remains
in ath11k_tm_cmd_wmi_ftm().
Please add dev_kfree_skb(skb) before goto out, matching
ath11k_tm_cmd_wmi() above.
--
--
Ramesh
^ permalink raw reply
* Re: [PATCH v2] wifi: ath12k: fix incorrect HT/VHT/HE/EHT MCS reporting in monitor mode
From: Jeff Johnson @ 2026-05-05 17:22 UTC (permalink / raw)
To: Tamizh Raja, kwan1996; +Cc: ath12k, linux-wireless
In-Reply-To: <CABkEBKbVG-UGWOQUgi0Q4M9HkMqOF-nc2gWTwBn9gebC8s7U8Q@mail.gmail.com>
On 5/5/2026 9:43 AM, Tamizh Raja wrote:
> On Tue, May 5, 2026 at 9:40 AM kwan1996 <laicheehou9@gmail.com> wrote:
>>
>> In monitor mode, the driver incorrectly assigns the legacy rate
>> to the rate_idx field of the radiotap header for HT/VHT/HE/EHT
>> frames, ignoring the actual MCS value parsed from the hardware.
>>
>> This causes packet analyzers (like Wireshark) to display incorrect
>> MCS values (e.g., legacy base rates instead of the true MCS).
>>
>> Fix this by assigning ppdu_info->mcs instead of ppdu_info->rate
>> for HT/VHT/HE/EHT frame types in ath12k_dp_mon_fill_rx_rate()
>> and ath12k_dp_mon_update_radiotap().
>>
>> Link: https://bugzilla.kernel.org/show_bug.cgi?id=220864
>>
>> Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.4.1-00199-QCAHKSWPL_SILICONZ
>>
>> Signed-off-by: kwan1996 <laicheehou9@gmail.com>
>>
>> ---
>>
>> v2: Fix indentation and formatting issues in v1.
>>
>> ---
>> drivers/net/wireless/ath/ath12k/dp_mon.c | 10 +++++++---
>> 1 file changed, 7 insertions(+), 3 deletions(-)
>>
>> diff --git a/drivers/net/wireless/ath/ath12k/dp_mon.c b/drivers/net/wireless/ath/ath12k/dp_mon.c
>> index 39d1967..4119bb8 100644
>> --- a/drivers/net/wireless/ath/ath12k/dp_mon.c
>> +++ b/drivers/net/wireless/ath/ath12k/dp_mon.c
>> @@ -1925,6 +1925,7 @@ ath12k_dp_mon_fill_rx_rate(struct ath12k *ar,
>> }
>> break;
>> case RX_MSDU_START_PKT_TYPE_11N:
>> + rate_mcs = ppdu_info->mcs;
>
> Can we assign this rate_mcs before the switch case? Since in all cases
> we are assigning unmodified ppdu_info->mcs.
>> rx_status->encoding = RX_ENC_HT;
>> if (rate_mcs > ATH12K_HT_MCS_MAX) {
>> ath12k_warn(ar->ab,
>> @@ -1937,6 +1938,7 @@ ath12k_dp_mon_fill_rx_rate(struct ath12k *ar,
>> rx_status->enc_flags |= RX_ENC_FLAG_SHORT_GI;
>> break;
>> case RX_MSDU_START_PKT_TYPE_11AC:
>> + rate_mcs = ppdu_info->mcs;
>> rx_status->encoding = RX_ENC_VHT;
>> rx_status->rate_idx = rate_mcs;
>> if (rate_mcs > ATH12K_VHT_MCS_MAX) {
>> @@ -1949,6 +1951,7 @@ ath12k_dp_mon_fill_rx_rate(struct ath12k *ar,
>> rx_status->enc_flags |= RX_ENC_FLAG_SHORT_GI;
>> break;
>> case RX_MSDU_START_PKT_TYPE_11AX:
>> + rate_mcs = ppdu_info->mcs;
>> rx_status->rate_idx = rate_mcs;
>> if (rate_mcs > ATH12K_HE_MCS_MAX) {
>> ath12k_warn(ar->ab,
>> @@ -1960,6 +1963,7 @@ ath12k_dp_mon_fill_rx_rate(struct ath12k *ar,
>> rx_status->he_gi = ath12k_he_gi_to_nl80211_he_gi(sgi);
>> break;
>> case RX_MSDU_START_PKT_TYPE_11BE:
>> + rate_mcs = ppdu_info->mcs;
>> rx_status->rate_idx = rate_mcs;
>> if (rate_mcs > ATH12K_EHT_MCS_MAX) {
>> ath12k_warn(ar->ab,
>> @@ -2259,13 +2263,13 @@ static void ath12k_dp_mon_update_radiotap(struct ath12k *ar,
>> rxs->encoding = RX_ENC_HE;
>> ptr = skb_push(mon_skb, sizeof(struct ieee80211_radiotap_he));
>> ath12k_dp_mon_rx_update_radiotap_he(ppduinfo, ptr);
>> - rxs->rate_idx = ppduinfo->rate;
>> + rxs->rate_idx = ppduinfo->mcs;
>> } else if (ppduinfo->vht_flags) {
>> rxs->encoding = RX_ENC_VHT;
>> - rxs->rate_idx = ppduinfo->rate;
>> + rxs->rate_idx = ppduinfo->mcs;
>> } else if (ppduinfo->ht_flags) {
>> rxs->encoding = RX_ENC_HT;
>> - rxs->rate_idx = ppduinfo->rate;
>> + rxs->rate_idx = ppduinfo->mcs;
>
> rate_idx should be assigned with ppdu_info->rate only not mcs.
why is that? documentation says:
* @rate_idx: index of data rate into band's supported rates or MCS index if
* HT or VHT is used (%RX_FLAG_HT/%RX_FLAG_VHT)
ppduinfo contains separate rate and mcs so doesn't one or the other need to be
copied to rxs->rate_idx based upon the current PHY configuration?
btw looks like the struct ieee80211_rx_status documentation needs to be
updated for HE & EHT (and UHR?)
^ permalink raw reply
* [PATCH v2] wifi: ath11k: fix use after free in ath11k_dp_rx_msdu_coalesce.
From: Willmar Knikker @ 2026-05-05 17:17 UTC (permalink / raw)
To: jjohnson; +Cc: linux-wireless, ath11k
In ath11k_dp_rx_msdu_coalesce the loop uses ->is_continuation after
the dev_kfree_skb_any. This can cause a use after free kfence.
Use flag for caching is_continuation for use after the
dev_kfree_skb_any.
Fixes: d5c65159f289 ("ath11k: driver for Qualcomm IEEE 802.11ax devices")
Signed-off-by: Willmar Knikker <willmar@met-dubbel-l.nl>
Changes in v2:
- add bool _is_continuation for use after the free.
- Add Fixes, label to commit.
---
drivers/net/wireless/ath/ath11k/dp_rx.c | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/drivers/net/wireless/ath/ath11k/dp_rx.c b/drivers/net/wireless/ath/ath11k/dp_rx.c
index fe79109adc70..16364f76fc3c 100644
--- a/drivers/net/wireless/ath/ath11k/dp_rx.c
+++ b/drivers/net/wireless/ath/ath11k/dp_rx.c
@@ -1761,6 +1761,7 @@ static int ath11k_dp_rx_msdu_coalesce(struct ath11k *ar,
int buf_first_hdr_len, buf_first_len;
struct hal_rx_desc *ldesc;
int space_extra, rem_len, buf_len;
+ bool is_continuation;
u32 hal_rx_desc_sz = ar->ab->hw_params.hal_desc_sz;
/* As the msdu is spread across multiple rx buffers,
@@ -1810,7 +1811,8 @@ static int ath11k_dp_rx_msdu_coalesce(struct ath11k *ar,
rem_len = msdu_len - buf_first_len;
while ((skb = __skb_dequeue(msdu_list)) != NULL && rem_len > 0) {
rxcb = ATH11K_SKB_RXCB(skb);
- if (rxcb->is_continuation)
+ is_continuation = rxcb->is_continuation;
+ if (is_continuation)
buf_len = DP_RX_BUFFER_SIZE - hal_rx_desc_sz;
else
buf_len = rem_len;
@@ -1828,7 +1830,7 @@ static int ath11k_dp_rx_msdu_coalesce(struct ath11k *ar,
dev_kfree_skb_any(skb);
rem_len -= buf_len;
- if (!rxcb->is_continuation)
+ if (!is_continuation)
break;
}
--
2.54.0
^ permalink raw reply related
* Hey, TQMPXKWZ91 39064302677 is your order number
From: ferdinandleka859 @ 2026-05-05 17:06 UTC (permalink / raw)
To: linux-wireless
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain, Size: 1536 bytes --]
Dear admin,
We hope this message finds you well. We’re reaching out to inform you that your subscription with GeekSquad is set to renew automatically very soon. As part of this process, a payment of 379.5 will be processed using the payment method currently associated with your account. This automatic renewal ensures that your access to GeekSquad services remains uninterrupted and that you continue to enjoy the full range of benefits and features that come with your membership. For your reference, your Subscription ID is 7079-SM-9415, and your Member ID is 97909-9161. If you have any questions, need to make updates to your account, or are considering cancelling your subscription, we kindly ask that you contact our support team before the renewal date to ensure we can assist you in time. Our customer support team is available and ready to help at (808)-210-4827, and they will be happy to guide you through any changes you may need to make. Please note that this is an automated notification, and replies to this email are not monitored. Also, as a reminder, GeekSquad will never ask for your personal or sensitive information via email, so please be cautious with your information online. We thank you sincerely for being a part of the GeekSquad community and for your continued trust in our services. Your ongoing support means a great deal to us, and we’re committed to continuing to serve you with excellence.
Best regards,
The GeekSquad Team
337 Calhoun Ave Calumet City Il 60409 United States
📞 (808)-210-4827
^ permalink raw reply
* Re: [PATCH] wifi: ath11k: fix use after free in ath11k_dp_rx_msdu_coalesce.
From: Willmar Knikker @ 2026-05-05 16:53 UTC (permalink / raw)
To: Jeff Johnson; +Cc: jjohnson, linux-wireless
In-Reply-To: <7d3c5eae-233a-4c31-b64e-70f0afe74da6@oss.qualcomm.com>
On Tue, 05 May 2026 08:08:48 -0700
"Jeff Johnson" <jeff.johnson@oss.qualcomm.com> wrote:
>
> rather than repeating code imo it would be "better" to cache the flag before
> freeing and then test the cached flag.
>
> however as you note this proposed logic matches the logic already present in
> the "Free up all buffers of the MSDU" portion of the function, so from that
> perspective the proposal is consistent with that logic.
>
> let's see if anyone else has an opinion...
>
> /jeff
imo, i would agree that would be cleaner.
But as this is my first patch i did not want to make to big of a change
so reusing the pattern from above felt te safest.
> This issue is present since day 1...
>
> Fixes: d5c65159f289 ("ath11k: driver for Qualcomm IEEE 802.11ax devices")
Should i make v2 commit to add that tag?
Thanks for taking the time.
--- Willmar K.
^ permalink raw reply
* [PATCH wireless-next] wifi: cfg80211: don't allow NAN DATA on multi radio devices
From: Miri Korenblit @ 2026-05-05 16:46 UTC (permalink / raw)
To: linux-wireless
The support for NAN DATA was added for single radio devices only. For
example, checking the interface combinations is done for a single radio.
Prevent registration with NAN DATA interface type for multi radio
devices.
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
net/wireless/core.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/net/wireless/core.c b/net/wireless/core.c
index 4dd1981a3629..62ab5e4639be 100644
--- a/net/wireless/core.c
+++ b/net/wireless/core.c
@@ -882,7 +882,7 @@ int wiphy_register(struct wiphy *wiphy)
return -EINVAL;
if (WARN_ON((wiphy->interface_modes & BIT(NL80211_IFTYPE_NAN_DATA)) &&
- !wiphy->nan_capa.phy.ht.ht_supported))
+ (!wiphy->nan_capa.phy.ht.ht_supported || wiphy->n_radio > 1)))
return -EINVAL;
if (WARN_ON(wiphy->interface_modes & BIT(NL80211_IFTYPE_WDS)))
--
2.34.1
^ permalink raw reply related
* [PATCH] wireless-regdb: add regulatory rules for Iraq (IQ)
From: Mohammed Abdalla @ 2026-05-05 16:12 UTC (permalink / raw)
To: wireless-regdb@lists.infradead.org,
linux-wireless@vger.kernel.org
Cc: mohammed.al-obaidi@badraproject.com
In-Reply-To: <1886262646.6291779.1777997531793.ref@mail.yahoo.com>
[-- Attachment #1.1: Type: text/plain, Size: 7050 bytes --]
## 1. Why this patch exists
Iraq is currently absent from `wireless-regdb/db.txt`. As aconsequence, every OpenWrt and Linux device set to `country=IQ`falls back to the world domain (`00`), which marks most of the5 GHz spectrum as `no IR` and limits 2.4 GHz EIRP to 20 dBm. Apublic OpenWrt forum thread on the Archer AX23 in Iraq concludedwith the maintainers' standard answer:
> *"IQ is the correct code for the place; once an engineer shares> the local radio laws with regdb maintainers it will be added."*
This patch is that contribution.
## 2. The primary source
The Iraqi Communications and Media Commission (CMC), the nationalregulator, has issued a numerical regulation specifically governingunlicensed Wi-Fi, SRD, and UWB devices:
- **Title:** Regulation on short-range radio communication devices (SRD) and devices using ultra-broadband (UWB) technology- **Issuer:** Republic of Iraq, CMC, Telecommunications Regulatory Department, International Relations Section- **Decree:** Council of Commissioners decision No. 122/q-2025- **In force from:** 2025-09-22- **Edition:** First edition, 2025; 26 pages- **Direct PDF:** <https://cmc.iq/wp-content/uploads/2025/09/Regulation-on-short-range-radio-communication-devices-SRD-and-devices-using-ultra-broadband-UWB-technology.pdf>
Article 4-1-13 of that regulation, titled "Wireless Access Systems(WAS)", contains a full numerical table for every Wi-Fi band. Thispatch reproduces that table directly. Nothing in the proposed`country IQ:` block is inferred or extrapolated.
## 3. The Article 4-1-13 table, verbatim
| Band | Use | Max EIRP | Required mitigations | Cited standard ||---|---|---|---|---|| 2400 – 2483.5 MHz | Indoor and outdoor | 100 mW | LBT and DAA | EN 300 328, ERC/REC 70-03 || 5150 – 5250 MHz | Indoor | 200 mW | — | EN 301 893, ITU Res. 229 (Rev. WRC-19) || 5250 – 5350 MHz | Indoor | 200 mW | — (DFS implied via EN 301 893) | EN 301 893 || 5470 – 5725 MHz | Indoor | 1000 mW | DFS and TPC (stated explicitly) | EN 301 893 || 5725 – 5875 MHz | Indoor and outdoor | 2000 mW (10 MHz ch) / 4000 mW (20 MHz ch) | — | EN 302 502 || 5945 – 6425 MHz | Indoor | 200 mW | — | EN 303 687, ECC Report 75 || 57000 – 66000 MHz | Indoor | 10000 mW | LBT and DAA | EN 302 567 |
The proposed `country IQ:` block encodes this table line for line.
## 4. The Iraqi regulation also defines its own glossary terms
For the avoidance of doubt, the regulation's Annex A explicitlydefines `Wi-Fi` as *"802.11 Local Area Networking in 2.4 and 5 GHzISM bands"*. So when the maintainers ask whether this regulationin fact covers Wi-Fi, the answer from the regulator is yes,in writing, in the regulation itself.
The same annex defines DFS, TPC, LBT, DAA, EIRP and AFA in theexact wireless-regdb sense.
## 5. Encoding choices and where they came from
A few wireless-regdb encoding details require explanation, becausethey are interpretations of the regulation's wording rather thandirect copies of numerical limits:
1. **NO-OUTDOOR on 5150–5725 MHz.** The regulation labels these rows simply as "Indoor". The wireless-regdb idiom for that is the `NO-OUTDOOR` flag.
2. **No NO-OUTDOOR on 5725–5875 MHz.** The regulation explicitly labels this row "Indoor and outdoor".
3. **DFS on 5250–5350 MHz.** The regulation's own column for this row is empty for mitigations, but the cited standard (EN 301 893) requires DFS in this sub-band, and the corresponding row for 5470–5725 in the same table does state DFS+TPC. Reading the regulation as a whole, DFS for 5250–5350 is required by the incorporated standard.
4. **Single EIRP figure for 5725–5875 MHz.** The regulation gives two figures (2000 mW for 10 MHz channels, 4000 mW for 20 MHz channels). The wireless-regdb format expresses one ceiling per band; the 4000 mW figure is used because it is the higher value that the regulation explicitly permits.
5. **6 GHz channel width set to 80 MHz.** The regulation does not explicitly distinguish standard-power from low-power indoor (LPI) operation, nor does it mention AFC. The conservative choice is to encode the 6 GHz block at 80 MHz (the widest non-AFC option in current practice) and leave a follow-up patch for a wider channelisation once CMC clarifies AFC requirements.
6. **AUTO-BW on the 5 GHz RLAN rows.** Standard practice for EN 301 893–compliant entries; no AUTO-BW is set on the 6 GHz row pending the AFC question above.
If the maintainers prefer a different encoding for any of thesesix points, please push back; the underlying regulatory text isclear and any of these can be re-encoded without changing what isactually permitted under Iraqi law.
## 6. The 5.8 GHz figure looks unusually high — it is intentional
`(5725 - 5875 @ 80), (4000 mW)` with no NO-OUTDOOR is not a typo.This is what Iraq's own regulation states for this sub-band, citingEN 302 502. It is the BFWA value, not the Non-Specific SRD value.This choice puts Iraq at the high end of the regional spectrumpolicy for the 5.8 GHz band. It is included verbatim because thepurpose of wireless-regdb is to reflect what each country'sregulator actually permits.
## 7. What is not in the patch
- **5850–5925 MHz ITS / V2X bands.** Article 4-1-8 of the same regulation covers ITS at 5855–5925 MHz with 2 W EIRP, but this is a non-Wi-Fi RLAN application and is outside the scope of what wireless-regdb usually encodes for `country` blocks.
- **All non-Wi-Fi SRD bands.** The regulation also covers RFID, inductive applications, alarms, model control, automotive radar, level probing radar, hearing aids, active medical implants, and the full UWB regime (Articles 4-2-1 through 4-2-6). None of these is a wireless-regdb concern.
- **6 GHz beyond 6425 MHz.** The Iraqi regulation only addresses 5945–6425 MHz at 6 GHz; the 6425–7125 MHz upper portion is not covered, and the patch therefore does not include it.
## 8. Submission checklist
- [ ] Verify the patch applies cleanly against the current `wireless-regdb` master; the IQ block must be inserted in alphabetical order, between `IN` and `IR`.- [ ] Build `regulatory.db` locally and confirm with `regdbdump regulatory.db | grep -A8 'country IQ'` that the output matches the proposed table exactly.- [ ] Post the cover letter and patch on the OpenWrt forum thread (231380) for community review by Iraqi engineers before sending upstream.- [ ] Send to `linux-wireless@vger.kernel.org` with cc to `wireless-regdb@lists.infradead.org`.
## 9. A note on responsibility
The numerical content of this patch is taken verbatim from a publicIraqi government regulation. The encoding choices listed in §5 arethe patch author's, and they are reversible.
Author: Mohammed Abdullah Ali Al-Obaidi (mnew_iraq@yahoo.com),OpenWrt forum handle `mnewiraq`. Any objection to the encodingchoices should be raised to that author or in the upstream reviewthread, not to the CMC.
[-- Attachment #1.2: Type: text/html, Size: 11942 bytes --]
[-- Attachment #2: 0001-wireless-regdb-add-Iraq-IQ-entry-2.patch --]
[-- Type: application/octet-stream, Size: 5123 bytes --]
From: Mohammed Abdullah Ali Al-Obaidi <mnew_iraq@yahoo.com>
Subject: [PATCH] wireless-regdb: add regulatory rules for Iraq (IQ)
Add a regulatory entry for Iraq (ISO 3166-1 alpha-2: IQ).
Iraq is currently absent from the regulatory database. Devices set
to country=IQ fall back to the world domain (00), which leaves most
of the 5 GHz spectrum marked "no IR" and severely restricts even
2.4 GHz operation. The Iraqi Communications and Media Commission
(CMC) has now published an explicit, numerical national regulation
that fills this gap.
Source document
---------------
Title : Regulation on short-range radio communication devices
(SRD) and devices using ultra-broadband (UWB) technology
Issuer: Republic of Iraq, Communications and Media Commission
(CMC), Telecommunications Regulatory Department,
International Relations Section
Decree: Council of Commissioners decision No. 122/q-2025
In force from: 2025-09-22
Edition: First edition, 2025; 26 pages
URL : https://cmc.iq/wp-content/uploads/2025/09/Regulation-on-short-range-radio-communication-devices-SRD-and-devices-using-ultra-broadband-UWB-technology.pdf
The values below are taken directly from Article 4-1-13 ("Wireless
Access Systems / WAS") of that regulation, which is the table
governing Wi-Fi (Annex A of the regulation defines Wi-Fi as
"802.11 Local Area Networking in 2.4 and 5 GHz ISM bands"). This
is not a derived reading: every band, every EIRP value, every
indoor restriction, and every required mitigation (LBT/DAA, DFS,
TPC) is named in the regulation itself.
Bands and limits, as stated in Article 4-1-13:
2400-2483.5 MHz : 100 mW EIRP, indoor and outdoor, LBT/DAA
(EN 300 328, ERC/REC 70-03)
5150-5250 MHz : 200 mW EIRP, indoor
(EN 301 893, ITU-R Res. 229 Rev. WRC-19)
5250-5350 MHz : 200 mW EIRP, indoor
(EN 301 893) -- DFS implied via EN 301 893
5470-5725 MHz : 1000 mW EIRP, indoor, DFS + TPC
(EN 301 893)
5725-5875 MHz : 2000 mW EIRP (10 MHz ch) / 4000 mW (20 MHz ch),
indoor and outdoor
(EN 302 502)
5945-6425 MHz : 200 mW EIRP, indoor
(EN 303 687, ECC Report 75)
57-66 GHz : 10 W EIRP, indoor, LBT/DAA
(EN 302 567)
Notes on the encoding chosen below
----------------------------------
* The Iraqi regulation lists bands 2 through 4 (5150-5725 MHz) as
"Indoor"; this is encoded as NO-OUTDOOR.
* The regulation lists 5725-5875 MHz as "Indoor and outdoor"; no
NO-OUTDOOR flag is applied to that row.
* DFS for 5250-5350 is required by EN 301 893, which the regulation
references; the DFS flag is included accordingly.
* DFS and TPC for 5470-5725 are stated explicitly in the regulation
("DFS & TPC shall be implemented as adequate sharing mechanism").
* 5725-5875 MHz uses the higher of the two stated EIRP figures
(4000 mW for 20 MHz channels) since wireless-regdb expresses a
per-band ceiling, not a per-channel-width ceiling.
* AUTO-BW is set on the 5 GHz RLAN bands consistent with EN 301 893.
* The 6 GHz block is encoded at the 80 MHz channel width which is
the widest standard-power option; it can be widened in a follow-up
patch if and when CMC clarifies AFC requirements.
* 60 GHz: the regulation specifies 57-66 GHz; this matches the
existing wireless-regdb convention used by other ETSI countries.
Background on the unique 5.8 GHz figure
---------------------------------------
The 4000 mW EIRP for 5725-5875 MHz with both indoor and outdoor
operation is not the conservative European figure; it reflects an
explicit Iraqi national choice that follows EN 302 502 (BFWA).
This is included verbatim from the regulation.
Discussion thread on the OpenWrt forum where this work was
solicited:
https://forum.openwrt.org/t/configuring-openwrt-on-archer-ax23-in-iraq/231380
Signed-off-by: Mohammed Abdullah Ali Al-Obaidi <mnew_iraq@yahoo.com>
---
db.txt | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/db.txt b/db.txt
--- a/db.txt
+++ b/db.txt
@@ -insert-after-IN-block@@
+# Iraq
+# Source: Regulation on short-range radio communication devices (SRD)
+# and devices using ultra-broadband (UWB) technology, First Edition
+# 2025, issued by the Iraqi Communications and Media Commission (CMC)
+# under Council of Commissioners decision No. 122/q-2025, in force
+# from 2025-09-22. Limits below are taken from Article 4-1-13
+# (Wireless Access Systems) of that regulation.
+# https://cmc.iq/wp-content/uploads/2025/09/Regulation-on-short-range-radio-communication-devices-SRD-and-devices-using-ultra-broadband-UWB-technology.pdf
+country IQ: DFS-ETSI
+ (2400 - 2483.5 @ 40), (100 mW), wmmrule=ETSI
+ (5150 - 5250 @ 80), (200 mW), NO-OUTDOOR, AUTO-BW, wmmrule=ETSI
+ (5250 - 5350 @ 80), (200 mW), NO-OUTDOOR, DFS, AUTO-BW, wmmrule=ETSI
+ (5470 - 5725 @ 160), (1000 mW), NO-OUTDOOR, DFS, wmmrule=ETSI
+ (5725 - 5875 @ 80), (4000 mW)
+ (5945 - 6425 @ 80), (200 mW), NO-OUTDOOR, wmmrule=ETSI
+ (57000 - 66000 @ 2160), (40)
--
2.43.0
^ permalink raw reply
* Re: [PATCH v2] wifi: ath12k: fix incorrect HT/VHT/HE/EHT MCS reporting in monitor mode
From: Tamizh Raja @ 2026-05-05 16:43 UTC (permalink / raw)
To: kwan1996; +Cc: ath12k, linux-wireless
In-Reply-To: <20260505040920.57521-1-laicheehou9@gmail.com>
On Tue, May 5, 2026 at 9:40 AM kwan1996 <laicheehou9@gmail.com> wrote:
>
> In monitor mode, the driver incorrectly assigns the legacy rate
> to the rate_idx field of the radiotap header for HT/VHT/HE/EHT
> frames, ignoring the actual MCS value parsed from the hardware.
>
> This causes packet analyzers (like Wireshark) to display incorrect
> MCS values (e.g., legacy base rates instead of the true MCS).
>
> Fix this by assigning ppdu_info->mcs instead of ppdu_info->rate
> for HT/VHT/HE/EHT frame types in ath12k_dp_mon_fill_rx_rate()
> and ath12k_dp_mon_update_radiotap().
>
> Link: https://bugzilla.kernel.org/show_bug.cgi?id=220864
>
> Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.4.1-00199-QCAHKSWPL_SILICONZ
>
> Signed-off-by: kwan1996 <laicheehou9@gmail.com>
>
> ---
>
> v2: Fix indentation and formatting issues in v1.
>
> ---
> drivers/net/wireless/ath/ath12k/dp_mon.c | 10 +++++++---
> 1 file changed, 7 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/net/wireless/ath/ath12k/dp_mon.c b/drivers/net/wireless/ath/ath12k/dp_mon.c
> index 39d1967..4119bb8 100644
> --- a/drivers/net/wireless/ath/ath12k/dp_mon.c
> +++ b/drivers/net/wireless/ath/ath12k/dp_mon.c
> @@ -1925,6 +1925,7 @@ ath12k_dp_mon_fill_rx_rate(struct ath12k *ar,
> }
> break;
> case RX_MSDU_START_PKT_TYPE_11N:
> + rate_mcs = ppdu_info->mcs;
Can we assign this rate_mcs before the switch case? Since in all cases
we are assigning unmodified ppdu_info->mcs.
> rx_status->encoding = RX_ENC_HT;
> if (rate_mcs > ATH12K_HT_MCS_MAX) {
> ath12k_warn(ar->ab,
> @@ -1937,6 +1938,7 @@ ath12k_dp_mon_fill_rx_rate(struct ath12k *ar,
> rx_status->enc_flags |= RX_ENC_FLAG_SHORT_GI;
> break;
> case RX_MSDU_START_PKT_TYPE_11AC:
> + rate_mcs = ppdu_info->mcs;
> rx_status->encoding = RX_ENC_VHT;
> rx_status->rate_idx = rate_mcs;
> if (rate_mcs > ATH12K_VHT_MCS_MAX) {
> @@ -1949,6 +1951,7 @@ ath12k_dp_mon_fill_rx_rate(struct ath12k *ar,
> rx_status->enc_flags |= RX_ENC_FLAG_SHORT_GI;
> break;
> case RX_MSDU_START_PKT_TYPE_11AX:
> + rate_mcs = ppdu_info->mcs;
> rx_status->rate_idx = rate_mcs;
> if (rate_mcs > ATH12K_HE_MCS_MAX) {
> ath12k_warn(ar->ab,
> @@ -1960,6 +1963,7 @@ ath12k_dp_mon_fill_rx_rate(struct ath12k *ar,
> rx_status->he_gi = ath12k_he_gi_to_nl80211_he_gi(sgi);
> break;
> case RX_MSDU_START_PKT_TYPE_11BE:
> + rate_mcs = ppdu_info->mcs;
> rx_status->rate_idx = rate_mcs;
> if (rate_mcs > ATH12K_EHT_MCS_MAX) {
> ath12k_warn(ar->ab,
> @@ -2259,13 +2263,13 @@ static void ath12k_dp_mon_update_radiotap(struct ath12k *ar,
> rxs->encoding = RX_ENC_HE;
> ptr = skb_push(mon_skb, sizeof(struct ieee80211_radiotap_he));
> ath12k_dp_mon_rx_update_radiotap_he(ppduinfo, ptr);
> - rxs->rate_idx = ppduinfo->rate;
> + rxs->rate_idx = ppduinfo->mcs;
> } else if (ppduinfo->vht_flags) {
> rxs->encoding = RX_ENC_VHT;
> - rxs->rate_idx = ppduinfo->rate;
> + rxs->rate_idx = ppduinfo->mcs;
> } else if (ppduinfo->ht_flags) {
> rxs->encoding = RX_ENC_HT;
> - rxs->rate_idx = ppduinfo->rate;
> + rxs->rate_idx = ppduinfo->mcs;
rate_idx should be assigned with ppdu_info->rate only not mcs.
> } else {
> rxs->encoding = RX_ENC_LEGACY;
> sband = &ar->mac.sbands[rxs->band];
> --
> 2.34.1
>
>
^ permalink raw reply
* [PATCH wireless-next 14/14] wifi: mac80211_hwsim: Support Tx of multicast data on NAN
From: Miri Korenblit @ 2026-05-05 16:42 UTC (permalink / raw)
To: linux-wireless; +Cc: Ilan Peer, Benjamin Berg
In-Reply-To: <20260505164219.2806117-1-miriam.rachel.korenblit@intel.com>
From: Ilan Peer <ilan.peer@intel.com>
Add support for transmitting multicast data frames. These
frames can be transmitted when all the peer NDI stations
on the interface are available at the current slot.
Signed-off-by: Ilan Peer <ilan.peer@intel.com>
Reviewed-by: Benjamin Berg <benjamin.berg@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
.../net/wireless/virtual/mac80211_hwsim_nan.c | 56 ++++++++++++++++++-
1 file changed, 53 insertions(+), 3 deletions(-)
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_nan.c b/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
index f786694b2932..a4ec930d9b69 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
@@ -41,6 +41,13 @@ struct hwsim_nan_sta_iter_ctx {
bool can_tx;
};
+struct hwsim_nan_mcast_data_iter_ctx {
+ struct ieee80211_hw *hw;
+ struct ieee80211_vif *vif;
+ size_t n_vif_sta;
+ size_t n_sta_can_tx;
+};
+
static void mac80211_hwsim_nan_resume_txqs(struct mac80211_hwsim_data *data);
static u64 hwsim_nan_get_timer_tsf(struct mac80211_hwsim_data *data)
@@ -1103,6 +1110,42 @@ mac80211_hwsim_nan_resume_txqs_timer(struct hrtimer *timer)
return HRTIMER_NORESTART;
}
+static void
+hwsim_nan_can_mcast_sta_transmit(void *_ctx, struct ieee80211_sta *sta)
+{
+ struct hwsim_nan_mcast_data_iter_ctx *ctx = _ctx;
+ struct ieee80211_txq *txq = sta->txq[0];
+
+ if (!txq || txq->vif != ctx->vif)
+ return;
+
+ ctx->n_vif_sta++;
+ if (mac80211_hwsim_nan_txq_transmitting(ctx->hw, txq))
+ ctx->n_sta_can_tx++;
+}
+
+static bool
+mac80211_hwsim_nan_mcast_data_transmitting(struct ieee80211_hw *hw,
+ struct ieee80211_txq *txq)
+{
+ struct mac80211_hwsim_data *data = hw->priv;
+ struct hwsim_nan_mcast_data_iter_ctx ctx = {
+ .hw = hw,
+ .vif = txq->vif,
+ .n_sta_can_tx = 0,
+ .n_vif_sta = 0,
+ };
+
+ /* Check if all the stations associated with the current
+ * interface are available.
+ */
+ ieee80211_iterate_stations_atomic(data->hw,
+ hwsim_nan_can_mcast_sta_transmit,
+ &ctx);
+
+ return ctx.n_vif_sta && ctx.n_sta_can_tx == ctx.n_vif_sta;
+}
+
bool mac80211_hwsim_nan_txq_transmitting(struct ieee80211_hw *hw,
struct ieee80211_txq *txq)
{
@@ -1125,9 +1168,16 @@ bool mac80211_hwsim_nan_txq_transmitting(struct ieee80211_hw *hw,
is_dw_slot = mac80211_hwsim_nan_is_dw_slot(data, slot);
- /* Non-STA TXQ: allow management frames during DW */
- if (!txq->sta)
- return is_dw_slot;
+ if (!txq->sta) {
+ /* Non-STA TXQ: allow management frames during DW */
+ if (txq->vif->type == NL80211_IFTYPE_NAN)
+ return is_dw_slot;
+
+ /* Allow multicast data when all the peers are available
+ * on this slot
+ */
+ return mac80211_hwsim_nan_mcast_data_transmitting(hw, txq);
+ }
/* STA TXQ: need peer schedule for availability check */
nmi_sta = rcu_dereference(txq->sta->nmi) ?: txq->sta;
--
2.34.1
^ permalink raw reply related
* [PATCH wireless-next 13/14] wifi: mac80211_hwsim: Do not declare support for NDPE
From: Miri Korenblit @ 2026-05-05 16:42 UTC (permalink / raw)
To: linux-wireless; +Cc: Ilan Peer
In-Reply-To: <20260505164219.2806117-1-miriam.rachel.korenblit@intel.com>
From: Ilan Peer <ilan.peer@intel.com>
Do not declare support for NAN Data Path Extension attribute
as this is handled by user space and should be set by it.
Signed-off-by: Ilan Peer <ilan.peer@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
drivers/net/wireless/virtual/mac80211_hwsim_main.c | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index bf45d48a3fe0..a214a3a18841 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -5715,8 +5715,7 @@ static int mac80211_hwsim_new_radio(struct genl_info *info,
hw->wiphy->nan_capa.n_antennas = 0x22;
hw->wiphy->nan_capa.max_channel_switch_time = 0;
hw->wiphy->nan_capa.dev_capabilities =
- NAN_DEV_CAPA_EXT_KEY_ID_SUPPORTED |
- NAN_DEV_CAPA_NDPE_SUPPORTED;
+ NAN_DEV_CAPA_EXT_KEY_ID_SUPPORTED;
wiphy_ext_feature_set(hw->wiphy,
NL80211_EXT_FEATURE_SECURE_NAN);
--
2.34.1
^ permalink raw reply related
* [PATCH wireless-next 12/14] wifi: mac80211_hwsim: enable NAN_DATA interface simulation support
From: Miri Korenblit @ 2026-05-05 16:42 UTC (permalink / raw)
To: linux-wireless; +Cc: Daniel Gabay
In-Reply-To: <20260505164219.2806117-1-miriam.rachel.korenblit@intel.com>
From: Daniel Gabay <daniel.gabay@intel.com>
Enable NAN_DATA interface simulation support by adding it to the
supported interface types. This completes the NAN Data Path
simulation introduced in the previous patches.
Signed-off-by: Daniel Gabay <daniel.gabay@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
drivers/net/wireless/virtual/mac80211_hwsim_main.c | 9 +++------
1 file changed, 3 insertions(+), 6 deletions(-)
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index 2b228ae3029a..bf45d48a3fe0 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -6765,12 +6765,9 @@ static int hwsim_new_radio_nl(struct sk_buff *msg, struct genl_info *info)
param.p2p_device = true;
}
- /* ensure both flag and iftype support is honored */
- if (param.nan_device ||
- param.iftypes & BIT(NL80211_IFTYPE_NAN)) {
- param.iftypes |= BIT(NL80211_IFTYPE_NAN);
- param.nan_device = true;
- }
+ if (param.nan_device)
+ param.iftypes |= BIT(NL80211_IFTYPE_NAN) |
+ BIT(NL80211_IFTYPE_NAN_DATA);
if (info->attrs[HWSIM_ATTR_CIPHER_SUPPORT]) {
u32 len = nla_len(info->attrs[HWSIM_ATTR_CIPHER_SUPPORT]);
--
2.34.1
^ permalink raw reply related
* [PATCH wireless-next 11/14] wifi: mac80211_hwsim: Declare support for secure NAN
From: Miri Korenblit @ 2026-05-05 16:42 UTC (permalink / raw)
To: linux-wireless; +Cc: Daniel Gabay, Avraham Stern
In-Reply-To: <20260505164219.2806117-1-miriam.rachel.korenblit@intel.com>
From: Daniel Gabay <daniel.gabay@intel.com>
Advertise NL80211_EXT_FEATURE_SECURE_NAN to indicate support for
NAN Pairing, enabling peer authentication and secure data path
establishment.
Signed-off-by: Daniel Gabay <daniel.gabay@intel.com>
Reviewed-by: Avraham Stern <avraham.stern@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
drivers/net/wireless/virtual/mac80211_hwsim_main.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index 66cc8c528c6b..2b228ae3029a 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -5718,6 +5718,9 @@ static int mac80211_hwsim_new_radio(struct genl_info *info,
NAN_DEV_CAPA_EXT_KEY_ID_SUPPORTED |
NAN_DEV_CAPA_NDPE_SUPPORTED;
+ wiphy_ext_feature_set(hw->wiphy,
+ NL80211_EXT_FEATURE_SECURE_NAN);
+
hrtimer_setup(&data->nan.slot_timer,
mac80211_hwsim_nan_slot_timer,
CLOCK_BOOTTIME, HRTIMER_MODE_ABS_SOFT);
--
2.34.1
^ permalink raw reply related
* [PATCH wireless-next 10/14] wifi: mac80211_hwsim: add NAN data path TX/RX support
From: Miri Korenblit @ 2026-05-05 16:42 UTC (permalink / raw)
To: linux-wireless; +Cc: Daniel Gabay
In-Reply-To: <20260505164219.2806117-1-miriam.rachel.korenblit@intel.com>
From: Daniel Gabay <daniel.gabay@intel.com>
Implement TX and RX path handling for NAN Data Path (NDP) frames,
enabling data communication between NAN peers during scheduled
availability windows.
TX path:
- Select TX channel based on current time slot: use DW channel
during Discovery Windows, or FAW channel from local
schedule during Further Availability Windows.
- Verify peer availability before transmission by checking committed
DW schedule or FAW of the peer schedule.
RX path:
- Extend NAN receive filtering to handle NAN_DATA interface frames.
- Accept incoming frames during FAW slots when channel matches local
schedule.
Signed-off-by: Daniel Gabay <daniel.gabay@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
.../wireless/virtual/mac80211_hwsim_main.c | 18 +-
.../net/wireless/virtual/mac80211_hwsim_nan.c | 250 ++++++++++++++++--
.../net/wireless/virtual/mac80211_hwsim_nan.h | 4 +-
3 files changed, 243 insertions(+), 29 deletions(-)
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index 107d47d31073..66cc8c528c6b 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -1744,7 +1744,8 @@ static void mac80211_hwsim_tx_iter(void *_data, u8 *addr,
struct tx_iter_data *data = _data;
int i;
- if (vif->type == NL80211_IFTYPE_NAN) {
+ if (vif->type == NL80211_IFTYPE_NAN ||
+ vif->type == NL80211_IFTYPE_NAN_DATA) {
data->receive = mac80211_hwsim_nan_receive(data->hw,
data->channel,
data->rx_status);
@@ -2093,13 +2094,19 @@ static void mac80211_hwsim_tx(struct ieee80211_hw *hw,
/* re-assign hdr since skb data may have shifted after encryption */
hdr = (void *)skb->data;
- if (vif && vif->type == NL80211_IFTYPE_NAN && !data->tmp_chan) {
- channel = mac80211_hwsim_nan_get_tx_channel(hw);
+ if (vif && !data->tmp_chan &&
+ (vif->type == NL80211_IFTYPE_NAN ||
+ vif->type == NL80211_IFTYPE_NAN_DATA)) {
+ struct cfg80211_chan_def chandef;
- if (WARN_ON(!channel)) {
+ mac80211_hwsim_nan_get_tx_chandef(hw, &chandef);
+ if (WARN_ON(!chandef.chan)) {
+ /* No valid channel in current slot, drop frame */
ieee80211_free_txskb(hw, skb);
return;
}
+ channel = chandef.chan;
+ confbw = chandef.width;
} else if (!data->use_chanctx) {
channel = data->channel;
confbw = data->bw;
@@ -2249,7 +2256,8 @@ void ieee80211_hwsim_wake_tx_queue(struct ieee80211_hw *hw,
};
struct sk_buff *skb;
- if (txq->vif->type == NL80211_IFTYPE_NAN &&
+ if ((txq->vif->type == NL80211_IFTYPE_NAN ||
+ txq->vif->type == NL80211_IFTYPE_NAN_DATA) &&
!mac80211_hwsim_nan_txq_transmitting(hw, txq))
return;
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_nan.c b/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
index 16883edd2215..f786694b2932 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
@@ -36,6 +36,11 @@ static_assert(DW0_TSF_MASK + 1 == 8192 * 1024);
/* Quiet time at the end of each slot where TX is suppressed */
#define NAN_CHAN_SWITCH_TIME_US 256
+struct hwsim_nan_sta_iter_ctx {
+ struct ieee80211_hw *hw;
+ bool can_tx;
+};
+
static void mac80211_hwsim_nan_resume_txqs(struct mac80211_hwsim_data *data);
static u64 hwsim_nan_get_timer_tsf(struct mac80211_hwsim_data *data)
@@ -77,6 +82,109 @@ static u64 hwsim_nan_get_master_rank(struct mac80211_hwsim_data *data)
data->nan.device_vif->addr);
}
+static bool mac80211_hwsim_nan_is_dw_slot(struct mac80211_hwsim_data *data,
+ u8 slot)
+{
+ return slot == SLOT_24GHZ_DW ||
+ (slot == SLOT_5GHZ_DW &&
+ (data->nan.bands & BIT(NL80211_BAND_5GHZ)));
+}
+
+static bool
+hwsim_nan_rx_chandef_compatible(struct mac80211_hwsim_data *data, u8 slot,
+ struct ieee80211_channel *rx_chan, u8 rx_bw)
+{
+ static const int bw_to_mhz[] = {
+ [RATE_INFO_BW_20] = 20, [RATE_INFO_BW_40] = 40,
+ [RATE_INFO_BW_80] = 80, [RATE_INFO_BW_160] = 160,
+ };
+ struct cfg80211_chan_def sched_chandef;
+ int rx_mhz, sched_mhz;
+
+ scoped_guard(spinlock_bh, &data->nan.state_lock)
+ sched_chandef = data->nan.local_sched[slot];
+
+ if (!sched_chandef.chan ||
+ sched_chandef.chan->center_freq != rx_chan->center_freq)
+ return false;
+
+ if (rx_bw >= ARRAY_SIZE(bw_to_mhz) ||!bw_to_mhz[rx_bw])
+ return false;
+
+ rx_mhz = bw_to_mhz[rx_bw];
+ sched_mhz = cfg80211_chandef_get_width(&sched_chandef);
+
+ /* Accept RX at narrower or equal bandwidth */
+ return rx_mhz <= sched_mhz;
+}
+
+static bool hwsim_nan_peer_present_in_dw(struct hwsim_sta_priv *sp, u64 tsf)
+{
+ u8 slot = hwsim_nan_slot_from_tsf(tsf);
+ u8 cdw = 0;
+ u8 dw_index, wake_interval;
+ u16 committed_dw;
+
+ scoped_guard(spinlock_bh, &sp->nan_sched.lock)
+ committed_dw = sp->nan_sched.committed_dw;
+
+ /* If peer doesn't advertise committed DW, assume presence in
+ * all 2.4 GHz DW slots
+ */
+ if (!committed_dw)
+ return slot == SLOT_24GHZ_DW;
+
+ /* Get DW index (0-15) within the 16-DWST DW0 cycle */
+ dw_index = (tsf / ieee80211_tu_to_usec(DWST_TU)) & 0xf;
+
+ /* Extract CDW for the appropriate band (spec Table 80) */
+ if (slot == SLOT_24GHZ_DW)
+ cdw = committed_dw & 0x7;
+ else if (slot == SLOT_5GHZ_DW)
+ cdw = (committed_dw >> 3) & 0x7;
+
+ if (cdw == 0)
+ return false;
+
+ /* Peer wakes every 2^(cdw-1) DWs: 1, 2, 4, 8, or 16 */
+ wake_interval = 1 << (cdw - 1);
+
+ return (dw_index % wake_interval) == 0;
+}
+
+static bool
+hwsim_nan_peer_present_in_faw(struct hwsim_sta_priv *sp,
+ struct mac80211_hwsim_data *data, u8 slot)
+{
+ struct cfg80211_chan_def local_chandef;
+
+ scoped_guard(spinlock_bh, &data->nan.state_lock)
+ local_chandef = data->nan.local_sched[slot];
+
+ if (!local_chandef.chan)
+ return false;
+
+ scoped_guard(spinlock_bh, &sp->nan_sched.lock) {
+ for (int i = 0; i < CFG80211_NAN_MAX_PEER_MAPS; i++) {
+ struct cfg80211_chan_def *peer_chandef;
+
+ if (sp->nan_sched.maps[i].map_id ==
+ CFG80211_NAN_INVALID_MAP_ID)
+ continue;
+
+ peer_chandef = &sp->nan_sched.maps[i].chans[slot];
+ if (!peer_chandef->chan)
+ continue;
+
+ if (cfg80211_chandef_compatible(&local_chandef,
+ peer_chandef))
+ return true;
+ }
+ }
+
+ return false;
+}
+
static void
mac80211_hwsim_nan_schedule_slot(struct mac80211_hwsim_data *data, u8 slot,
bool discontinuity)
@@ -880,15 +988,65 @@ int mac80211_hwsim_nan_change_config(struct ieee80211_hw *hw,
return 0;
}
+static void hwsim_nan_can_sta_transmit(void *_ctx, struct ieee80211_sta *sta)
+{
+ struct hwsim_nan_sta_iter_ctx *ctx = _ctx;
+
+ if (ctx->can_tx)
+ return;
+
+ for (int i = 0; i < ARRAY_SIZE(sta->txq); i++) {
+ struct ieee80211_txq *txq = sta->txq[i];
+
+ if (!txq)
+ continue;
+
+ if (txq->vif->type != NL80211_IFTYPE_NAN &&
+ txq->vif->type != NL80211_IFTYPE_NAN_DATA)
+ return;
+
+ if (mac80211_hwsim_nan_txq_transmitting(ctx->hw, txq)) {
+ ctx->can_tx = true;
+ return;
+ }
+ }
+}
+
static void mac80211_hwsim_nan_resume_txqs(struct mac80211_hwsim_data *data)
{
+ u64 tsf = mac80211_hwsim_get_tsf(data->hw, data->nan.device_vif);
+ u8 slot = hwsim_nan_slot_from_tsf(tsf);
+ bool is_dw_slot = mac80211_hwsim_nan_is_dw_slot(data, slot);
+ struct hwsim_nan_sta_iter_ctx ctx = {
+ .hw = data->hw,
+ .can_tx = false,
+ };
u32 timeout_ns;
- /* Nothing to do if we are not in a DW */
- if (!mac80211_hwsim_nan_txq_transmitting(data->hw,
- data->nan.device_vif->txq_mgmt))
+ /* Outside DW, require local FAW schedule to proceed */
+ if (!is_dw_slot) {
+ scoped_guard(spinlock_bh, &data->nan.state_lock) {
+ if (!data->nan.local_sched[slot].chan)
+ return;
+ }
+ }
+
+ guard(rcu)();
+
+ /* Check if management queue can transmit */
+ if (mac80211_hwsim_nan_txq_transmitting(data->hw,
+ data->nan.device_vif->txq_mgmt))
+ goto resume_txqs_timer;
+
+ /* Check if any STA queue can transmit */
+ ieee80211_iterate_stations_atomic(data->hw,
+ hwsim_nan_can_sta_transmit,
+ &ctx);
+
+ if (!ctx.can_tx)
return;
+resume_txqs_timer:
/*
* Wait a bit and also randomize things so that not everyone is TXing
* at the same time. Each slot is 16 TU long, this waits between 100 us
@@ -902,6 +1060,26 @@ static void mac80211_hwsim_nan_resume_txqs(struct mac80211_hwsim_data *data)
HRTIMER_MODE_REL_SOFT);
}
+static void hwsim_nan_wake_sta_iter(void *_data, struct ieee80211_sta *sta)
+{
+ struct ieee80211_hw *hw = _data;
+
+ for (int i = 0; i < ARRAY_SIZE(sta->txq); i++) {
+ struct ieee80211_txq *txq = sta->txq[i];
+
+ if (!txq)
+ continue;
+
+ /* exit early if non-NAN */
+ if (txq->vif->type != NL80211_IFTYPE_NAN &&
+ txq->vif->type != NL80211_IFTYPE_NAN_DATA)
+ return;
+
+ if (mac80211_hwsim_nan_txq_transmitting(hw, txq))
+ ieee80211_hwsim_wake_tx_queue(hw, txq);
+ }
+}
+
enum hrtimer_restart
mac80211_hwsim_nan_resume_txqs_timer(struct hrtimer *timer)
{
@@ -917,6 +1095,11 @@ mac80211_hwsim_nan_resume_txqs_timer(struct hrtimer *timer)
ieee80211_hwsim_wake_tx_queue(data->hw,
data->nan.device_vif->txq_mgmt);
+ /* Wake TX queues for all stations */
+ ieee80211_iterate_stations_atomic(data->hw,
+ hwsim_nan_wake_sta_iter,
+ data->hw);
+
return HRTIMER_NORESTART;
}
@@ -924,6 +1107,9 @@ bool mac80211_hwsim_nan_txq_transmitting(struct ieee80211_hw *hw,
struct ieee80211_txq *txq)
{
struct mac80211_hwsim_data *data = hw->priv;
+ struct ieee80211_sta *nmi_sta;
+ struct hwsim_sta_priv *sp;
+ bool is_dw_slot;
u64 tsf;
u8 slot;
@@ -937,36 +1123,54 @@ bool mac80211_hwsim_nan_txq_transmitting(struct ieee80211_hw *hw,
if (slot != hwsim_nan_slot_from_tsf(tsf + NAN_CHAN_SWITCH_TIME_US))
return false;
- /* Check NAN device interface management frame transmission */
- if (!txq->sta) {
- /* Only transmit these during one of the DWs */
- if (slot == SLOT_24GHZ_DW ||
- (slot == SLOT_5GHZ_DW &&
- (data->nan.bands & BIT(NL80211_BAND_5GHZ))))
- return true;
+ is_dw_slot = mac80211_hwsim_nan_is_dw_slot(data, slot);
- return false;
+ /* Non-STA TXQ: allow management frames during DW */
+ if (!txq->sta)
+ return is_dw_slot;
+
+ /* STA TXQ: need peer schedule for availability check */
+ nmi_sta = rcu_dereference(txq->sta->nmi) ?: txq->sta;
+ sp = (void *)nmi_sta->drv_priv;
+
+ /* DW slot: NDI can TX only mgmt but not worth checking,
+ * NMI checks peer's committed DW
+ */
+ if (is_dw_slot) {
+ if (txq->vif->type == NL80211_IFTYPE_NAN_DATA)
+ return false;
+ return hwsim_nan_peer_present_in_dw(sp, tsf);
}
- return true;
+ /* FAW slot: verify local schedule and peer availability */
+ return hwsim_nan_peer_present_in_faw(sp, data, slot);
}
-struct ieee80211_channel *
-mac80211_hwsim_nan_get_tx_channel(struct ieee80211_hw *hw)
+void mac80211_hwsim_nan_get_tx_chandef(struct ieee80211_hw *hw,
+ struct cfg80211_chan_def *chandef)
{
struct mac80211_hwsim_data *data = hw->priv;
u64 tsf = mac80211_hwsim_get_tsf(data->hw, data->nan.device_vif);
u8 slot = hwsim_nan_slot_from_tsf(tsf);
- if (slot == SLOT_24GHZ_DW)
- return ieee80211_get_channel(hw->wiphy, 2437);
+ /* DW slots are always 20 MHz */
+ if (slot == SLOT_24GHZ_DW) {
+ cfg80211_chandef_create(chandef,
+ ieee80211_get_channel(hw->wiphy, 2437),
+ NL80211_CHAN_NO_HT);
+ return;
+ }
- if (slot == SLOT_5GHZ_DW &&
- data->nan.bands & BIT(NL80211_BAND_5GHZ))
- return ieee80211_get_channel(hw->wiphy, 5745);
+ if (slot == SLOT_5GHZ_DW && data->nan.bands & BIT(NL80211_BAND_5GHZ)) {
+ cfg80211_chandef_create(chandef,
+ ieee80211_get_channel(hw->wiphy, 5745),
+ NL80211_CHAN_NO_HT);
+ return;
+ }
- /* drop frame and warn, NAN_CHAN_SWITCH_TIME_US should avoid races */
- return NULL;
+ /* FAW slot: copy local schedule for this slot */
+ scoped_guard(spinlock_bh, &data->nan.state_lock)
+ *chandef = data->nan.local_sched[slot];
}
bool mac80211_hwsim_nan_receive(struct ieee80211_hw *hw,
@@ -1006,7 +1210,9 @@ bool mac80211_hwsim_nan_receive(struct ieee80211_hw *hw,
channel->center_freq == 5745)
return true;
- return false;
+ /* Accept frames during FAW slots if chandef is compatible */
+ return hwsim_nan_rx_chandef_compatible(data, slot, channel,
+ rx_status->bw);
}
void mac80211_hwsim_nan_local_sched_changed(struct ieee80211_hw *hw,
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_nan.h b/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
index eb53bacee206..81e105ac7b8e 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
@@ -86,8 +86,8 @@ int mac80211_hwsim_nan_peer_sched_changed(struct ieee80211_hw *hw,
bool mac80211_hwsim_nan_txq_transmitting(struct ieee80211_hw *hw,
struct ieee80211_txq *txq);
-struct ieee80211_channel *
-mac80211_hwsim_nan_get_tx_channel(struct ieee80211_hw *hw);
+void mac80211_hwsim_nan_get_tx_chandef(struct ieee80211_hw *hw,
+ struct cfg80211_chan_def *chandef);
bool mac80211_hwsim_nan_receive(struct ieee80211_hw *hw,
struct ieee80211_channel *channel,
--
2.34.1
^ permalink raw reply related
* [PATCH wireless-next 09/14] wifi: mac80211_hwsim: set HAS_RATE_CONTROL when using NAN
From: Miri Korenblit @ 2026-05-05 16:42 UTC (permalink / raw)
To: linux-wireless; +Cc: Daniel Gabay
In-Reply-To: <20260505164219.2806117-1-miriam.rachel.korenblit@intel.com>
From: Daniel Gabay <daniel.gabay@intel.com>
- NAN switches between bands/channels per its schedule, so mac80211
rate control can't work, set HAS_RATE_CONTROL instead.
- Skip rate control checks for NAN interfaces in
mac80211_hwsim_sta_rc_update() as it's not relevant.
- Move set_rts_threshold stub to HWSIM_COMMON_OPS and return 0 instead
of -EOPNOTSUPP to prevent failures in non-MLO tests that set RTS
threshold (hwsim ignores the use_rts instruction from mac80211
anyway).
Signed-off-by: Daniel Gabay <daniel.gabay@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
.../net/wireless/virtual/mac80211_hwsim_main.c | 15 +++++++++++++--
1 file changed, 13 insertions(+), 2 deletions(-)
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index 1ea33ec577dd..107d47d31073 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -2747,6 +2747,10 @@ mac80211_hwsim_sta_rc_update(struct ieee80211_hw *hw,
u32 bw = U32_MAX;
int link_id;
+ if (vif->type == NL80211_IFTYPE_NAN ||
+ vif->type == NL80211_IFTYPE_NAN_DATA)
+ return;
+
rcu_read_lock();
for (link_id = 0;
link_id < ARRAY_SIZE(vif->link_conf);
@@ -3475,7 +3479,8 @@ static int mac80211_hwsim_tx_last_beacon(struct ieee80211_hw *hw)
static int mac80211_hwsim_set_rts_threshold(struct ieee80211_hw *hw,
int radio_idx, u32 value)
{
- return -EOPNOTSUPP;
+ /* hwsim ignores the use_rts instruction from mac80211 anyway */
+ return 0;
}
static int mac80211_hwsim_change_vif_links(struct ieee80211_hw *hw,
@@ -4239,6 +4244,7 @@ static int mac80211_hwsim_set_radar_background(struct ieee80211_hw *hw,
.abort_pmsr = mac80211_hwsim_abort_pmsr, \
.set_radar_background = mac80211_hwsim_set_radar_background, \
.set_key = mac80211_hwsim_set_key, \
+ .set_rts_threshold = mac80211_hwsim_set_rts_threshold, \
.start_nan = mac80211_hwsim_nan_start, \
.stop_nan = mac80211_hwsim_nan_stop, \
.nan_change_conf = mac80211_hwsim_nan_change_config, \
@@ -4284,7 +4290,6 @@ static const struct ieee80211_ops mac80211_hwsim_mchan_ops = {
static const struct ieee80211_ops mac80211_hwsim_mlo_ops = {
HWSIM_COMMON_OPS
HWSIM_CHANCTX_OPS
- .set_rts_threshold = mac80211_hwsim_set_rts_threshold,
.change_vif_links = mac80211_hwsim_change_vif_links,
.change_sta_links = mac80211_hwsim_change_sta_links,
.sta_state = mac80211_hwsim_sta_state,
@@ -5726,6 +5731,12 @@ static int mac80211_hwsim_new_radio(struct genl_info *info,
hw->wiphy->nan_capa.phy.ht = hwsim_nan_ht_cap;
hw->wiphy->nan_capa.phy.vht = hwsim_nan_vht_cap;
hw->wiphy->nan_capa.phy.he = hwsim_nan_he_cap;
+
+ /*
+ * NAN switches between bands/channels per its schedule,
+ * so mac80211 rate control can't work here.
+ */
+ ieee80211_hw_set(hw, HAS_RATE_CONTROL);
}
data->if_combination.radar_detect_widths =
--
2.34.1
^ permalink raw reply related
* [PATCH wireless-next 08/14] wifi: mac80211_hwsim: implement NAN schedule callbacks
From: Miri Korenblit @ 2026-05-05 16:42 UTC (permalink / raw)
To: linux-wireless; +Cc: Daniel Gabay
In-Reply-To: <20260505164219.2806117-1-miriam.rachel.korenblit@intel.com>
From: Daniel Gabay <daniel.gabay@intel.com>
Implement mac80211 schedule callbacks for NAN Data Path support:
- Track local schedule via BSS_CHANGED_NAN_LOCAL_SCHED, caching
the channel for each 16TU time slot.
- Copy peer schedule to driver-private storage in
nan_peer_sched_changed callback for use in TX availability
decisions.
Signed-off-by: Daniel Gabay <daniel.gabay@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
.../net/wireless/virtual/mac80211_hwsim_i.h | 21 +++++
.../wireless/virtual/mac80211_hwsim_main.c | 14 ++--
.../net/wireless/virtual/mac80211_hwsim_nan.c | 80 +++++++++++++++++++
.../net/wireless/virtual/mac80211_hwsim_nan.h | 15 +++-
4 files changed, 121 insertions(+), 9 deletions(-)
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_i.h b/drivers/net/wireless/virtual/mac80211_hwsim_i.h
index d182b5117bfb..0f0f2ac6d80e 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_i.h
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_i.h
@@ -14,6 +14,27 @@
#include "mac80211_hwsim.h"
#include "mac80211_hwsim_nan.h"
+struct hwsim_sta_nan_sched {
+ /* Later members are protected by this lock */
+ spinlock_t lock;
+ u16 committed_dw;
+ struct {
+ u8 map_id;
+ struct cfg80211_chan_def chans[CFG80211_NAN_SCHED_NUM_TIME_SLOTS];
+ } maps[CFG80211_NAN_MAX_PEER_MAPS];
+};
+
+struct hwsim_sta_priv {
+ u32 magic;
+ unsigned int last_link;
+ u16 active_links_rx;
+
+ /* NAN peer schedule - must be accessed under nan_sched.lock */
+ struct hwsim_sta_nan_sched nan_sched;
+};
+
+#define HWSIM_STA_MAGIC 0x6d537749
+
struct mac80211_hwsim_link_data {
u32 link_id;
u64 beacon_int /* beacon interval in us */;
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index fc940b38c52c..1ea33ec577dd 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -252,14 +252,6 @@ static inline void hwsim_clear_magic(struct ieee80211_vif *vif)
vp->magic = 0;
}
-struct hwsim_sta_priv {
- u32 magic;
- unsigned int last_link;
- u16 active_links_rx;
-};
-
-#define HWSIM_STA_MAGIC 0x6d537749
-
static inline void hwsim_check_sta_magic(struct ieee80211_sta *sta)
{
struct hwsim_sta_priv *sp = (void *)sta->drv_priv;
@@ -2652,6 +2644,9 @@ static void mac80211_hwsim_vif_info_changed(struct ieee80211_hw *hw,
vp->aid = vif->cfg.aid;
}
+ if (changed & BSS_CHANGED_NAN_LOCAL_SCHED)
+ mac80211_hwsim_nan_local_sched_changed(hw, vif);
+
if (vif->type == NL80211_IFTYPE_STATION &&
changed & (BSS_CHANGED_MLD_VALID_LINKS | BSS_CHANGED_MLD_TTLM)) {
u16 usable_links = ieee80211_vif_usable_links(vif);
@@ -2818,6 +2813,8 @@ static int mac80211_hwsim_sta_add(struct ieee80211_hw *hw,
sp->active_links_rx = sta->valid_links;
}
+ spin_lock_init(&sp->nan_sched.lock);
+
return 0;
}
@@ -4245,6 +4242,7 @@ static int mac80211_hwsim_set_radar_background(struct ieee80211_hw *hw,
.start_nan = mac80211_hwsim_nan_start, \
.stop_nan = mac80211_hwsim_nan_stop, \
.nan_change_conf = mac80211_hwsim_nan_change_config, \
+ .nan_peer_sched_changed = mac80211_hwsim_nan_peer_sched_changed, \
HWSIM_DEBUGFS_OPS
#define HWSIM_NON_MLO_OPS \
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_nan.c b/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
index 6053b6f8f91f..16883edd2215 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
@@ -1008,3 +1008,83 @@ bool mac80211_hwsim_nan_receive(struct ieee80211_hw *hw,
return false;
}
+
+void mac80211_hwsim_nan_local_sched_changed(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif)
+{
+ struct mac80211_hwsim_data *data = hw->priv;
+ struct ieee80211_nan_channel **slots = vif->cfg.nan_sched.schedule;
+
+ if (WARN_ON(vif->type != NL80211_IFTYPE_NAN))
+ return;
+
+ spin_lock_bh(&data->nan.state_lock);
+
+ for (int i = 0; i < ARRAY_SIZE(data->nan.local_sched); i++) {
+ struct ieee80211_chanctx_conf *chanctx;
+
+ if (!slots[i] || IS_ERR(slots[i])) {
+ memset(&data->nan.local_sched[i], 0,
+ sizeof(data->nan.local_sched[i]));
+ continue;
+ }
+
+ chanctx = slots[i]->chanctx_conf;
+ if (!chanctx) {
+ memset(&data->nan.local_sched[i], 0,
+ sizeof(data->nan.local_sched[i]));
+ continue;
+ }
+
+ data->nan.local_sched[i] = chanctx->def;
+ }
+
+ spin_unlock_bh(&data->nan.state_lock);
+}
+
+int mac80211_hwsim_nan_peer_sched_changed(struct ieee80211_hw *hw,
+ struct ieee80211_sta *sta)
+{
+ struct hwsim_sta_priv *sp = (void *)sta->drv_priv;
+ struct ieee80211_nan_peer_sched *sched = sta->nan_sched;
+
+ spin_lock_bh(&sp->nan_sched.lock);
+
+ /* Clear existing schedule */
+ sp->nan_sched.committed_dw = 0;
+ for (int i = 0; i < CFG80211_NAN_MAX_PEER_MAPS; i++) {
+ sp->nan_sched.maps[i].map_id = CFG80211_NAN_INVALID_MAP_ID;
+ memset(sp->nan_sched.maps[i].chans, 0,
+ sizeof(sp->nan_sched.maps[i].chans));
+ }
+
+ if (!sched)
+ goto out;
+
+ sp->nan_sched.committed_dw = sched->committed_dw;
+
+ for (int i = 0; i < CFG80211_NAN_MAX_PEER_MAPS; i++) {
+ struct ieee80211_nan_peer_map *map = &sched->maps[i];
+
+ if (map->map_id == CFG80211_NAN_INVALID_MAP_ID)
+ continue;
+
+ sp->nan_sched.maps[i].map_id = map->map_id;
+
+ for (int j = 0; j < CFG80211_NAN_SCHED_NUM_TIME_SLOTS; j++) {
+ struct ieee80211_nan_channel *peer_chan =
+ map->slots[j];
+
+ if (peer_chan && peer_chan->chanreq.oper.chan)
+ sp->nan_sched.maps[i].chans[j] =
+ peer_chan->chanreq.oper;
+ else
+ memset(&sp->nan_sched.maps[i].chans[j], 0,
+ sizeof(sp->nan_sched.maps[i].chans[j]));
+ }
+ }
+
+out:
+ spin_unlock_bh(&sp->nan_sched.lock);
+ return 0;
+}
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_nan.h b/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
index 3199e5c5376b..eb53bacee206 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* mac80211_hwsim_nan - NAN software simulation for mac80211_hwsim
- * Copyright (C) 2025 Intel Corporation
+ * Copyright (C) 2025-2026 Intel Corporation
*/
#ifndef __MAC80211_HWSIM_NAN_H
@@ -52,6 +52,13 @@ struct mac80211_hwsim_nan_data {
bool tsf_adjusted;
bool tsf_discontinuity;
+
+ /*
+ * Local schedule - stores channel definition for each 16TU slot.
+ * Derived from NMI vif->cfg.nan_schedule. chan == NULL means not
+ * available in that slot (except DW which is implicit).
+ */
+ struct cfg80211_chan_def local_sched[CFG80211_NAN_SCHED_NUM_TIME_SLOTS];
};
enum hrtimer_restart
@@ -73,6 +80,9 @@ int mac80211_hwsim_nan_change_config(struct ieee80211_hw *hw,
struct cfg80211_nan_conf *conf,
u32 changes);
+int mac80211_hwsim_nan_peer_sched_changed(struct ieee80211_hw *hw,
+ struct ieee80211_sta *sta);
+
bool mac80211_hwsim_nan_txq_transmitting(struct ieee80211_hw *hw,
struct ieee80211_txq *txq);
@@ -86,4 +96,7 @@ bool mac80211_hwsim_nan_receive(struct ieee80211_hw *hw,
void mac80211_hwsim_nan_rx(struct ieee80211_hw *hw,
struct sk_buff *skb);
+void mac80211_hwsim_nan_local_sched_changed(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif);
+
#endif /* __MAC80211_HWSIM_NAN_H */
--
2.34.1
^ permalink raw reply related
* [PATCH wireless-next 07/14] wifi: mac80211_hwsim: add NAN PHY capabilities
From: Miri Korenblit @ 2026-05-05 16:42 UTC (permalink / raw)
To: linux-wireless; +Cc: Daniel Gabay
In-Reply-To: <20260505164219.2806117-1-miriam.rachel.korenblit@intel.com>
From: Daniel Gabay <daniel.gabay@intel.com>
Add static HT, VHT and HE PHY capabilities to the NAN capabilities
structure. These are based on the existing band capability structures
and initialization in mac80211_hwsim.
The NAN PHY capabilities are used by mac80211 and nl80211 to
advertise device capabilities for NAN data interfaces.
Signed-off-by: Daniel Gabay <daniel.gabay@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
.../wireless/virtual/mac80211_hwsim_main.c | 102 ++++++++++++++++--
1 file changed, 94 insertions(+), 8 deletions(-)
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index 3397acfe61c8..fc940b38c52c 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -5412,6 +5412,95 @@ static const struct ieee80211_sband_iftype_data sband_capa_6ghz[] = {
#endif
};
+#define HWSIM_VHT_MCS_MAP \
+ (IEEE80211_VHT_MCS_SUPPORT_0_9 << 0 | \
+ IEEE80211_VHT_MCS_SUPPORT_0_9 << 2 | \
+ IEEE80211_VHT_MCS_SUPPORT_0_9 << 4 | \
+ IEEE80211_VHT_MCS_SUPPORT_0_9 << 6 | \
+ IEEE80211_VHT_MCS_SUPPORT_0_9 << 8 | \
+ IEEE80211_VHT_MCS_SUPPORT_0_9 << 10 | \
+ IEEE80211_VHT_MCS_SUPPORT_0_9 << 12 | \
+ IEEE80211_VHT_MCS_SUPPORT_0_9 << 14)
+
+static const struct ieee80211_sta_ht_cap hwsim_nan_ht_cap = {
+ .ht_supported = true,
+ .cap = IEEE80211_HT_CAP_SUP_WIDTH_20_40 |
+ IEEE80211_HT_CAP_GRN_FLD |
+ IEEE80211_HT_CAP_SGI_20 |
+ IEEE80211_HT_CAP_SGI_40 |
+ IEEE80211_HT_CAP_DSSSCCK40,
+ .ampdu_factor = 0x3,
+ .ampdu_density = 0x6,
+ .mcs = {
+ .rx_mask = { 0xff, 0xff },
+ .tx_params = IEEE80211_HT_MCS_TX_DEFINED,
+ },
+};
+
+static const struct ieee80211_sta_vht_cap hwsim_nan_vht_cap = {
+ .vht_supported = true,
+ .cap = IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_11454 |
+ IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ |
+ IEEE80211_VHT_CAP_RXLDPC |
+ IEEE80211_VHT_CAP_SHORT_GI_80 |
+ IEEE80211_VHT_CAP_SHORT_GI_160 |
+ IEEE80211_VHT_CAP_TXSTBC |
+ IEEE80211_VHT_CAP_RXSTBC_4 |
+ IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MASK,
+ .vht_mcs = {
+ .rx_mcs_map = cpu_to_le16(HWSIM_VHT_MCS_MAP),
+ .tx_mcs_map = cpu_to_le16(HWSIM_VHT_MCS_MAP),
+ },
+};
+
+static const struct ieee80211_sta_he_cap hwsim_nan_he_cap = {
+ .has_he = true,
+ .he_cap_elem = {
+ .mac_cap_info[0] =
+ IEEE80211_HE_MAC_CAP0_HTC_HE,
+ .mac_cap_info[1] =
+ IEEE80211_HE_MAC_CAP1_TF_MAC_PAD_DUR_16US |
+ IEEE80211_HE_MAC_CAP1_MULTI_TID_AGG_RX_QOS_8,
+ .mac_cap_info[2] =
+ IEEE80211_HE_MAC_CAP2_BSR |
+ IEEE80211_HE_MAC_CAP2_MU_CASCADING |
+ IEEE80211_HE_MAC_CAP2_ACK_EN,
+ .mac_cap_info[3] =
+ IEEE80211_HE_MAC_CAP3_OMI_CONTROL |
+ IEEE80211_HE_MAC_CAP3_MAX_AMPDU_LEN_EXP_EXT_3,
+ .mac_cap_info[4] = IEEE80211_HE_MAC_CAP4_AMSDU_IN_AMPDU,
+ .phy_cap_info[0] =
+ IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G |
+ IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_160MHZ_IN_5G |
+ IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_80PLUS80_MHZ_IN_5G,
+ .phy_cap_info[1] =
+ IEEE80211_HE_PHY_CAP1_PREAMBLE_PUNC_RX_MASK |
+ IEEE80211_HE_PHY_CAP1_DEVICE_CLASS_A |
+ IEEE80211_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD |
+ IEEE80211_HE_PHY_CAP1_MIDAMBLE_RX_TX_MAX_NSTS,
+ .phy_cap_info[2] =
+ IEEE80211_HE_PHY_CAP2_NDP_4x_LTF_AND_3_2US |
+ IEEE80211_HE_PHY_CAP2_STBC_TX_UNDER_80MHZ |
+ IEEE80211_HE_PHY_CAP2_STBC_RX_UNDER_80MHZ |
+ IEEE80211_HE_PHY_CAP2_UL_MU_FULL_MU_MIMO |
+ IEEE80211_HE_PHY_CAP2_UL_MU_PARTIAL_MU_MIMO,
+
+ /*
+ * Leave all the other PHY capability bytes
+ * unset, as DCM, beam forming, RU and PPE
+ * threshold information are not supported
+ */
+ },
+ .he_mcs_nss_supp = {
+ .rx_mcs_80 = cpu_to_le16(0xfffa),
+ .tx_mcs_80 = cpu_to_le16(0xfffa),
+ .rx_mcs_160 = cpu_to_le16(0xfffa),
+ .tx_mcs_160 = cpu_to_le16(0xfffa),
+ .rx_mcs_80p80 = cpu_to_le16(0xfffa),
+ .tx_mcs_80p80 = cpu_to_le16(0xfffa),
+ },
+};
+
static void mac80211_hwsim_sband_capab(struct ieee80211_supported_band *sband)
{
switch (sband->band) {
@@ -5635,6 +5724,10 @@ static int mac80211_hwsim_new_radio(struct genl_info *info,
data->if_limits[n_limits].max = 2;
data->if_limits[n_limits].types = BIT(NL80211_IFTYPE_NAN_DATA);
n_limits++;
+
+ hw->wiphy->nan_capa.phy.ht = hwsim_nan_ht_cap;
+ hw->wiphy->nan_capa.phy.vht = hwsim_nan_vht_cap;
+ hw->wiphy->nan_capa.phy.he = hwsim_nan_he_cap;
}
data->if_combination.radar_detect_widths =
@@ -5816,14 +5909,7 @@ static int mac80211_hwsim_new_radio(struct genl_info *info,
IEEE80211_VHT_CAP_RXSTBC_4 |
IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MASK;
sband->vht_cap.vht_mcs.rx_mcs_map =
- cpu_to_le16(IEEE80211_VHT_MCS_SUPPORT_0_9 << 0 |
- IEEE80211_VHT_MCS_SUPPORT_0_9 << 2 |
- IEEE80211_VHT_MCS_SUPPORT_0_9 << 4 |
- IEEE80211_VHT_MCS_SUPPORT_0_9 << 6 |
- IEEE80211_VHT_MCS_SUPPORT_0_9 << 8 |
- IEEE80211_VHT_MCS_SUPPORT_0_9 << 10 |
- IEEE80211_VHT_MCS_SUPPORT_0_9 << 12 |
- IEEE80211_VHT_MCS_SUPPORT_0_9 << 14);
+ cpu_to_le16(HWSIM_VHT_MCS_MAP);
sband->vht_cap.vht_mcs.tx_mcs_map =
sband->vht_cap.vht_mcs.rx_mcs_map;
break;
--
2.34.1
^ permalink raw reply related
* [PATCH wireless-next 06/14] wifi: mac80211_hwsim: add NAN_DATA interface limits
From: Miri Korenblit @ 2026-05-05 16:42 UTC (permalink / raw)
To: linux-wireless; +Cc: Daniel Gabay
In-Reply-To: <20260505164219.2806117-1-miriam.rachel.korenblit@intel.com>
From: Daniel Gabay <daniel.gabay@intel.com>
Increase interface limits for NAN_DATA interface.
Signed-off-by: Daniel Gabay <daniel.gabay@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
drivers/net/wireless/virtual/mac80211_hwsim_i.h | 4 ++--
drivers/net/wireless/virtual/mac80211_hwsim_main.c | 8 +++++++-
2 files changed, 9 insertions(+), 3 deletions(-)
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_i.h b/drivers/net/wireless/virtual/mac80211_hwsim_i.h
index 5378f721c299..d182b5117bfb 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_i.h
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_i.h
@@ -4,7 +4,7 @@
* Copyright (c) 2008, Jouni Malinen <j@w1.fi>
* Copyright (c) 2011, Javier Lopez <jlopex@gmail.com>
* Copyright (c) 2016 - 2017 Intel Deutschland GmbH
- * Copyright (C) 2018 - 2025 Intel Corporation
+ * Copyright (C) 2018 - 2026 Intel Corporation
*/
#ifndef __MAC80211_HWSIM_I_H
@@ -39,7 +39,7 @@ struct mac80211_hwsim_data {
struct ieee80211_channel channels_s1g[HWSIM_NUM_S1G_CHANNELS_US];
struct ieee80211_rate rates[HWSIM_NUM_RATES];
struct ieee80211_iface_combination if_combination;
- struct ieee80211_iface_limit if_limits[4];
+ struct ieee80211_iface_limit if_limits[5];
int n_if_limits;
/* Storage space for channels, etc. */
struct mac80211_hwsim_phy_data *phy_data;
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index 969ebc28cb1e..3397acfe61c8 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -4,7 +4,7 @@
* Copyright (c) 2008, Jouni Malinen <j@w1.fi>
* Copyright (c) 2011, Javier Lopez <jlopex@gmail.com>
* Copyright (c) 2016 - 2017 Intel Deutschland GmbH
- * Copyright (C) 2018 - 2025 Intel Corporation
+ * Copyright (C) 2018 - 2026 Intel Corporation
*/
/*
@@ -5631,6 +5631,12 @@ static int mac80211_hwsim_new_radio(struct genl_info *info,
spin_lock_init(&data->nan.state_lock);
}
+ if (param->iftypes & BIT(NL80211_IFTYPE_NAN_DATA)) {
+ data->if_limits[n_limits].max = 2;
+ data->if_limits[n_limits].types = BIT(NL80211_IFTYPE_NAN_DATA);
+ n_limits++;
+ }
+
data->if_combination.radar_detect_widths =
BIT(NL80211_CHAN_WIDTH_5) |
BIT(NL80211_CHAN_WIDTH_10) |
--
2.34.1
^ permalink raw reply related
* [PATCH wireless-next 05/14] wifi: mac80211_hwsim: implement NAN synchronization
From: Miri Korenblit @ 2026-05-05 16:42 UTC (permalink / raw)
To: linux-wireless; +Cc: Benjamin Berg
In-Reply-To: <20260505164219.2806117-1-miriam.rachel.korenblit@intel.com>
From: Benjamin Berg <benjamin.berg@intel.com>
Add all the handling to do NAN synchronization on 2.4 GHz including
sending out beacons. With this, the mac80211_hwsim NAN device also works
when used in conjunction with an external medium simulation.
Note that the TSF sync is not ideal in case of an external medium
simulation. This is because the mactime for received frames needs to be
estimated and the simulation may not update the timestamp of beacons
to the actual time that the frame was transmitted.
The implementation has an initial short phase where it scans for
clusters. This facilitates cluster joining and avoids creating a new
cluster immediately, which would result in two cluster join
notifications. It does not scan otherwise and will only see another
cluster appearing if a discovery beacon happens to be sent during the
2.4 GHz discovery window (DW).
Signed-off-by: Benjamin Berg <benjamin.berg@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
.../net/wireless/virtual/mac80211_hwsim_i.h | 4 +
.../wireless/virtual/mac80211_hwsim_main.c | 18 +-
.../net/wireless/virtual/mac80211_hwsim_nan.c | 723 +++++++++++++++++-
.../net/wireless/virtual/mac80211_hwsim_nan.h | 43 ++
4 files changed, 743 insertions(+), 45 deletions(-)
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_i.h b/drivers/net/wireless/virtual/mac80211_hwsim_i.h
index 7a6495ea79e7..5378f721c299 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_i.h
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_i.h
@@ -137,6 +137,10 @@ u64 mac80211_hwsim_boottime_to_tsf(struct mac80211_hwsim_data *data,
u64 mac80211_hwsim_get_tsf(struct ieee80211_hw *hw,
struct ieee80211_vif *vif);
+void mac80211_hwsim_tx_frame(struct ieee80211_hw *hw,
+ struct sk_buff *skb,
+ struct ieee80211_channel *chan);
+
void ieee80211_hwsim_wake_tx_queue(struct ieee80211_hw *hw,
struct ieee80211_txq *txq);
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index 2de44c5fb1ff..969ebc28cb1e 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -957,10 +957,6 @@ static int hwsim_get_chanwidth(enum nl80211_chan_width bw)
return INT_MAX;
}
-static void mac80211_hwsim_tx_frame(struct ieee80211_hw *hw,
- struct sk_buff *skb,
- struct ieee80211_channel *chan);
-
/* sysfs attributes */
static void hwsim_send_ps_poll(void *dat, u8 *mac, struct ieee80211_vif *vif)
{
@@ -1865,6 +1861,9 @@ static void mac80211_hwsim_rx(struct mac80211_hwsim_data *data,
mac80211_hwsim_add_vendor_rtap(skb);
+ if (data->nan.device_vif)
+ mac80211_hwsim_nan_rx(data->hw, skb);
+
data->rx_pkts++;
data->rx_bytes += skb->len;
ieee80211_rx_irqsafe(data->hw, skb);
@@ -2359,9 +2358,9 @@ static void mac80211_hwsim_remove_interface(
mac80211_hwsim_config_mac_nl(hw, vif->addr, false);
}
-static void mac80211_hwsim_tx_frame(struct ieee80211_hw *hw,
- struct sk_buff *skb,
- struct ieee80211_channel *chan)
+void mac80211_hwsim_tx_frame(struct ieee80211_hw *hw,
+ struct sk_buff *skb,
+ struct ieee80211_channel *chan)
{
struct mac80211_hwsim_data *data = hw->priv;
u32 _portid = READ_ONCE(data->wmediumd);
@@ -5625,6 +5624,11 @@ static int mac80211_hwsim_new_radio(struct genl_info *info,
hrtimer_setup(&data->nan.resume_txqs_timer,
mac80211_hwsim_nan_resume_txqs_timer,
CLOCK_BOOTTIME, HRTIMER_MODE_ABS_SOFT);
+ hrtimer_setup(&data->nan.discovery_beacon_timer,
+ mac80211_hwsim_nan_discovery_beacon_timer,
+ CLOCK_BOOTTIME, HRTIMER_MODE_ABS_SOFT);
+
+ spin_lock_init(&data->nan.state_lock);
}
data->if_combination.radar_detect_widths =
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_nan.c b/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
index 805848172605..6053b6f8f91f 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
@@ -1,9 +1,10 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* mac80211_hwsim_nan - NAN software simulation for mac80211_hwsim
- * Copyright (C) 2025 Intel Corporation
+ * Copyright (C) 2025-2026 Intel Corporation
*/
+#include <net/cfg80211.h>
#include "mac80211_hwsim_i.h"
/* Defined as the lower 23 bits being zero */
@@ -26,11 +27,15 @@
static_assert(16 * DWST_TU * 1024 == 8192 * 1024);
static_assert(DW0_TSF_MASK + 1 == 8192 * 1024);
+/* warmup phase should be 120 seconds, which is approximately 225 DWSTs */
+#define NAN_WARMUP_DWST 225
+
+#define NAN_RSSI_CLOSE (-60)
+#define NAN_RSSI_MIDDLE (-75)
+
/* Quiet time at the end of each slot where TX is suppressed */
#define NAN_CHAN_SWITCH_TIME_US 256
-static u8 hwsim_nan_cluster_id[ETH_ALEN];
-
static void mac80211_hwsim_nan_resume_txqs(struct mac80211_hwsim_data *data);
static u64 hwsim_nan_get_timer_tsf(struct mac80211_hwsim_data *data)
@@ -45,10 +50,43 @@ static u8 hwsim_nan_slot_from_tsf(u64 tsf)
return (tsf & DWST_TSF_MASK) / ieee80211_tu_to_usec(SLOT_TU);
}
+static u64 hwsim_nan_encode_master_rank(u8 master_pref, u8 random_factor,
+ const u8 *addr)
+{
+ return ((u64)master_pref << 56) +
+ ((u64)random_factor << 48) +
+ ((u64)addr[5] << 40) +
+ ((u64)addr[4] << 32) +
+ ((u64)addr[3] << 24) +
+ ((u64)addr[2] << 16) +
+ ((u64)addr[1] << 8) +
+ ((u64)addr[0] << 0);
+}
+
+static u64 hwsim_nan_get_master_rank(struct mac80211_hwsim_data *data)
+{
+ u8 master_pref = 0;
+ u8 random_factor = 0;
+
+ if (data->nan.phase == MAC80211_HWSIM_NAN_PHASE_UP) {
+ master_pref = data->nan.master_pref;
+ random_factor = data->nan.random_factor;
+ }
+
+ return hwsim_nan_encode_master_rank(master_pref, random_factor,
+ data->nan.device_vif->addr);
+}
+
static void
-mac80211_hwsim_nan_schedule_slot(struct mac80211_hwsim_data *data, u8 slot)
+mac80211_hwsim_nan_schedule_slot(struct mac80211_hwsim_data *data, u8 slot,
+ bool discontinuity)
{
- u64 tsf = hwsim_nan_get_timer_tsf(data);
+ u64 tsf;
+
+ if (!discontinuity)
+ tsf = hwsim_nan_get_timer_tsf(data);
+ else
+ tsf = mac80211_hwsim_get_tsf(data->hw, data->nan.device_vif);
/* Only called by mac80211_hwsim_nan_dw_timer from softirq context */
lockdep_assert_in_softirq();
@@ -60,16 +98,561 @@ mac80211_hwsim_nan_schedule_slot(struct mac80211_hwsim_data *data, u8 slot)
mac80211_hwsim_tsf_to_boottime(data, tsf));
}
+void mac80211_hwsim_nan_rx(struct ieee80211_hw *hw,
+ struct sk_buff *skb)
+{
+ struct mac80211_hwsim_data *data = hw->priv;
+ const struct ieee80211_mgmt *mgmt = (void *)skb->data;
+ struct element *nan_elem = (void *)mgmt->u.beacon.variable;
+ struct ieee80211_nan_anchor_master_info *ami = NULL;
+ const struct ieee80211_nan_attr *nan_attr;
+ struct ieee80211_rx_status rx_status;
+ bool joined_cluster = false;
+ bool adopt_tsf = false;
+ bool is_sync_beacon;
+ bool is_same_cluster;
+ u64 master_rank = 0;
+ ssize_t data_len;
+ u8 slot;
+
+ /* Need a NAN vendor element at the start */
+ if (skb->len < (offsetofend(struct ieee80211_mgmt, u.beacon) + 6) ||
+ !ieee80211_is_beacon(mgmt->frame_control))
+ return;
+
+ data_len = skb->len - offsetofend(struct ieee80211_mgmt, u.beacon);
+
+ /* Copy the RX status to add a MAC timestamp if needed */
+ memcpy(&rx_status, IEEE80211_SKB_RXCB(skb),
+ sizeof(struct ieee80211_rx_status));
+
+ /* And deal with the lack of mac time stamp */
+ if ((rx_status.flag & RX_FLAG_MACTIME) != RX_FLAG_MACTIME_START) {
+ u64 tsf = mac80211_hwsim_get_tsf(hw, data->nan.device_vif);;
+
+ /* In that case there should be no timestamp */
+ WARN_ON_ONCE(rx_status.flag & RX_FLAG_MACTIME);
+
+ /* No mac timestamp, set current TSF for the frame end */
+ rx_status.flag |= RX_FLAG_MACTIME_END;
+ rx_status.mactime = tsf;
+
+ /* And translate to the start for the rest of the code */
+ rx_status.mactime =
+ ieee80211_calculate_rx_timestamp(hw, &rx_status,
+ skb->len, 0);
+ rx_status.flag &= ~RX_FLAG_MACTIME;
+ rx_status.flag |= RX_FLAG_MACTIME_START;
+
+ /* Match mac80211_hwsim_nan_receive, see comment there */
+ slot = hwsim_nan_slot_from_tsf(tsf + 128);
+ } else {
+ slot = hwsim_nan_slot_from_tsf(rx_status.mactime);
+ }
+
+ /*
+ * (overly) simplify things, only track 2.4 GHz here. Also, ignore
+ * frames outside of the 2.4 GHz DW slot, unless in the initial SCAN
+ * phase.
+ */
+ if ((slot != SLOT_24GHZ_DW &&
+ data->nan.phase != MAC80211_HWSIM_NAN_PHASE_SCAN) ||
+ rx_status.freq != 2437)
+ return;
+
+ /* Just ignore low RSSI beacons that we cannot sync to */
+ if (rx_status.signal < NAN_RSSI_MIDDLE)
+ return;
+
+ /* Needs to be a valid NAN cluster ID in A3 */
+ if (get_unaligned_be32(mgmt->bssid) != ((WLAN_OUI_WFA << 8) | 0x01))
+ return;
+
+ /* We are only interested in NAN beacons */
+ if (nan_elem->id != WLAN_EID_VENDOR_SPECIFIC ||
+ nan_elem->datalen < 4 ||
+ get_unaligned_be32(nan_elem->data) !=
+ (WLAN_OUI_WFA << 8 | WLAN_OUI_TYPE_WFA_NAN))
+ return;
+
+ u8 *nan_defragmented __free(kfree) = kzalloc(data_len, GFP_ATOMIC);
+ if (!nan_defragmented)
+ return;
+
+ data_len = cfg80211_defragment_element(nan_elem,
+ mgmt->u.beacon.variable,
+ data_len,
+ nan_defragmented, data_len,
+ WLAN_EID_FRAGMENT);
+
+ if (data_len < 0)
+ return;
+
+ /* Assume it is a synchronization beacon if beacon_int is 512 TUs */
+ is_sync_beacon = le16_to_cpu(mgmt->u.beacon.beacon_int) == DWST_TU;
+ is_same_cluster = ether_addr_equal(mgmt->bssid, data->nan.cluster_id);
+
+ for_each_nan_attr(nan_attr, nan_defragmented + 4, data_len - 4) {
+ if (nan_attr->attr == NAN_ATTR_MASTER_INDICATION &&
+ le16_to_cpu(nan_attr->length) >=
+ sizeof(struct ieee80211_nan_master_indication)) {
+ struct ieee80211_nan_master_indication *mi =
+ (void *)nan_attr->data;
+
+ master_rank =
+ hwsim_nan_encode_master_rank(mi->master_pref,
+ mi->random_factor,
+ mgmt->sa);
+ }
+
+ if (nan_attr->attr == NAN_ATTR_CLUSTER_INFO &&
+ le16_to_cpu(nan_attr->length) >=
+ sizeof(struct ieee80211_nan_anchor_master_info)) {
+ ami = (void *)nan_attr->data;
+
+ /*
+ * The AMBTT should be set to the beacon timestamp when
+ * the sender is the anchor master. We can simply
+ * modify the structure because we created a copy when
+ * defragmenting the NAN element.
+ */
+ if (ami->hop_count == 0)
+ ami->ambtt = cpu_to_le32(
+ le64_to_cpu(mgmt->u.beacon.timestamp));
+ }
+ }
+
+ /* Do the rest of the processing under lock */
+ spin_lock_bh(&data->nan.state_lock);
+
+ /*
+ * sync beacon should be discarded if the master rank is the same
+ * and the AMBTT is older than 16 * 512 TUs compared to our own TSF.
+ *
+ * Subtract the AMBTT from the lowered TSF. If the AMBTT is older
+ * (smaller) then the calculation will not underflow.
+ */
+ if (is_sync_beacon && ami &&
+ ami->master_rank == data->nan.current_ami.master_rank &&
+ (((u32)rx_status.mactime -
+ ieee80211_tu_to_usec(16 * 512)) -
+ le32_to_cpu(ami->ambtt)) < 0x8000000) {
+ wiphy_dbg(hw->wiphy,
+ "NAN: ignoring sync beacon with old AMBTT\n");
+ is_sync_beacon = false;
+ }
+
+ if (is_same_cluster && is_sync_beacon &&
+ master_rank > hwsim_nan_get_master_rank(data)) {
+ if (rx_status.signal > NAN_RSSI_CLOSE)
+ data->nan.master_transition_score += 3;
+ else
+ data->nan.master_transition_score += 1;
+ }
+
+ if (is_same_cluster && is_sync_beacon && ami &&
+ ((ami->master_rank == data->nan.current_ami.master_rank &&
+ ami->hop_count < data->nan.current_ami.hop_count) ||
+ (master_rank > hwsim_nan_get_master_rank(data) &&
+ ami->hop_count == data->nan.current_ami.hop_count))) {
+ if (rx_status.signal > NAN_RSSI_CLOSE)
+ data->nan.sync_transition_score += 3;
+ else
+ data->nan.sync_transition_score += 1;
+ }
+
+ /*
+ * Decide on TSF adjustments before updating any other state
+ */
+ if (is_same_cluster && is_sync_beacon && ami &&
+ data->nan.current_ami.hop_count != 0) {
+ if (le64_to_cpu(ami->master_rank) >
+ le64_to_cpu(data->nan.current_ami.master_rank) &&
+ ami->master_rank != data->nan.last_ami.master_rank)
+ adopt_tsf = true;
+
+ if (le64_to_cpu(ami->master_rank) >
+ le64_to_cpu(data->nan.current_ami.master_rank) &&
+ ami->master_rank == data->nan.last_ami.master_rank &&
+ le32_to_cpu(ami->ambtt) >
+ le32_to_cpu(data->nan.last_ami.ambtt))
+ adopt_tsf = true;
+
+ if (le64_to_cpu(ami->master_rank) <
+ le64_to_cpu(data->nan.current_ami.master_rank) &&
+ le64_to_cpu(ami->master_rank) >
+ hwsim_nan_get_master_rank(data) &&
+ ether_addr_equal(ami->master_addr,
+ data->nan.current_ami.master_addr))
+ adopt_tsf = true;
+
+ if (ami->master_rank == data->nan.current_ami.master_rank &&
+ le32_to_cpu(ami->ambtt) >
+ le32_to_cpu(data->nan.current_ami.ambtt))
+ adopt_tsf = true;
+
+ /* Anchor Master case is handled below */
+ }
+
+ /*
+ * NAN Cluster merging
+ */
+ if (!is_same_cluster && ami) {
+ u64 curr_amr;
+ u64 own_cg;
+ u64 frame_amr;
+ u64 cg;
+
+ /* Shifted down by 19 bits compared to spec */
+ frame_amr = le64_to_cpu(ami->master_rank);
+ cg = (u64)ami->master_pref << (64 - 19);
+ cg += le64_to_cpu(mgmt->u.beacon.timestamp) >> 19;
+
+ curr_amr = le64_to_cpu(data->nan.current_ami.master_rank);
+ own_cg = (u64)data->nan.current_ami.master_pref << (64 - 19);
+ own_cg += rx_status.mactime >> 19;
+
+ /*
+ * Check if the cluster shall be joined
+ *
+ * When in the "scan" phase, just join immediately.
+ */
+ if (cg > own_cg ||
+ (cg == own_cg && frame_amr > curr_amr) ||
+ data->nan.phase == MAC80211_HWSIM_NAN_PHASE_SCAN) {
+ /* Avoid a state transition */
+ data->nan.master_transition_score = 0;
+ data->nan.sync_transition_score = 0;
+
+ /*
+ * NOTE: The spec says we should TX sync beacons on the
+ * old schedule after joining. We do not implement this.
+ */
+
+ wiphy_dbg(hw->wiphy, "NAN: joining cluster %pM\n",
+ mgmt->bssid);
+
+ joined_cluster = true;
+ adopt_tsf = true;
+
+ memcpy(&data->nan.last_ami, &data->nan.current_ami,
+ sizeof(data->nan.last_ami));
+ memcpy(&data->nan.current_ami, ami,
+ sizeof(data->nan.last_ami));
+ data->nan.current_ami.hop_count += 1;
+
+ memcpy(data->nan.cluster_id, mgmt->bssid, ETH_ALEN);
+
+ /*
+ * Assume we are UP if we joined a cluster.
+ *
+ * If the other anchor master is still in the warmup
+ * phase, then we may temporarily become the anchor
+ * master until it sets its own master preference to
+ * be non-zero.
+ */
+ data->nan.phase = MAC80211_HWSIM_NAN_PHASE_UP;
+ data->nan.random_factor_valid_dwst = 0;
+ }
+ }
+
+ /*
+ * Anchor master selection
+ */
+ /* We are not anchor master */
+ if (is_same_cluster && is_sync_beacon && ami &&
+ data->nan.current_ami.hop_count != 0) {
+ if (le64_to_cpu(data->nan.current_ami.master_rank) <
+ le64_to_cpu(ami->master_rank)) {
+ if (ami->master_rank == data->nan.last_ami.master_rank &&
+ le32_to_cpu(ami->ambtt) <=
+ le32_to_cpu(data->nan.last_ami.ambtt)) {
+ /* disregard frame */
+ } else {
+ memcpy(&data->nan.last_ami,
+ &data->nan.current_ami,
+ sizeof(data->nan.last_ami));
+ memcpy(&data->nan.current_ami, ami,
+ sizeof(data->nan.last_ami));
+ data->nan.current_ami.hop_count += 1;
+ }
+ }
+
+ if (le64_to_cpu(data->nan.current_ami.master_rank) >
+ le64_to_cpu(ami->master_rank)) {
+ if (!ether_addr_equal(data->nan.current_ami.master_addr,
+ ami->master_addr)) {
+ /* disregard frame */
+ } else {
+ u64 amr = hwsim_nan_get_master_rank(data);
+
+ if (amr > le64_to_cpu(ami->master_rank)) {
+ /* assume ourselves as anchor master */
+ wiphy_dbg(hw->wiphy,
+ "NAN: assume anchor master role\n");
+ data->nan.current_ami.master_rank =
+ cpu_to_le64(amr);
+ data->nan.current_ami.hop_count = 0;
+ memset(&data->nan.last_ami, 0,
+ sizeof(data->nan.last_ami));
+ data->nan.last_ami.ambtt =
+ data->nan.current_ami.ambtt;
+ data->nan.current_ami.ambtt = 0;
+ } else {
+ memcpy(&data->nan.last_ami,
+ &data->nan.current_ami,
+ sizeof(data->nan.last_ami));
+ memcpy(&data->nan.current_ami, ami,
+ sizeof(data->nan.last_ami));
+ data->nan.current_ami.hop_count += 1;
+ }
+ }
+ }
+
+ if (data->nan.current_ami.master_rank == ami->master_rank) {
+ if (le32_to_cpu(data->nan.current_ami.ambtt) <
+ le32_to_cpu(ami->ambtt)) {
+ data->nan.current_ami.ambtt = ami->ambtt;
+ }
+
+ if (data->nan.current_ami.hop_count >
+ ami->hop_count + 1) {
+ data->nan.current_ami.hop_count =
+ ami->hop_count + 1;
+ }
+ }
+ }
+
+ /* We are anchor master */
+ if (is_same_cluster && is_sync_beacon && ami &&
+ data->nan.current_ami.hop_count == 0) {
+ WARN_ON_ONCE(!ether_addr_equal(data->nan.current_ami.master_addr,
+ data->nan.device_vif->addr));
+
+ if (le64_to_cpu(ami->master_rank) <
+ le64_to_cpu(data->nan.current_ami.master_rank) ||
+ ether_addr_equal(ami->master_addr,
+ data->nan.current_ami.master_addr)) {
+ /* disregard */
+ } else {
+ wiphy_dbg(hw->wiphy, "NAN: lost anchor master role\n");
+ adopt_tsf = true;
+ memcpy(&data->nan.last_ami, &data->nan.current_ami,
+ sizeof(data->nan.last_ami));
+ memcpy(&data->nan.current_ami, ami,
+ sizeof(data->nan.last_ami));
+ data->nan.current_ami.hop_count += 1;
+ }
+ }
+
+ if (adopt_tsf && !data->nan.tsf_adjusted) {
+ int threshold = 5;
+ s64 adjustment;
+
+ /* Timestamp is likely inaccurate (and late) in this case */
+ if (!(IEEE80211_SKB_RXCB(skb)->flag & RX_FLAG_MACTIME))
+ threshold = 128;
+
+ adjustment =
+ le64_to_cpu(mgmt->u.beacon.timestamp) -
+ ieee80211_calculate_rx_timestamp(hw, &rx_status,
+ skb->len, 24);
+
+ scoped_guard(spinlock_bh, &data->tsf_offset_lock) {
+ if (adjustment < -threshold || adjustment > threshold) {
+ if (adjustment < -(s64)ieee80211_tu_to_usec(4) ||
+ adjustment > (s64)ieee80211_tu_to_usec(4))
+ data->nan.tsf_discontinuity = true;
+
+ wiphy_debug(hw->wiphy,
+ "NAN: Adjusting TSF by +/- %d us or more: %lld us (discontinuity: %d, from: %pM, old offset: %lld)\n",
+ threshold, adjustment,
+ data->nan.tsf_discontinuity, mgmt->sa,
+ data->tsf_offset);
+ } else {
+ /* smooth things out a little bit */
+ adjustment /= 2;
+ }
+
+ /*
+ * Do the TSF adjustment
+ * The flag prevents further adjustments until the next
+ * 2.4 GHz DW starts to avoid race conditions for
+ * in-flight packets.
+ */
+ data->nan.tsf_adjusted = true;
+ data->tsf_offset += adjustment;
+ }
+ }
+
+ spin_unlock_bh(&data->nan.state_lock);
+
+ if (joined_cluster)
+ ieee80211_nan_cluster_joined(data->nan.device_vif,
+ data->nan.cluster_id, false,
+ GFP_ATOMIC);
+}
+
static void
mac80211_hwsim_nan_exec_state_transitions(struct mac80211_hwsim_data *data)
{
+ bool notify_join = false;
+
/*
* Handle NAN role and state transitions at the end of the DW period
* in accordance to Wi-Fi Aware version 4.0 section 3.3.7 point 2, i.e.
* end of 5 GHz DW if enabled else at the end of the 2.4 GHz DW.
- *
- * TODO: Implement
*/
+
+ spin_lock(&data->nan.state_lock);
+
+ /* Handle role transitions, Wi-Fi Aware version 4.0 section 3.3.6 */
+ if (data->nan.master_transition_score < 3)
+ data->nan.role = MAC80211_HWSIM_NAN_ROLE_MASTER;
+ else if (data->nan.role == MAC80211_HWSIM_NAN_ROLE_MASTER &&
+ data->nan.master_transition_score >= 3)
+ data->nan.role = MAC80211_HWSIM_NAN_ROLE_SYNC;
+ else if (data->nan.role == MAC80211_HWSIM_NAN_ROLE_SYNC &&
+ data->nan.sync_transition_score >= 3)
+ data->nan.role = MAC80211_HWSIM_NAN_ROLE_NON_SYNC;
+ else if (data->nan.role == MAC80211_HWSIM_NAN_ROLE_NON_SYNC &&
+ data->nan.sync_transition_score < 3)
+ data->nan.role = MAC80211_HWSIM_NAN_ROLE_SYNC;
+
+ /*
+ * The discovery beacon timer will stop automatically. Make sure it is
+ * running if we are master. Do not bother with a proper alignment it
+ * will sync itself to the TSF after the first TX.
+ */
+ if (data->nan.role == MAC80211_HWSIM_NAN_ROLE_MASTER &&
+ !hrtimer_active(&data->nan.discovery_beacon_timer))
+ hrtimer_start(&data->nan.discovery_beacon_timer,
+ ns_to_ktime(10 * NSEC_PER_USEC),
+ HRTIMER_MODE_REL_SOFT);
+
+ data->nan.master_transition_score = 0;
+ data->nan.sync_transition_score = 0;
+
+ if (data->nan.random_factor_valid_dwst == 0) {
+ u64 amr;
+
+ if (data->nan.phase == MAC80211_HWSIM_NAN_PHASE_SCAN) {
+ data->nan.phase = MAC80211_HWSIM_NAN_PHASE_WARMUP;
+ data->nan.random_factor_valid_dwst = NAN_WARMUP_DWST;
+
+ notify_join = true;
+ } else {
+ data->nan.phase = MAC80211_HWSIM_NAN_PHASE_UP;
+ data->nan.random_factor_valid_dwst =
+ get_random_u32_inclusive(120, 240);
+ data->nan.random_factor = get_random_u8();
+ }
+
+ amr = hwsim_nan_get_master_rank(data);
+
+ if (data->nan.current_ami.hop_count == 0) {
+ /* Update if we are already anchor master */
+ data->nan.current_ami.master_rank = cpu_to_le64(amr);
+ } else if (le64_to_cpu(data->nan.current_ami.master_rank) < amr) {
+ /* assume role if we have a higher rank */
+ wiphy_dbg(data->hw->wiphy,
+ "NAN: assume anchor master role\n");
+ data->nan.current_ami.master_rank = cpu_to_le64(amr);
+ data->nan.current_ami.hop_count = 0;
+ memset(&data->nan.last_ami, 0,
+ sizeof(data->nan.last_ami));
+ data->nan.last_ami.ambtt = data->nan.current_ami.ambtt;
+ data->nan.current_ami.ambtt = 0;
+ }
+ } else {
+ data->nan.random_factor_valid_dwst--;
+ }
+
+ spin_unlock(&data->nan.state_lock);
+
+ if (notify_join)
+ ieee80211_nan_cluster_joined(data->nan.device_vif,
+ data->nan.cluster_id, true,
+ GFP_ATOMIC);
+}
+
+static void
+mac80211_hwsim_nan_tx_beacon(struct mac80211_hwsim_data *data,
+ bool is_discovery,
+ struct ieee80211_channel *channel)
+{
+ struct ieee80211_vendor_ie nan_ie = {
+ .element_id = WLAN_EID_VENDOR_SPECIFIC,
+ .len = 27 - 2,
+ .oui = { u32_get_bits(WLAN_OUI_WFA, 0xff0000),
+ u32_get_bits(WLAN_OUI_WFA, 0xff00),
+ u32_get_bits(WLAN_OUI_WFA, 0xff) },
+ .oui_type = WLAN_OUI_TYPE_WFA_NAN,
+ };
+ size_t alloc_size =
+ IEEE80211_TX_STATUS_HEADROOM +
+ offsetofend(struct ieee80211_mgmt, u.beacon) +
+ 27 /* size of NAN vendor element */;
+ struct ieee80211_nan_master_indication master_indication;
+ struct ieee80211_nan_attr nan_attr;
+ struct ieee80211_mgmt *mgmt;
+ struct sk_buff *skb;
+
+ /*
+ * TODO: Should the configured vendor elements or NAN attributes be
+ * included in some of these beacons?
+ */
+
+ skb = alloc_skb(alloc_size, GFP_ATOMIC);
+ if (!skb)
+ return;
+
+ spin_lock(&data->nan.state_lock);
+
+ skb_reserve(skb, IEEE80211_TX_STATUS_HEADROOM);
+ mgmt = skb_put(skb, offsetofend(struct ieee80211_mgmt, u.beacon));
+
+ memset(mgmt, 0, offsetofend(struct ieee80211_mgmt, u.beacon));
+ memcpy(mgmt->sa, data->nan.device_vif->addr, ETH_ALEN);
+ memset(mgmt->da, 0xff, ETH_ALEN);
+ memcpy(mgmt->bssid, data->nan.cluster_id, ETH_ALEN);
+
+ mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
+ IEEE80211_STYPE_BEACON);
+ mgmt->u.beacon.beacon_int = cpu_to_le16(is_discovery ? 100 : DWST_TU);
+ mgmt->u.beacon.capab_info =
+ cpu_to_le16(WLAN_CAPABILITY_SHORT_SLOT_TIME |
+ WLAN_CAPABILITY_SHORT_PREAMBLE);
+
+ /* FIXME: set these to saner values? */
+ mgmt->duration = 0;
+ mgmt->seq_ctrl = 0;
+
+ /* Put the NAN element */
+ skb_put_data(skb, &nan_ie, sizeof(nan_ie));
+
+ nan_attr.attr = NAN_ATTR_MASTER_INDICATION;
+ nan_attr.length = cpu_to_le16(sizeof(master_indication));
+ if (data->nan.phase == MAC80211_HWSIM_NAN_PHASE_UP) {
+ master_indication.master_pref = data->nan.master_pref;
+ master_indication.random_factor = data->nan.random_factor;
+ } else {
+ master_indication.master_pref = 0;
+ master_indication.random_factor = 0;
+ }
+
+ skb_put_data(skb, &nan_attr, sizeof(nan_attr));
+ skb_put_data(skb, &master_indication, sizeof(master_indication));
+
+ nan_attr.attr = NAN_ATTR_CLUSTER_INFO;
+ nan_attr.length = cpu_to_le16(sizeof(data->nan.current_ami));
+ skb_put_data(skb, &nan_attr, sizeof(nan_attr));
+ skb_put_data(skb, &data->nan.current_ami,
+ sizeof(data->nan.current_ami));
+
+ spin_unlock(&data->nan.state_lock);
+
+ mac80211_hwsim_tx_frame(data->hw, skb, channel);
}
enum hrtimer_restart
@@ -80,10 +663,12 @@ mac80211_hwsim_nan_slot_timer(struct hrtimer *timer)
nan.slot_timer);
struct ieee80211_hw *hw = data->hw;
struct ieee80211_channel *notify_dw_chan = NULL;
+ struct ieee80211_channel *beacon_sync_chan = NULL;
u64 tsf = hwsim_nan_get_timer_tsf(data);
u8 slot = hwsim_nan_slot_from_tsf(tsf);
bool dwst_of_dw0 = false;
bool dw_end = false;
+ bool tx_sync_beacon;
if (!data->nan.device_vif)
return HRTIMER_NORESTART;
@@ -92,10 +677,28 @@ mac80211_hwsim_nan_slot_timer(struct hrtimer *timer)
dwst_of_dw0 = true;
+ scoped_guard(spinlock, &data->nan.state_lock) {
+ if (data->nan.tsf_discontinuity) {
+ data->nan.tsf_discontinuity = false;
+
+ mac80211_hwsim_nan_schedule_slot(data, 32, true);
+
+ return HRTIMER_RESTART;
+ }
+
+ if (slot == SLOT_24GHZ_DW)
+ data->nan.tsf_adjusted = false;
+
+ tx_sync_beacon =
+ data->nan.phase != MAC80211_HWSIM_NAN_PHASE_SCAN &&
+ data->nan.role != MAC80211_HWSIM_NAN_ROLE_NON_SYNC;
+ }
+
switch (slot) {
case SLOT_24GHZ_DW:
wiphy_dbg(data->hw->wiphy, "Start of 2.4 GHz DW, is DW0=%d\n",
dwst_of_dw0);
+ beacon_sync_chan = ieee80211_get_channel(hw->wiphy, 2437);
break;
case SLOT_24GHZ_DW + 1:
@@ -110,6 +713,8 @@ mac80211_hwsim_nan_slot_timer(struct hrtimer *timer)
case SLOT_5GHZ_DW:
if (data->nan.bands & BIT(NL80211_BAND_5GHZ)) {
wiphy_dbg(data->hw->wiphy, "Start of 5 GHz DW\n");
+ beacon_sync_chan =
+ ieee80211_get_channel(hw->wiphy, 5745);
}
break;
@@ -122,6 +727,10 @@ mac80211_hwsim_nan_slot_timer(struct hrtimer *timer)
break;
}
+ /* TODO: This does not implement DW contention mitigation */
+ if (beacon_sync_chan && tx_sync_beacon)
+ mac80211_hwsim_nan_tx_beacon(data, false, beacon_sync_chan);
+
if (dw_end)
mac80211_hwsim_nan_exec_state_transitions(data);
@@ -134,7 +743,50 @@ mac80211_hwsim_nan_slot_timer(struct hrtimer *timer)
mac80211_hwsim_nan_resume_txqs(data);
- mac80211_hwsim_nan_schedule_slot(data, slot + 1);
+ mac80211_hwsim_nan_schedule_slot(data, slot + 1, false);
+
+ return HRTIMER_RESTART;
+}
+
+enum hrtimer_restart
+mac80211_hwsim_nan_discovery_beacon_timer(struct hrtimer *timer)
+{
+ struct mac80211_hwsim_data *data =
+ container_of(timer, struct mac80211_hwsim_data,
+ nan.discovery_beacon_timer);
+ u32 remainder;
+ u64 tsf_now;
+ u64 tbtt;
+
+ if (!data->nan.device_vif)
+ return HRTIMER_NORESTART;
+
+ scoped_guard(spinlock, &data->nan.state_lock) {
+ if (data->nan.phase == MAC80211_HWSIM_NAN_PHASE_SCAN ||
+ data->nan.role != MAC80211_HWSIM_NAN_ROLE_MASTER)
+ return HRTIMER_NORESTART;
+ }
+
+ mac80211_hwsim_nan_tx_beacon(
+ data, true, ieee80211_get_channel(data->hw->wiphy, 2437));
+
+ if (data->nan.bands & BIT(NL80211_BAND_5GHZ))
+ mac80211_hwsim_nan_tx_beacon(
+ data, true,
+ ieee80211_get_channel(data->hw->wiphy, 5745));
+
+ /* Read the TSF from the current time in case of adjustments */
+ tsf_now = mac80211_hwsim_get_tsf(data->hw, data->nan.device_vif);
+
+ /* Wrap value to be after the next TBTT */
+ tbtt = tsf_now + ieee80211_tu_to_usec(100);
+
+ /* Round TBTT down to the correct time */
+ div_u64_rem(tbtt, ieee80211_tu_to_usec(100), &remainder);
+ tbtt = tbtt - remainder;
+
+ hrtimer_set_expires(&data->nan.discovery_beacon_timer,
+ mac80211_hwsim_tsf_to_boottime(data, tbtt));
return HRTIMER_RESTART;
}
@@ -144,7 +796,6 @@ int mac80211_hwsim_nan_start(struct ieee80211_hw *hw,
struct cfg80211_nan_conf *conf)
{
struct mac80211_hwsim_data *data = hw->priv;
- struct wireless_dev *wdev = ieee80211_vif_to_wdev(vif);
if (vif->type != NL80211_IFTYPE_NAN)
return -EINVAL;
@@ -156,28 +807,29 @@ int mac80211_hwsim_nan_start(struct ieee80211_hw *hw,
data->nan.device_vif = vif;
data->nan.bands = conf->bands;
+ scoped_guard(spinlock_bh, &data->nan.state_lock) {
+ /* Start in the "scan" phase and stay there for a little bit */
+ data->nan.phase = MAC80211_HWSIM_NAN_PHASE_SCAN;
+ data->nan.random_factor_valid_dwst = 1;
+ data->nan.random_factor = 0;
+ data->nan.master_pref = conf->master_pref;
+ data->nan.role = MAC80211_HWSIM_NAN_ROLE_MASTER;
+ memset(&data->nan.current_ami, 0,
+ sizeof(data->nan.current_ami));
+ memset(&data->nan.last_ami, 0, sizeof(data->nan.last_ami));
+ data->nan.current_ami.master_rank =
+ cpu_to_le64(hwsim_nan_get_master_rank(data));
+ }
+
/* Just run this "soon" and start in a random schedule position */
hrtimer_start(&data->nan.slot_timer,
ns_to_ktime(10 * NSEC_PER_USEC),
HRTIMER_MODE_REL_SOFT);
- if (!is_zero_ether_addr(conf->cluster_id) &&
- is_zero_ether_addr(hwsim_nan_cluster_id)) {
- memcpy(hwsim_nan_cluster_id, conf->cluster_id, ETH_ALEN);
- } else if (is_zero_ether_addr(hwsim_nan_cluster_id)) {
- hwsim_nan_cluster_id[0] = 0x50;
- hwsim_nan_cluster_id[1] = 0x6f;
- hwsim_nan_cluster_id[2] = 0x9a;
- hwsim_nan_cluster_id[3] = 0x01;
- hwsim_nan_cluster_id[4] = get_random_u8();
- hwsim_nan_cluster_id[5] = get_random_u8();
- }
+ ether_addr_copy(data->nan.cluster_id, conf->cluster_id);
data->nan.notify_dw = conf->enable_dw_notification;
- cfg80211_nan_cluster_joined(wdev, hwsim_nan_cluster_id, true,
- GFP_KERNEL);
-
return 0;
}
@@ -185,8 +837,6 @@ int mac80211_hwsim_nan_stop(struct ieee80211_hw *hw,
struct ieee80211_vif *vif)
{
struct mac80211_hwsim_data *data = hw->priv;
- struct mac80211_hwsim_data *data2;
- bool nan_cluster_running = false;
if (vif->type != NL80211_IFTYPE_NAN || !data->nan.device_vif ||
data->nan.device_vif != vif)
@@ -194,20 +844,9 @@ int mac80211_hwsim_nan_stop(struct ieee80211_hw *hw,
hrtimer_cancel(&data->nan.slot_timer);
hrtimer_cancel(&data->nan.resume_txqs_timer);
+ hrtimer_cancel(&data->nan.discovery_beacon_timer);
data->nan.device_vif = NULL;
- spin_lock_bh(&hwsim_radio_lock);
- list_for_each_entry(data2, &hwsim_radios, list) {
- if (data2->nan.device_vif) {
- nan_cluster_running = true;
- break;
- }
- }
- spin_unlock_bh(&hwsim_radio_lock);
-
- if (!nan_cluster_running)
- memset(hwsim_nan_cluster_id, 0, ETH_ALEN);
-
return 0;
}
@@ -233,6 +872,11 @@ int mac80211_hwsim_nan_change_config(struct ieee80211_hw *hw,
if (changes & CFG80211_NAN_CONF_CHANGED_CONFIG)
data->nan.notify_dw = conf->enable_dw_notification;
+ if (changes & CFG80211_NAN_CONF_CHANGED_PREF) {
+ scoped_guard(spinlock_bh, &data->nan.state_lock)
+ data->nan.master_pref = conf->master_pref;
+ }
+
return 0;
}
@@ -335,7 +979,10 @@ bool mac80211_hwsim_nan_receive(struct ieee80211_hw *hw,
if (WARN_ON_ONCE(!data->nan.device_vif))
return false;
- if (rx_status->rx_flags & RX_FLAG_MACTIME) {
+ if (data->nan.phase == MAC80211_HWSIM_NAN_PHASE_SCAN)
+ return channel->center_freq == 2437;
+
+ if (rx_status->flag & RX_FLAG_MACTIME) {
slot = hwsim_nan_slot_from_tsf(rx_status->mactime);
} else {
u64 tsf;
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_nan.h b/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
index af8dd7ff00cc..3199e5c5376b 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
@@ -7,6 +7,18 @@
#ifndef __MAC80211_HWSIM_NAN_H
#define __MAC80211_HWSIM_NAN_H
+enum mac80211_hwsim_nan_phase {
+ MAC80211_HWSIM_NAN_PHASE_SCAN,
+ MAC80211_HWSIM_NAN_PHASE_WARMUP,
+ MAC80211_HWSIM_NAN_PHASE_UP,
+};
+
+enum mac80211_hwsim_nan_role {
+ MAC80211_HWSIM_NAN_ROLE_MASTER,
+ MAC80211_HWSIM_NAN_ROLE_SYNC,
+ MAC80211_HWSIM_NAN_ROLE_NON_SYNC,
+};
+
struct mac80211_hwsim_nan_data {
struct ieee80211_vif *device_vif;
u8 bands;
@@ -14,12 +26,40 @@ struct mac80211_hwsim_nan_data {
struct hrtimer slot_timer;
struct hrtimer resume_txqs_timer;
bool notify_dw;
+
+ struct hrtimer discovery_beacon_timer;
+
+ /* Later members are protected by this lock */
+ spinlock_t state_lock;
+
+ u8 master_pref;
+ u8 random_factor;
+
+ u8 random_factor_valid_dwst;
+
+ enum mac80211_hwsim_nan_phase phase;
+ enum mac80211_hwsim_nan_role role;
+
+ u8 cluster_id[ETH_ALEN];
+
+ struct ieee80211_nan_anchor_master_info current_ami;
+ struct ieee80211_nan_anchor_master_info last_ami;
+
+ /* Wi-Fi Aware version 4.0, section 3.3.6.1 and 3.3.6.2 */
+ int master_transition_score;
+ /* Wi-Fi Aware version 4.0, section 3.3.6.3 and 3.3.6.4 */
+ int sync_transition_score;
+
+ bool tsf_adjusted;
+ bool tsf_discontinuity;
};
enum hrtimer_restart
mac80211_hwsim_nan_slot_timer(struct hrtimer *timer);
enum hrtimer_restart
mac80211_hwsim_nan_resume_txqs_timer(struct hrtimer *timer);
+enum hrtimer_restart
+mac80211_hwsim_nan_discovery_beacon_timer(struct hrtimer *timer);
int mac80211_hwsim_nan_start(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
@@ -43,4 +83,7 @@ bool mac80211_hwsim_nan_receive(struct ieee80211_hw *hw,
struct ieee80211_channel *channel,
struct ieee80211_rx_status *rx_status);
+void mac80211_hwsim_nan_rx(struct ieee80211_hw *hw,
+ struct sk_buff *skb);
+
#endif /* __MAC80211_HWSIM_NAN_H */
--
2.34.1
^ permalink raw reply related
* [PATCH wireless-next 04/14] wifi: mac80211_hwsim: protect tsf_offset using a spinlock
From: Miri Korenblit @ 2026-05-05 16:42 UTC (permalink / raw)
To: linux-wireless; +Cc: Benjamin Berg
In-Reply-To: <20260505164219.2806117-1-miriam.rachel.korenblit@intel.com>
From: Benjamin Berg <benjamin.berg@intel.com>
To implement NAN synchronization in hwsim, the TSF needs to be adjusted
regularly from the RX path. Add a spinlock so that this can be done in a
safe manner.
Signed-off-by: Benjamin Berg <benjamin.berg@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
.../net/wireless/virtual/mac80211_hwsim_i.h | 1 +
.../wireless/virtual/mac80211_hwsim_main.c | 34 ++++++++++++++-----
2 files changed, 26 insertions(+), 9 deletions(-)
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_i.h b/drivers/net/wireless/virtual/mac80211_hwsim_i.h
index 5432de92beab..7a6495ea79e7 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_i.h
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_i.h
@@ -102,6 +102,7 @@ struct mac80211_hwsim_data {
u32 wmediumd;
/* difference between this hw's clock and the real clock, in usecs */
+ spinlock_t tsf_offset_lock;
s64 tsf_offset;
/* Stats */
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index 76f1028bc42e..2de44c5fb1ff 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -1256,13 +1256,17 @@ static inline u64 mac80211_hwsim_get_sim_tsf(void)
ktime_t mac80211_hwsim_tsf_to_boottime(struct mac80211_hwsim_data *data,
u64 tsf)
{
- return us_to_ktime(tsf - data->tsf_offset);
+ scoped_guard(spinlock_bh, &data->tsf_offset_lock) {
+ return us_to_ktime(tsf - data->tsf_offset);
+ }
}
u64 mac80211_hwsim_boottime_to_tsf(struct mac80211_hwsim_data *data,
ktime_t ts)
{
- return ktime_to_us(ts + data->tsf_offset);
+ scoped_guard(spinlock_bh, &data->tsf_offset_lock) {
+ return ktime_to_us(ts) + data->tsf_offset;
+ }
}
u64 mac80211_hwsim_get_tsf(struct ieee80211_hw *hw,
@@ -1271,14 +1275,18 @@ u64 mac80211_hwsim_get_tsf(struct ieee80211_hw *hw,
struct mac80211_hwsim_data *data = hw->priv;
u64 sim_time = mac80211_hwsim_get_sim_tsf();
- return sim_time + data->tsf_offset;
+ scoped_guard(spinlock_bh, &data->tsf_offset_lock) {
+ return sim_time + data->tsf_offset;
+ }
}
static __le64 __mac80211_hwsim_get_tsf(struct mac80211_hwsim_data *data)
{
u64 sim_time = mac80211_hwsim_get_sim_tsf();
- return cpu_to_le64(sim_time + data->tsf_offset);
+ scoped_guard(spinlock_bh, &data->tsf_offset_lock) {
+ return cpu_to_le64(sim_time + data->tsf_offset);
+ }
}
static void mac80211_hwsim_set_tsf(struct ieee80211_hw *hw,
@@ -1293,11 +1301,13 @@ static void mac80211_hwsim_set_tsf(struct ieee80211_hw *hw,
if (conf && !conf->enable_beacon)
return;
- /* adjust after beaconing with new timestamp at old TBTT */
- if (tsf > now)
- data->tsf_offset += delta;
- else
- data->tsf_offset -= delta;
+ scoped_guard(spinlock_bh, &data->tsf_offset_lock) {
+ /* adjust after beaconing with new timestamp at old TBTT */
+ if (tsf > now)
+ data->tsf_offset += delta;
+ else
+ data->tsf_offset -= delta;
+ }
}
static void mac80211_hwsim_monitor_rx(struct ieee80211_hw *hw,
@@ -1577,6 +1587,8 @@ static void mac80211_hwsim_write_tsf(struct mac80211_hwsim_data *data,
/* TODO: get MCS */
int bitrate = 100;
+ spin_lock_bh(&data->tsf_offset_lock);
+
txrate = ieee80211_get_tx_rate(data->hw, info);
if (txrate)
bitrate = txrate->bitrate;
@@ -1602,6 +1614,8 @@ static void mac80211_hwsim_write_tsf(struct mac80211_hwsim_data *data,
10 * 8 * 10 /
bitrate);
}
+
+ spin_unlock_bh(&data->tsf_offset_lock);
}
static void mac80211_hwsim_tx_frame_nl(struct ieee80211_hw *hw,
@@ -5664,6 +5678,8 @@ static int mac80211_hwsim_new_radio(struct genl_info *info,
hw->wiphy->mbssid_max_interfaces = 8;
hw->wiphy->ema_max_profile_periodicity = 3;
+ spin_lock_init(&data->tsf_offset_lock);
+
data->rx_rssi = DEFAULT_RX_RSSI;
INIT_DELAYED_WORK(&data->roc_start, hw_roc_start);
--
2.34.1
^ permalink raw reply related
* [PATCH wireless-next 03/14] wifi: mac80211_hwsim: only RX on NAN when active on a slot
From: Miri Korenblit @ 2026-05-05 16:42 UTC (permalink / raw)
To: linux-wireless; +Cc: Benjamin Berg
In-Reply-To: <20260505164219.2806117-1-miriam.rachel.korenblit@intel.com>
From: Benjamin Berg <benjamin.berg@intel.com>
This moves the NAN receive into the main code and changes it so that
frame RX only happens when the device is active on the channel. This
limits RX to the DW slots as there is currently no datapath.
With this the globally stored channel is obsolete, remove it.
Signed-off-by: Benjamin Berg <benjamin.berg@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
.../wireless/virtual/mac80211_hwsim_main.c | 26 ++++++++----
.../net/wireless/virtual/mac80211_hwsim_nan.c | 41 +++++++++++++++++--
.../net/wireless/virtual/mac80211_hwsim_nan.h | 7 ++--
3 files changed, 58 insertions(+), 16 deletions(-)
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index f0f6cb7fa894..76f1028bc42e 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -1731,6 +1731,8 @@ static bool hwsim_chans_compat(struct ieee80211_channel *c1,
struct tx_iter_data {
struct ieee80211_channel *channel;
+ struct ieee80211_rx_status *rx_status;
+ struct ieee80211_hw *hw;
bool receive;
};
@@ -1740,13 +1742,10 @@ static void mac80211_hwsim_tx_iter(void *_data, u8 *addr,
struct tx_iter_data *data = _data;
int i;
- /* For NAN Device simulation purposes, assume that NAN is always
- * on channel 6 or channel 149.
- */
if (vif->type == NL80211_IFTYPE_NAN) {
- data->receive = (data->channel &&
- (data->channel->center_freq == 2437 ||
- data->channel->center_freq == 5745));
+ data->receive = mac80211_hwsim_nan_receive(data->hw,
+ data->channel,
+ data->rx_status);
return;
}
@@ -1923,7 +1922,9 @@ static bool mac80211_hwsim_tx_frame_no_nl(struct ieee80211_hw *hw,
struct sk_buff *nskb;
struct tx_iter_data tx_iter_data = {
.receive = false,
+ .hw = data2->hw,
.channel = chan,
+ .rx_status = &rx_status,
};
if (data == data2)
@@ -1939,6 +1940,12 @@ static bool mac80211_hwsim_tx_frame_no_nl(struct ieee80211_hw *hw,
if (data->netgroup != data2->netgroup)
continue;
+ /*
+ * Set mactime early since NAN RX filtering relies on it
+ * for slot calculation
+ */
+ rx_status.mactime = sim_tsf + data2->tsf_offset;
+
if (!hwsim_chans_compat(chan, data2->tmp_chan) &&
!hwsim_chans_compat(chan, data2->channel)) {
ieee80211_iterate_active_interfaces_atomic(
@@ -1975,8 +1982,6 @@ static bool mac80211_hwsim_tx_frame_no_nl(struct ieee80211_hw *hw,
if (mac80211_hwsim_addr_match(data2, hdr->addr1))
ack = true;
- rx_status.mactime = sim_tsf + data2->tsf_offset;
-
mac80211_hwsim_rx(data2, &rx_status, nskb);
}
spin_unlock(&hwsim_radio_lock);
@@ -6286,7 +6291,10 @@ static int hwsim_cloned_frame_received_nl(struct sk_buff *skb_2,
/* A frame is received from user space */
memset(&rx_status, 0, sizeof(rx_status));
if (info->attrs[HWSIM_ATTR_FREQ]) {
- struct tx_iter_data iter_data = {};
+ struct tx_iter_data iter_data = {
+ .hw = data2->hw,
+ .rx_status = &rx_status,
+ };
/* throw away off-channel packets, but allow both the temporary
* ("hw" scan/remain-on-channel), regular channels and links,
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_nan.c b/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
index 10bbac9a4b55..805848172605 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
@@ -96,7 +96,6 @@ mac80211_hwsim_nan_slot_timer(struct hrtimer *timer)
case SLOT_24GHZ_DW:
wiphy_dbg(data->hw->wiphy, "Start of 2.4 GHz DW, is DW0=%d\n",
dwst_of_dw0);
- data->nan.channel = ieee80211_get_channel(hw->wiphy, 2437);
break;
case SLOT_24GHZ_DW + 1:
@@ -111,8 +110,6 @@ mac80211_hwsim_nan_slot_timer(struct hrtimer *timer)
case SLOT_5GHZ_DW:
if (data->nan.bands & BIT(NL80211_BAND_5GHZ)) {
wiphy_dbg(data->hw->wiphy, "Start of 5 GHz DW\n");
- data->nan.channel =
- ieee80211_get_channel(hw->wiphy, 5745);
}
break;
@@ -158,7 +155,6 @@ int mac80211_hwsim_nan_start(struct ieee80211_hw *hw,
/* set this before starting the timer, as preemption might occur */
data->nan.device_vif = vif;
data->nan.bands = conf->bands;
- data->nan.channel = ieee80211_get_channel(hw->wiphy, 2437);
/* Just run this "soon" and start in a random schedule position */
hrtimer_start(&data->nan.slot_timer,
@@ -328,3 +324,40 @@ mac80211_hwsim_nan_get_tx_channel(struct ieee80211_hw *hw)
/* drop frame and warn, NAN_CHAN_SWITCH_TIME_US should avoid races */
return NULL;
}
+
+bool mac80211_hwsim_nan_receive(struct ieee80211_hw *hw,
+ struct ieee80211_channel *channel,
+ struct ieee80211_rx_status *rx_status)
+{
+ struct mac80211_hwsim_data *data = hw->priv;
+ u8 slot;
+
+ if (WARN_ON_ONCE(!data->nan.device_vif))
+ return false;
+
+ if (rx_status->rx_flags & RX_FLAG_MACTIME) {
+ slot = hwsim_nan_slot_from_tsf(rx_status->mactime);
+ } else {
+ u64 tsf;
+
+ /*
+ * This is not perfect, but that should be fine.
+ *
+ * Assume the frame might be a bit early in relation to our
+ * own TSF. This is largely because the TSF sync is going to be
+ * pretty bad when the frame was RXed via NL and the beacon as
+ * well as RX timestamps are not accurate.
+ */
+ tsf = mac80211_hwsim_get_tsf(data->hw, data->nan.device_vif);
+ slot = hwsim_nan_slot_from_tsf(tsf + 128);
+ }
+
+ if (slot == SLOT_24GHZ_DW && channel->center_freq == 2437)
+ return true;
+
+ if (slot == SLOT_5GHZ_DW && data->nan.bands & BIT(NL80211_BAND_5GHZ) &&
+ channel->center_freq == 5745)
+ return true;
+
+ return false;
+}
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_nan.h b/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
index 796cc17d194e..af8dd7ff00cc 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
@@ -11,9 +11,6 @@ struct mac80211_hwsim_nan_data {
struct ieee80211_vif *device_vif;
u8 bands;
- /* Current channel of the NAN device */
- struct ieee80211_channel *channel;
-
struct hrtimer slot_timer;
struct hrtimer resume_txqs_timer;
bool notify_dw;
@@ -42,4 +39,8 @@ bool mac80211_hwsim_nan_txq_transmitting(struct ieee80211_hw *hw,
struct ieee80211_channel *
mac80211_hwsim_nan_get_tx_channel(struct ieee80211_hw *hw);
+bool mac80211_hwsim_nan_receive(struct ieee80211_hw *hw,
+ struct ieee80211_channel *channel,
+ struct ieee80211_rx_status *rx_status);
+
#endif /* __MAC80211_HWSIM_NAN_H */
--
2.34.1
^ permalink raw reply related
* [PATCH wireless-next 02/14] wifi: mac80211_hwsim: select NAN TX channel based on current TSF
From: Miri Korenblit @ 2026-05-05 16:42 UTC (permalink / raw)
To: linux-wireless; +Cc: Benjamin Berg
In-Reply-To: <20260505164219.2806117-1-miriam.rachel.korenblit@intel.com>
From: Benjamin Berg <benjamin.berg@intel.com>
Move the TX channel selection into the NAN specific file and select the
channel based on the current slot.
Signed-off-by: Benjamin Berg <benjamin.berg@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
.../net/wireless/virtual/mac80211_hwsim_main.c | 6 +-----
.../net/wireless/virtual/mac80211_hwsim_nan.c | 18 ++++++++++++++++++
.../net/wireless/virtual/mac80211_hwsim_nan.h | 3 +++
3 files changed, 22 insertions(+), 5 deletions(-)
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index 6740504b30e6..f0f6cb7fa894 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -2084,11 +2084,7 @@ static void mac80211_hwsim_tx(struct ieee80211_hw *hw,
hdr = (void *)skb->data;
if (vif && vif->type == NL80211_IFTYPE_NAN && !data->tmp_chan) {
- /* For NAN Device simulation purposes, assume that NAN is always
- * on channel 6 or channel 149, unless a ROC is in progress (for
- * USD use cases).
- */
- channel = data->nan.channel;
+ channel = mac80211_hwsim_nan_get_tx_channel(hw);
if (WARN_ON(!channel)) {
ieee80211_free_txskb(hw, skb);
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_nan.c b/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
index 22805c3723e6..10bbac9a4b55 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
@@ -310,3 +310,21 @@ bool mac80211_hwsim_nan_txq_transmitting(struct ieee80211_hw *hw,
return true;
}
+
+struct ieee80211_channel *
+mac80211_hwsim_nan_get_tx_channel(struct ieee80211_hw *hw)
+{
+ struct mac80211_hwsim_data *data = hw->priv;
+ u64 tsf = mac80211_hwsim_get_tsf(data->hw, data->nan.device_vif);
+ u8 slot = hwsim_nan_slot_from_tsf(tsf);
+
+ if (slot == SLOT_24GHZ_DW)
+ return ieee80211_get_channel(hw->wiphy, 2437);
+
+ if (slot == SLOT_5GHZ_DW &&
+ data->nan.bands & BIT(NL80211_BAND_5GHZ))
+ return ieee80211_get_channel(hw->wiphy, 5745);
+
+ /* drop frame and warn, NAN_CHAN_SWITCH_TIME_US should avoid races */
+ return NULL;
+}
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_nan.h b/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
index 6a0780797273..796cc17d194e 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
@@ -39,4 +39,7 @@ int mac80211_hwsim_nan_change_config(struct ieee80211_hw *hw,
bool mac80211_hwsim_nan_txq_transmitting(struct ieee80211_hw *hw,
struct ieee80211_txq *txq);
+struct ieee80211_channel *
+mac80211_hwsim_nan_get_tx_channel(struct ieee80211_hw *hw);
+
#endif /* __MAC80211_HWSIM_NAN_H */
--
2.34.1
^ permalink raw reply related
* [PATCH wireless-next 01/14] wifi: mac80211_hwsim: limit TX of frames to the NAN DW
From: Miri Korenblit @ 2026-05-05 16:42 UTC (permalink / raw)
To: linux-wireless; +Cc: Benjamin Berg
In-Reply-To: <20260505164219.2806117-1-miriam.rachel.korenblit@intel.com>
From: Benjamin Berg <benjamin.berg@intel.com>
Frames submitted on the NAN device interface should only be transmitted
during one of the discovery windows (DWs). It is assumed that software
submits frames from the DW end notifications for the next DW period.
Simulate this behaviour by checking that we are currently in a DW before
transmitting from ieee80211_hwsim_wake_tx_queue. As frames will be
queued up at the start of a DW, wake the management TX queue every time
a DW is started. Do so with a randomized offset just to avoid every
client transmitting at the same time.
Signed-off-by: Benjamin Berg <benjamin.berg@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
.../net/wireless/virtual/mac80211_hwsim_i.h | 3 +
.../wireless/virtual/mac80211_hwsim_main.c | 11 ++-
.../net/wireless/virtual/mac80211_hwsim_nan.c | 79 +++++++++++++++++++
.../net/wireless/virtual/mac80211_hwsim_nan.h | 6 ++
4 files changed, 97 insertions(+), 2 deletions(-)
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_i.h b/drivers/net/wireless/virtual/mac80211_hwsim_i.h
index 6b2a5dccb106..5432de92beab 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_i.h
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_i.h
@@ -136,4 +136,7 @@ u64 mac80211_hwsim_boottime_to_tsf(struct mac80211_hwsim_data *data,
u64 mac80211_hwsim_get_tsf(struct ieee80211_hw *hw,
struct ieee80211_vif *vif);
+void ieee80211_hwsim_wake_tx_queue(struct ieee80211_hw *hw,
+ struct ieee80211_txq *txq);
+
#endif /* __MAC80211_HWSIM_I_H */
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index 3a0c4366dfdb..6740504b30e6 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -2235,14 +2235,18 @@ static void mac80211_hwsim_tx(struct ieee80211_hw *hw,
ieee80211_tx_status_irqsafe(hw, skb);
}
-static void ieee80211_hwsim_wake_tx_queue(struct ieee80211_hw *hw,
- struct ieee80211_txq *txq)
+void ieee80211_hwsim_wake_tx_queue(struct ieee80211_hw *hw,
+ struct ieee80211_txq *txq)
{
struct ieee80211_tx_control control = {
.sta = txq->sta,
};
struct sk_buff *skb;
+ if (txq->vif->type == NL80211_IFTYPE_NAN &&
+ !mac80211_hwsim_nan_txq_transmitting(hw, txq))
+ return;
+
while ((skb = ieee80211_tx_dequeue(hw, txq)))
mac80211_hwsim_tx(hw, &control, skb);
}
@@ -5603,6 +5607,9 @@ static int mac80211_hwsim_new_radio(struct genl_info *info,
hrtimer_setup(&data->nan.slot_timer,
mac80211_hwsim_nan_slot_timer,
CLOCK_BOOTTIME, HRTIMER_MODE_ABS_SOFT);
+ hrtimer_setup(&data->nan.resume_txqs_timer,
+ mac80211_hwsim_nan_resume_txqs_timer,
+ CLOCK_BOOTTIME, HRTIMER_MODE_ABS_SOFT);
}
data->if_combination.radar_detect_widths =
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_nan.c b/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
index aa4aef0920f4..22805c3723e6 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
@@ -26,8 +26,13 @@
static_assert(16 * DWST_TU * 1024 == 8192 * 1024);
static_assert(DW0_TSF_MASK + 1 == 8192 * 1024);
+/* Quiet time at the end of each slot where TX is suppressed */
+#define NAN_CHAN_SWITCH_TIME_US 256
+
static u8 hwsim_nan_cluster_id[ETH_ALEN];
+static void mac80211_hwsim_nan_resume_txqs(struct mac80211_hwsim_data *data);
+
static u64 hwsim_nan_get_timer_tsf(struct mac80211_hwsim_data *data)
{
ktime_t expires = hrtimer_get_expires(&data->nan.slot_timer);
@@ -130,6 +135,8 @@ mac80211_hwsim_nan_slot_timer(struct hrtimer *timer)
cfg80211_next_nan_dw_notif(wdev, notify_dw_chan, GFP_ATOMIC);
}
+ mac80211_hwsim_nan_resume_txqs(data);
+
mac80211_hwsim_nan_schedule_slot(data, slot + 1);
return HRTIMER_RESTART;
@@ -190,6 +197,7 @@ int mac80211_hwsim_nan_stop(struct ieee80211_hw *hw,
return -EINVAL;
hrtimer_cancel(&data->nan.slot_timer);
+ hrtimer_cancel(&data->nan.resume_txqs_timer);
data->nan.device_vif = NULL;
spin_lock_bh(&hwsim_radio_lock);
@@ -231,3 +239,74 @@ int mac80211_hwsim_nan_change_config(struct ieee80211_hw *hw,
return 0;
}
+
+static void mac80211_hwsim_nan_resume_txqs(struct mac80211_hwsim_data *data)
+{
+ u32 timeout_ns;
+
+ /* Nothing to do if we are not in a DW */
+ if (!mac80211_hwsim_nan_txq_transmitting(data->hw,
+ data->nan.device_vif->txq_mgmt))
+ return;
+
+ /*
+ * Wait a bit and also randomize things so that not everyone is TXing
+ * at the same time. Each slot is 16 TU long, this waits between 100 us
+ * and 5 ms before starting to TX (unless a new frame arrives).
+ */
+ timeout_ns = get_random_u32_inclusive(100 * NSEC_PER_USEC,
+ 5 * NSEC_PER_MSEC);
+
+ hrtimer_start(&data->nan.resume_txqs_timer,
+ ns_to_ktime(timeout_ns),
+ HRTIMER_MODE_REL_SOFT);
+}
+
+enum hrtimer_restart
+mac80211_hwsim_nan_resume_txqs_timer(struct hrtimer *timer)
+{
+ struct mac80211_hwsim_data *data =
+ container_of(timer, struct mac80211_hwsim_data,
+ nan.resume_txqs_timer);
+
+ guard(rcu)();
+
+ /* Wake TX queue for management frames on the NAN device interface */
+ if (mac80211_hwsim_nan_txq_transmitting(data->hw,
+ data->nan.device_vif->txq_mgmt))
+ ieee80211_hwsim_wake_tx_queue(data->hw,
+ data->nan.device_vif->txq_mgmt);
+
+ return HRTIMER_NORESTART;
+}
+
+bool mac80211_hwsim_nan_txq_transmitting(struct ieee80211_hw *hw,
+ struct ieee80211_txq *txq)
+{
+ struct mac80211_hwsim_data *data = hw->priv;
+ u64 tsf;
+ u8 slot;
+
+ if (WARN_ON_ONCE(!data->nan.device_vif))
+ return true;
+
+ tsf = mac80211_hwsim_get_tsf(hw, data->nan.device_vif);
+ slot = hwsim_nan_slot_from_tsf(tsf);
+
+ /* Enforce a maximum channel switch time and guard against TX delays */
+ if (slot != hwsim_nan_slot_from_tsf(tsf + NAN_CHAN_SWITCH_TIME_US))
+ return false;
+
+ /* Check NAN device interface management frame transmission */
+ if (!txq->sta) {
+ /* Only transmit these during one of the DWs */
+ if (slot == SLOT_24GHZ_DW ||
+ (slot == SLOT_5GHZ_DW &&
+ (data->nan.bands & BIT(NL80211_BAND_5GHZ))))
+ return true;
+
+ return false;
+ }
+
+ return true;
+}
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_nan.h b/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
index e86e7f9e9a3c..6a0780797273 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
@@ -15,11 +15,14 @@ struct mac80211_hwsim_nan_data {
struct ieee80211_channel *channel;
struct hrtimer slot_timer;
+ struct hrtimer resume_txqs_timer;
bool notify_dw;
};
enum hrtimer_restart
mac80211_hwsim_nan_slot_timer(struct hrtimer *timer);
+enum hrtimer_restart
+mac80211_hwsim_nan_resume_txqs_timer(struct hrtimer *timer);
int mac80211_hwsim_nan_start(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
@@ -33,4 +36,7 @@ int mac80211_hwsim_nan_change_config(struct ieee80211_hw *hw,
struct cfg80211_nan_conf *conf,
u32 changes);
+bool mac80211_hwsim_nan_txq_transmitting(struct ieee80211_hw *hw,
+ struct ieee80211_txq *txq);
+
#endif /* __MAC80211_HWSIM_NAN_H */
--
2.34.1
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox