* [PATCH wireless] rtlwifi: rtl_pci: Fix possible use-after-free caused by unfinished tasklet
From: Duoming Zhou @ 2026-02-23 3:14 UTC (permalink / raw)
To: linux-wireless; +Cc: pkshih, linux-kernel, Duoming Zhou
The irq_prepare_bcn_tasklet is initialized in rtl_pci_init() and
scheduled when RTL_IMR_BCNINT interrupt is triggered by hardware.
But it is never killed in rtl_pci_deinit(). When the rtlwifi card
probe fails or is being detached, the ieee80211_hw is deallocated.
However, irq_prepare_bcn_tasklet may still be running or pending,
leading to use-after-free when the freed ieee80211_hw is accessed
in _rtl_pci_prepare_bcn_tasklet().
Similar to irq_tasklet, add tasklet_kill() in rtl_pci_deinit() to
ensure that irq_prepare_bcn_tasklet is properly terminated before
the ieee80211_hw is released.
The issue was identified through static analysis.
Fixes: 0c8173385e54 ("rtl8192ce: Add new driver")
Signed-off-by: Duoming Zhou <duoming@zju.edu.cn>
---
drivers/net/wireless/realtek/rtlwifi/pci.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/net/wireless/realtek/rtlwifi/pci.c b/drivers/net/wireless/realtek/rtlwifi/pci.c
index d080469264c..f0010336e78 100644
--- a/drivers/net/wireless/realtek/rtlwifi/pci.c
+++ b/drivers/net/wireless/realtek/rtlwifi/pci.c
@@ -1674,6 +1674,7 @@ static void rtl_pci_deinit(struct ieee80211_hw *hw)
synchronize_irq(rtlpci->pdev->irq);
tasklet_kill(&rtlpriv->works.irq_tasklet);
+ tasklet_kill(&rtlpriv->works.irq_prepare_bcn_tasklet);
cancel_work_sync(&rtlpriv->works.lps_change_work);
}
--
2.34.1
^ permalink raw reply related
* RE: [PATCH wireless] rtlwifi: rtl_pci: Fix possible use-after-free caused by unfinished tasklet
From: Ping-Ke Shih @ 2026-02-23 3:52 UTC (permalink / raw)
To: Duoming Zhou, linux-wireless@vger.kernel.org; +Cc: linux-kernel@vger.kernel.org
In-Reply-To: <20260223031415.39221-1-duoming@zju.edu.cn>
Duoming Zhou <duoming@zju.edu.cn> wrote:
> The irq_prepare_bcn_tasklet is initialized in rtl_pci_init() and
> scheduled when RTL_IMR_BCNINT interrupt is triggered by hardware.
> But it is never killed in rtl_pci_deinit(). When the rtlwifi card
> probe fails or is being detached, the ieee80211_hw is deallocated.
> However, irq_prepare_bcn_tasklet may still be running or pending,
> leading to use-after-free when the freed ieee80211_hw is accessed
> in _rtl_pci_prepare_bcn_tasklet().
>
> Similar to irq_tasklet, add tasklet_kill() in rtl_pci_deinit() to
> ensure that irq_prepare_bcn_tasklet is properly terminated before
> the ieee80211_hw is released.
>
> The issue was identified through static analysis.
>
> Fixes: 0c8173385e54 ("rtl8192ce: Add new driver")
> Signed-off-by: Duoming Zhou <duoming@zju.edu.cn>
The tree selection should be "rtw-next" (may be "rtw", but I don't think
this patch is urgent), and subject prefix should be "wifi: rtlwifi: ...".
More, I'd point out the name of tasklet in subject. Then,
"[PATCH rtw-next] wifi: rtlwifi: pci: fix possible use-after-free caused by unfinished irq_prepare_bcn_tasklet"
Otherwise, looks good to me.
Acked-by: Ping-Ke Shih <pkshih@realtek.com>
> ---
> drivers/net/wireless/realtek/rtlwifi/pci.c | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/drivers/net/wireless/realtek/rtlwifi/pci.c b/drivers/net/wireless/realtek/rtlwifi/pci.c
> index d080469264c..f0010336e78 100644
> --- a/drivers/net/wireless/realtek/rtlwifi/pci.c
> +++ b/drivers/net/wireless/realtek/rtlwifi/pci.c
> @@ -1674,6 +1674,7 @@ static void rtl_pci_deinit(struct ieee80211_hw *hw)
>
> synchronize_irq(rtlpci->pdev->irq);
> tasklet_kill(&rtlpriv->works.irq_tasklet);
> + tasklet_kill(&rtlpriv->works.irq_prepare_bcn_tasklet);
> cancel_work_sync(&rtlpriv->works.lps_change_work);
> }
>
> --
> 2.34.1
^ permalink raw reply
* Re: [PATCH wireless] rtlwifi: rtl_pci: Fix possible use-after-free caused by unfinished tasklet
From: duoming @ 2026-02-23 4:31 UTC (permalink / raw)
To: Ping-Ke Shih; +Cc: linux-wireless@vger.kernel.org, linux-kernel@vger.kernel.org
In-Reply-To: <153117a9fd5b464baa77b5ef48daaabc@realtek.com>
Ping-Ke Shih <pkshih@realtek.com> wrote:
> > The irq_prepare_bcn_tasklet is initialized in rtl_pci_init() and
> > scheduled when RTL_IMR_BCNINT interrupt is triggered by hardware.
> > But it is never killed in rtl_pci_deinit(). When the rtlwifi card
> > probe fails or is being detached, the ieee80211_hw is deallocated.
> > However, irq_prepare_bcn_tasklet may still be running or pending,
> > leading to use-after-free when the freed ieee80211_hw is accessed
> > in _rtl_pci_prepare_bcn_tasklet().
> >
> > Similar to irq_tasklet, add tasklet_kill() in rtl_pci_deinit() to
> > ensure that irq_prepare_bcn_tasklet is properly terminated before
> > the ieee80211_hw is released.
> >
> > The issue was identified through static analysis.
> >
> > Fixes: 0c8173385e54 ("rtl8192ce: Add new driver")
> > Signed-off-by: Duoming Zhou <duoming@zju.edu.cn>
>
> The tree selection should be "rtw-next" (may be "rtw", but I don't think
> this patch is urgent), and subject prefix should be "wifi: rtlwifi: ...".
> More, I'd point out the name of tasklet in subject. Then,
> "[PATCH rtw-next] wifi: rtlwifi: pci: fix possible use-after-free caused by unfinished irq_prepare_bcn_tasklet"
Thank you for your time and reply! I will select the "rtw-next" tree,
adjust the subject prefix and send a v2 patch.
> Otherwise, looks good to me.
>
> Acked-by: Ping-Ke Shih <pkshih@realtek.com>
Best regards,
Duoming Zhou
^ permalink raw reply
* [PATCH rtw-next v2] wifi: rtlwifi: pci: fix possible use-after-free caused by unfinished irq_prepare_bcn_tasklet
From: Duoming Zhou @ 2026-02-23 4:55 UTC (permalink / raw)
To: linux-wireless; +Cc: linux-kernel, pkshih, Duoming Zhou
The irq_prepare_bcn_tasklet is initialized in rtl_pci_init() and
scheduled when RTL_IMR_BCNINT interrupt is triggered by hardware.
But it is never killed in rtl_pci_deinit(). When the rtlwifi card
probe fails or is being detached, the ieee80211_hw is deallocated.
However, irq_prepare_bcn_tasklet may still be running or pending,
leading to use-after-free when the freed ieee80211_hw is accessed
in _rtl_pci_prepare_bcn_tasklet().
Similar to irq_tasklet, add tasklet_kill() in rtl_pci_deinit() to
ensure that irq_prepare_bcn_tasklet is properly terminated before
the ieee80211_hw is released.
The issue was identified through static analysis.
Fixes: 0c8173385e54 ("rtl8192ce: Add new driver")
Signed-off-by: Duoming Zhou <duoming@zju.edu.cn>
Acked-by: Ping-Ke Shih <pkshih@realtek.com>
---
Changes in v2:
- Adjust the subject prefix and point out the problematic tasklet in subject.
- Select the "rtw-next" tree.
drivers/net/wireless/realtek/rtlwifi/pci.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/net/wireless/realtek/rtlwifi/pci.c b/drivers/net/wireless/realtek/rtlwifi/pci.c
index d080469264c..f0010336e78 100644
--- a/drivers/net/wireless/realtek/rtlwifi/pci.c
+++ b/drivers/net/wireless/realtek/rtlwifi/pci.c
@@ -1674,6 +1674,7 @@ static void rtl_pci_deinit(struct ieee80211_hw *hw)
synchronize_irq(rtlpci->pdev->irq);
tasklet_kill(&rtlpriv->works.irq_tasklet);
+ tasklet_kill(&rtlpriv->works.irq_prepare_bcn_tasklet);
cancel_work_sync(&rtlpriv->works.lps_change_work);
}
--
2.34.1
^ permalink raw reply related
* [PATCH ath-next v3] wifi: ath12k: add basic hwmon temperature reporting
From: Maharaja Kennadyrajan @ 2026-02-23 6:02 UTC (permalink / raw)
To: ath12k; +Cc: linux-wireless, Maharaja Kennadyrajan, Aishwarya R
Add initial thermal support by wiring up a per-radio (pdev) hwmon temperature
sensor backed by the existing WMI pdev temperature command and event.
When userspace reads the sysfs file temp1_input, the driver sends
WMI_PDEV_GET_TEMPERATURE_CMDID (tag WMI_TAG_PDEV_GET_TEMPERATURE_CMD) and waits
for the corresponding WMI_PDEV_TEMPERATURE_EVENTID
(tag WMI_TAG_PDEV_TEMPERATURE_EVENT) to get the temperature and pdev_id.
Export the reported value in millidegrees Celsius as required by hwmon.
The temperature reported is per-radio (pdev). In a multi-radio wiphy under a
single phy, a separate hwmon device is created for each radio.
Sample command and output:
$ cat /sys/devices/pci0000:00/.../ieee80211/phyX/hwmonY/temp1_input
$ 50000
Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1
Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3
Reported-by: kernel test robot <lkp@intel.com>
Closes: https://lore.kernel.org/oe-kbuild-all/202602160145.YQdvbqYY-lkp@intel.com/
Co-developed-by: Aishwarya R <aishwarya.r@oss.qualcomm.com>
Signed-off-by: Aishwarya R <aishwarya.r@oss.qualcomm.com>
Signed-off-by: Maharaja Kennadyrajan <maharaja.kennadyrajan@oss.qualcomm.com>
---
v3: Addressed the Pablo's comment on looping the ab->pdevs in the function
ath12k_thermal_register() while handling the failure case.
Renamed mutex_lock to generic name from thermal.read_lock to
thermal.lock.
v2: Fixed the kernel test robot reported build test error.
drivers/net/wireless/ath/ath12k/Makefile | 1 +
drivers/net/wireless/ath/ath12k/core.c | 13 +++
drivers/net/wireless/ath/ath12k/core.h | 3 +
drivers/net/wireless/ath/ath12k/mac.c | 5 +
drivers/net/wireless/ath/ath12k/thermal.c | 125 ++++++++++++++++++++++
drivers/net/wireless/ath/ath12k/thermal.h | 44 ++++++++
drivers/net/wireless/ath/ath12k/wmi.c | 57 +++++-----
7 files changed, 217 insertions(+), 31 deletions(-)
create mode 100644 drivers/net/wireless/ath/ath12k/thermal.c
create mode 100644 drivers/net/wireless/ath/ath12k/thermal.h
diff --git a/drivers/net/wireless/ath/ath12k/Makefile b/drivers/net/wireless/ath/ath12k/Makefile
index 3ba1236956cc..3b39b2c33307 100644
--- a/drivers/net/wireless/ath/ath12k/Makefile
+++ b/drivers/net/wireless/ath/ath12k/Makefile
@@ -32,6 +32,7 @@ ath12k-$(CONFIG_ATH12K_TRACING) += trace.o
ath12k-$(CONFIG_PM) += wow.o
ath12k-$(CONFIG_ATH12K_COREDUMP) += coredump.o
ath12k-$(CONFIG_NL80211_TESTMODE) += testmode.o
+ath12k-$(CONFIG_THERMAL) += thermal.o
# for tracing framework to find trace.h
CFLAGS_trace.o := -I$(src)
diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c
index 9d6c50a94e64..9dca1a0af73e 100644
--- a/drivers/net/wireless/ath/ath12k/core.c
+++ b/drivers/net/wireless/ath/ath12k/core.c
@@ -863,11 +863,22 @@ static int ath12k_core_pdev_create(struct ath12k_base *ab)
return ret;
}
+ ret = ath12k_thermal_register(ab);
+ if (ret) {
+ ath12k_err(ab, "could not register thermal device: %d\n", ret);
+ goto err_dp_pdev_free;
+ }
+
return 0;
+
+err_dp_pdev_free:
+ ath12k_dp_pdev_free(ab);
+ return ret;
}
static void ath12k_core_pdev_destroy(struct ath12k_base *ab)
{
+ ath12k_thermal_unregister(ab);
ath12k_dp_pdev_free(ab);
}
@@ -1361,6 +1372,7 @@ static int ath12k_core_reconfigure_on_crash(struct ath12k_base *ab)
mutex_lock(&ab->core_lock);
ath12k_link_sta_rhash_tbl_destroy(ab);
+ ath12k_thermal_unregister(ab);
ath12k_dp_pdev_free(ab);
ath12k_ce_cleanup_pipes(ab);
ath12k_wmi_detach(ab);
@@ -1502,6 +1514,7 @@ static void ath12k_core_pre_reconfigure_recovery(struct ath12k_base *ab)
complete(&ar->vdev_delete_done);
complete(&ar->bss_survey_done);
complete_all(&ar->regd_update_completed);
+ complete_all(&ar->thermal.wmi_sync);
wake_up(&ar->dp.tx_empty_waitq);
idr_for_each(&ar->txmgmt_idr,
diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h
index 990934ec92fc..760c76d6f0f4 100644
--- a/drivers/net/wireless/ath/ath12k/core.h
+++ b/drivers/net/wireless/ath/ath12k/core.h
@@ -36,6 +36,7 @@
#include "coredump.h"
#include "cmn_defs.h"
#include "dp_cmn.h"
+#include "thermal.h"
#define SM(_v, _f) (((_v) << _f##_LSB) & _f##_MASK)
@@ -757,6 +758,8 @@ struct ath12k {
s8 max_allowed_tx_power;
struct ath12k_pdev_rssi_offsets rssi_info;
+
+ struct ath12k_thermal thermal;
};
struct ath12k_hw {
diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c
index 68431a0e128e..03e4edb5fb43 100644
--- a/drivers/net/wireless/ath/ath12k/mac.c
+++ b/drivers/net/wireless/ath/ath12k/mac.c
@@ -14792,6 +14792,11 @@ static void ath12k_mac_setup(struct ath12k *ar)
init_completion(&ar->mlo_setup_done);
init_completion(&ar->completed_11d_scan);
init_completion(&ar->regd_update_completed);
+ init_completion(&ar->thermal.wmi_sync);
+ mutex_init(&ar->thermal.lock);
+
+ ar->thermal.temperature = 0;
+ ar->thermal.hwmon_dev = NULL;
INIT_DELAYED_WORK(&ar->scan.timeout, ath12k_scan_timeout_work);
wiphy_work_init(&ar->scan.vdev_clean_wk, ath12k_scan_vdev_clean_work);
diff --git a/drivers/net/wireless/ath/ath12k/thermal.c b/drivers/net/wireless/ath/ath12k/thermal.c
new file mode 100644
index 000000000000..fcfe5bf69075
--- /dev/null
+++ b/drivers/net/wireless/ath/ath12k/thermal.c
@@ -0,0 +1,125 @@
+// SPDX-License-Identifier: BSD-3-Clause-Clear
+/*
+ * Copyright (c) 2020 The Linux Foundation. All rights reserved.
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/device.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/sysfs.h>
+#include <linux/thermal.h>
+#include "core.h"
+#include "debug.h"
+
+static ssize_t ath12k_thermal_temp_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct ath12k *ar = dev_get_drvdata(dev);
+ unsigned long time_left;
+ int ret, temperature;
+
+ guard(wiphy)(ath12k_ar_to_hw(ar)->wiphy);
+ guard(mutex)(&ar->thermal.lock);
+
+ if (ar->ah->state != ATH12K_HW_STATE_ON)
+ return -ENETDOWN;
+
+ reinit_completion(&ar->thermal.wmi_sync);
+ ret = ath12k_wmi_send_pdev_temperature_cmd(ar);
+ if (ret) {
+ ath12k_warn(ar->ab, "failed to read temperature %d\n", ret);
+ return ret;
+ }
+
+ if (test_bit(ATH12K_FLAG_CRASH_FLUSH, &ar->ab->dev_flags))
+ return -ESHUTDOWN;
+
+ time_left = wait_for_completion_timeout(&ar->thermal.wmi_sync,
+ ATH12K_THERMAL_SYNC_TIMEOUT_HZ);
+ if (!time_left) {
+ ath12k_warn(ar->ab, "failed to synchronize thermal read\n");
+ return -ETIMEDOUT;
+ }
+
+ spin_lock_bh(&ar->data_lock);
+ temperature = ar->thermal.temperature;
+ spin_unlock_bh(&ar->data_lock);
+
+ /* display in millidegree celsius */
+ return sysfs_emit(buf, "%d\n", temperature * 1000);
+}
+
+void ath12k_thermal_event_temperature(struct ath12k *ar, int temperature)
+{
+ spin_lock_bh(&ar->data_lock);
+ ar->thermal.temperature = temperature;
+ spin_unlock_bh(&ar->data_lock);
+ complete_all(&ar->thermal.wmi_sync);
+}
+
+static SENSOR_DEVICE_ATTR_RO(temp1_input, ath12k_thermal_temp, 0);
+
+static struct attribute *ath12k_hwmon_attrs[] = {
+ &sensor_dev_attr_temp1_input.dev_attr.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(ath12k_hwmon);
+
+int ath12k_thermal_register(struct ath12k_base *ab)
+{
+ struct ath12k *ar;
+ int i, j, ret;
+
+ if (!IS_REACHABLE(CONFIG_HWMON))
+ return 0;
+
+ for (i = 0; i < ab->num_radios; i++) {
+ ar = ab->pdevs[i].ar;
+ if (!ar)
+ continue;
+
+ ar->thermal.hwmon_dev =
+ hwmon_device_register_with_groups(&ar->ah->hw->wiphy->dev,
+ "ath12k_hwmon", ar,
+ ath12k_hwmon_groups);
+ if (IS_ERR(ar->thermal.hwmon_dev)) {
+ ret = PTR_ERR(ar->thermal.hwmon_dev);
+ ar->thermal.hwmon_dev = NULL;
+ ath12k_err(ar->ab, "failed to register hwmon device: %d\n",
+ ret);
+ for (j = i - 1; j >= 0; j--) {
+ ar = ab->pdevs[j].ar;
+ if (!ar)
+ continue;
+
+ hwmon_device_unregister(ar->thermal.hwmon_dev);
+ ar->thermal.hwmon_dev = NULL;
+ }
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+void ath12k_thermal_unregister(struct ath12k_base *ab)
+{
+ struct ath12k *ar;
+ int i;
+
+ if (!IS_REACHABLE(CONFIG_HWMON))
+ return;
+
+ for (i = 0; i < ab->num_radios; i++) {
+ ar = ab->pdevs[i].ar;
+ if (!ar)
+ continue;
+
+ if (ar->thermal.hwmon_dev) {
+ hwmon_device_unregister(ar->thermal.hwmon_dev);
+ ar->thermal.hwmon_dev = NULL;
+ }
+ }
+}
diff --git a/drivers/net/wireless/ath/ath12k/thermal.h b/drivers/net/wireless/ath/ath12k/thermal.h
new file mode 100644
index 000000000000..17822d998b6e
--- /dev/null
+++ b/drivers/net/wireless/ath/ath12k/thermal.h
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: BSD-3-Clause-Clear */
+/*
+ * Copyright (c) 2020 The Linux Foundation. All rights reserved.
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef _ATH12K_THERMAL_
+#define _ATH12K_THERMAL_
+
+#include <linux/mutex.h>
+
+#define ATH12K_THERMAL_SYNC_TIMEOUT_HZ (5 * HZ)
+
+struct ath12k_thermal {
+ struct completion wmi_sync;
+
+ /* temperature value in Celsius degree protected by data_lock. */
+ int temperature;
+ struct device *hwmon_dev;
+ /* Serialize thermal operations and hwmon reads */
+ struct mutex lock;
+};
+
+#if IS_REACHABLE(CONFIG_THERMAL)
+int ath12k_thermal_register(struct ath12k_base *ab);
+void ath12k_thermal_unregister(struct ath12k_base *ab);
+void ath12k_thermal_event_temperature(struct ath12k *ar, int temperature);
+#else
+static inline int ath12k_thermal_register(struct ath12k_base *ab)
+{
+ return 0;
+}
+
+static inline void ath12k_thermal_unregister(struct ath12k_base *ab)
+{
+}
+
+static inline void ath12k_thermal_event_temperature(struct ath12k *ar,
+ int temperature)
+{
+}
+
+#endif
+#endif /* _ATH12K_THERMAL_ */
diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c
index 7617fc3a2479..69f2dbcfca63 100644
--- a/drivers/net/wireless/ath/ath12k/wmi.c
+++ b/drivers/net/wireless/ath/ath12k/wmi.c
@@ -6778,31 +6778,6 @@ static int ath12k_pull_peer_assoc_conf_ev(struct ath12k_base *ab, struct sk_buff
return 0;
}
-static int
-ath12k_pull_pdev_temp_ev(struct ath12k_base *ab, struct sk_buff *skb,
- const struct wmi_pdev_temperature_event *ev)
-{
- const void **tb;
- int ret;
-
- tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
- if (IS_ERR(tb)) {
- ret = PTR_ERR(tb);
- ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
- return ret;
- }
-
- ev = tb[WMI_TAG_PDEV_TEMPERATURE_EVENT];
- if (!ev) {
- ath12k_warn(ab, "failed to fetch pdev temp ev");
- kfree(tb);
- return -EPROTO;
- }
-
- kfree(tb);
- return 0;
-}
-
static void ath12k_wmi_op_ep_tx_credits(struct ath12k_base *ab)
{
/* try to send pending beacons first. they take priority */
@@ -8811,25 +8786,45 @@ static void
ath12k_wmi_pdev_temperature_event(struct ath12k_base *ab,
struct sk_buff *skb)
{
+ const struct wmi_pdev_temperature_event *ev;
struct ath12k *ar;
- struct wmi_pdev_temperature_event ev = {};
+ const void **tb;
+ int temp;
+ u32 pdev_id;
+
+ tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
+ if (IS_ERR(tb)) {
+ ath12k_warn(ab, "failed to parse tlv: %ld\n", PTR_ERR(tb));
+ return;
+ }
- if (ath12k_pull_pdev_temp_ev(ab, skb, &ev) != 0) {
- ath12k_warn(ab, "failed to extract pdev temperature event");
+ ev = tb[WMI_TAG_PDEV_TEMPERATURE_EVENT];
+ if (!ev) {
+ ath12k_warn(ab, "failed to fetch pdev temp ev\n");
+ kfree(tb);
return;
}
+ temp = a_sle32_to_cpu(ev->temp);
+ pdev_id = le32_to_cpu(ev->pdev_id);
+
+ kfree(tb);
+
ath12k_dbg(ab, ATH12K_DBG_WMI,
- "pdev temperature ev temp %d pdev_id %d\n", ev.temp, ev.pdev_id);
+ "pdev temperature ev temp %d pdev_id %u\n",
+ temp, pdev_id);
rcu_read_lock();
- ar = ath12k_mac_get_ar_by_pdev_id(ab, le32_to_cpu(ev.pdev_id));
+ ar = ath12k_mac_get_ar_by_pdev_id(ab, pdev_id);
if (!ar) {
- ath12k_warn(ab, "invalid pdev id in pdev temperature ev %d", ev.pdev_id);
+ ath12k_warn(ab, "invalid pdev id %u in pdev temperature ev\n",
+ pdev_id);
goto exit;
}
+ ath12k_thermal_event_temperature(ar, temp);
+
exit:
rcu_read_unlock();
}
base-commit: 37a93dd5c49b5fda807fd204edf2547c3493319c
--
2.17.1
^ permalink raw reply related
* [PATCH 3/5] wifi: mt76: mt7921: refactor regulatory notifier flow
From: JB Tsai @ 2026-02-23 7:38 UTC (permalink / raw)
To: nbd, lorenzo
Cc: linux-wireless, linux-mediatek, Deren.Wu, Sean.Wang, Quan.Zhou,
Ryder.Lee, Leon.Yen, litien.chang, jb.tsai
In-Reply-To: <20260223073854.2464232-1-jb.tsai@mediatek.com>
Rename mt7921_regd_update() to mt7921_mcu_regd_update() to centralize
regd updates with error handling.
Signed-off-by: JB Tsai <jb.tsai@mediatek.com>
---
.../net/wireless/mediatek/mt76/mt7921/pci.c | 2 +-
.../net/wireless/mediatek/mt76/mt7921/regd.c | 45 +++++++++++++------
.../net/wireless/mediatek/mt76/mt7921/regd.h | 3 +-
3 files changed, 35 insertions(+), 15 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c
index a173a61f2b49..3fdf55c056a6 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c
@@ -545,7 +545,7 @@ static int mt7921_pci_resume(struct device *device)
if (err < 0)
goto failed;
- mt7921_regd_update(dev);
+ mt7921_mcu_regd_update(dev, mdev->alpha2, dev->country_ie_env);
err = mt7921_mcu_radio_led_ctrl(dev, EXT_CMD_RADIO_ON_LED);
failed:
pm->suspended = false;
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/regd.c b/drivers/net/wireless/mediatek/mt76/mt7921/regd.c
index 70440ab8ba82..f795ee2eb446 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/regd.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/regd.c
@@ -72,18 +72,43 @@ mt7921_regd_channel_update(struct wiphy *wiphy, struct mt792x_dev *dev)
}
}
-void mt7921_regd_update(struct mt792x_dev *dev)
+int mt7921_mcu_regd_update(struct mt792x_dev *dev, u8 *alpha2,
+ enum environment_cap country_ie_env)
{
struct mt76_dev *mdev = &dev->mt76;
struct ieee80211_hw *hw = mdev->hw;
struct wiphy *wiphy = hw->wiphy;
+ int ret = 0;
+
+ dev->regd_in_progress = true;
+
+ mt792x_mutex_acquire(dev);
+ if (!dev->regd_change)
+ goto err;
+
+ ret = mt7921_mcu_set_clc(dev, alpha2, country_ie_env);
+ if (ret < 0)
+ goto err;
- mt7921_mcu_set_clc(dev, mdev->alpha2, dev->country_ie_env);
mt7921_regd_channel_update(wiphy, dev);
- mt76_connac_mcu_set_channel_domain(hw->priv);
- mt7921_set_tx_sar_pwr(hw, NULL);
+
+ ret = mt76_connac_mcu_set_channel_domain(hw->priv);
+ if (ret < 0)
+ goto err;
+
+ ret = mt7921_set_tx_sar_pwr(hw, NULL);
+ if (ret < 0)
+ goto err;
+
+err:
+ mt792x_mutex_release(dev);
+ dev->regd_change = false;
+ dev->regd_in_progress = false;
+ wake_up(&dev->wait);
+
+ return ret;
}
-EXPORT_SYMBOL_GPL(mt7921_regd_update);
+EXPORT_SYMBOL_GPL(mt7921_mcu_regd_update);
void mt7921_regd_notifier(struct wiphy *wiphy,
struct regulatory_request *request)
@@ -106,12 +131,6 @@ void mt7921_regd_notifier(struct wiphy *wiphy,
if (pm->suspended)
return;
- dev->regd_in_progress = true;
-
- mt792x_mutex_acquire(dev);
- mt7921_regd_update(dev);
- mt792x_mutex_release(dev);
-
- dev->regd_in_progress = false;
- wake_up(&dev->wait);
+ mt7921_mcu_regd_update(dev, request->alpha2,
+ request->country_ie_env);
}
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/regd.h b/drivers/net/wireless/mediatek/mt76/mt7921/regd.h
index 74bc2fdd532c..da5bd4450312 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/regd.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/regd.h
@@ -6,7 +6,8 @@
#include "mt7921.h"
-void mt7921_regd_update(struct mt792x_dev *dev);
+int mt7921_mcu_regd_update(struct mt792x_dev *dev, u8 *alpha2,
+ enum environment_cap country_ie_env);
void mt7921_regd_notifier(struct wiphy *wiphy,
struct regulatory_request *request);
bool mt7921_regd_clc_supported(struct mt792x_dev *dev);
--
2.45.2
^ permalink raw reply related
* [PATCH 2/5] wifi: mt76: mt7921: refactor CLC support check flow
From: JB Tsai @ 2026-02-23 7:38 UTC (permalink / raw)
To: nbd, lorenzo
Cc: linux-wireless, linux-mediatek, Deren.Wu, Sean.Wang, Quan.Zhou,
Ryder.Lee, Leon.Yen, litien.chang, jb.tsai
In-Reply-To: <20260223073854.2464232-1-jb.tsai@mediatek.com>
Move the disable_clc module parameter to regd.c and introduce
mt7925_regd_clc_supported() to centralize CLC support checks.
Signed-off-by: JB Tsai <jb.tsai@mediatek.com>
---
drivers/net/wireless/mediatek/mt76/mt7921/mcu.c | 14 ++++++++------
drivers/net/wireless/mediatek/mt76/mt7921/regd.c | 13 +++++++++++++
drivers/net/wireless/mediatek/mt76/mt7921/regd.h | 1 +
3 files changed, 22 insertions(+), 6 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c
index 8442dbd2ee23..1e2afa736cdf 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c
@@ -4,6 +4,7 @@
#include <linux/fs.h>
#include <linux/firmware.h>
#include "mt7921.h"
+#include "regd.h"
#include "mcu.h"
#include "../mt76_connac2_mac.h"
#include "../mt792x_trace.h"
@@ -11,10 +12,6 @@
#define MT_STA_BFER BIT(0)
#define MT_STA_BFEE BIT(1)
-static bool mt7921_disable_clc;
-module_param_named(disable_clc, mt7921_disable_clc, bool, 0644);
-MODULE_PARM_DESC(disable_clc, "disable CLC support");
-
int mt7921_mcu_parse_response(struct mt76_dev *mdev, int cmd,
struct sk_buff *skb, int seq)
{
@@ -422,8 +419,7 @@ static int mt7921_load_clc(struct mt792x_dev *dev, const char *fw_name)
u8 *clc_base = NULL, hw_encap = 0;
dev->phy.clc_chan_conf = 0xff;
- if (mt7921_disable_clc ||
- mt76_is_usb(&dev->mt76))
+ if (!mt7921_regd_clc_supported(dev))
return 0;
if (mt76_is_mmio(&dev->mt76)) {
@@ -470,6 +466,9 @@ static int mt7921_load_clc(struct mt792x_dev *dev, const char *fw_name)
for (offset = 0; offset < len; offset += le32_to_cpu(clc->len)) {
clc = (const struct mt7921_clc *)(clc_base + offset);
+ if (clc->idx >= ARRAY_SIZE(phy->clc))
+ break;
+
/* do not init buf again if chip reset triggered */
if (phy->clc[clc->idx])
continue;
@@ -1403,6 +1402,9 @@ int mt7921_mcu_set_clc(struct mt792x_dev *dev, u8 *alpha2,
struct mt792x_phy *phy = (struct mt792x_phy *)&dev->phy;
int i, ret;
+ if (!ARRAY_SIZE(phy->clc))
+ return -ESRCH;
+
/* submit all clc config */
for (i = 0; i < ARRAY_SIZE(phy->clc); i++) {
ret = __mt7921_mcu_set_clc(dev, alpha2, env_cap,
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/regd.c b/drivers/net/wireless/mediatek/mt76/mt7921/regd.c
index 6e6c81189222..70440ab8ba82 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/regd.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/regd.c
@@ -7,6 +7,19 @@
#include "regd.h"
#include "mcu.h"
+static bool mt7921_disable_clc;
+module_param_named(disable_clc, mt7921_disable_clc, bool, 0644);
+MODULE_PARM_DESC(disable_clc, "disable CLC support");
+
+bool mt7921_regd_clc_supported(struct mt792x_dev *dev)
+{
+ if (mt7921_disable_clc ||
+ mt76_is_usb(&dev->mt76))
+ return false;
+
+ return true;
+}
+
static void
mt7921_regd_channel_update(struct wiphy *wiphy, struct mt792x_dev *dev)
{
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/regd.h b/drivers/net/wireless/mediatek/mt76/mt7921/regd.h
index 0ba6161e1919..74bc2fdd532c 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/regd.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/regd.h
@@ -9,5 +9,6 @@
void mt7921_regd_update(struct mt792x_dev *dev);
void mt7921_regd_notifier(struct wiphy *wiphy,
struct regulatory_request *request);
+bool mt7921_regd_clc_supported(struct mt792x_dev *dev);
#endif
--
2.45.2
^ permalink raw reply related
* [PATCH 1/5] wifi: mt76: mt7921: refactor regulatory domain handling to regd.[ch]
From: JB Tsai @ 2026-02-23 7:38 UTC (permalink / raw)
To: nbd, lorenzo
Cc: linux-wireless, linux-mediatek, Deren.Wu, Sean.Wang, Quan.Zhou,
Ryder.Lee, Leon.Yen, litien.chang, jb.tsai
Move regd logic to regd.c and regd.h files
Signed-off-by: JB Tsai <jb.tsai@mediatek.com>
---
.../wireless/mediatek/mt76/mt7921/Makefile | 2 +-
.../net/wireless/mediatek/mt76/mt7921/init.c | 98 +----------------
.../net/wireless/mediatek/mt76/mt7921/main.c | 1 +
.../wireless/mediatek/mt76/mt7921/mt7921.h | 1 -
.../net/wireless/mediatek/mt76/mt7921/pci.c | 1 +
.../net/wireless/mediatek/mt76/mt7921/regd.c | 104 ++++++++++++++++++
.../net/wireless/mediatek/mt76/mt7921/regd.h | 13 +++
7 files changed, 121 insertions(+), 99 deletions(-)
create mode 100644 drivers/net/wireless/mediatek/mt76/mt7921/regd.c
create mode 100644 drivers/net/wireless/mediatek/mt76/mt7921/regd.h
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/Makefile b/drivers/net/wireless/mediatek/mt76/mt7921/Makefile
index 2ad3c1cc3779..3ef7c9c45386 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/Makefile
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/Makefile
@@ -5,7 +5,7 @@ obj-$(CONFIG_MT7921E) += mt7921e.o
obj-$(CONFIG_MT7921S) += mt7921s.o
obj-$(CONFIG_MT7921U) += mt7921u.o
-mt7921-common-y := mac.o mcu.o main.o init.o debugfs.o
+mt7921-common-y := mac.o mcu.o regd.o main.o init.o debugfs.o
mt7921-common-$(CONFIG_NL80211_TESTMODE) += testmode.o
mt7921e-y := pci.o pci_mac.o pci_mcu.o
mt7921s-y := sdio.o sdio_mac.o sdio_mcu.o
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/init.c b/drivers/net/wireless/mediatek/mt76/mt7921/init.c
index 29732315af1c..1fe2f2bc3881 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/init.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/init.c
@@ -9,6 +9,7 @@
#include "mt7921.h"
#include "../mt76_connac2_mac.h"
#include "mcu.h"
+#include "regd.h"
static ssize_t mt7921_thermal_temp_show(struct device *dev,
struct device_attribute *attr,
@@ -60,103 +61,6 @@ static int mt7921_thermal_init(struct mt792x_phy *phy)
return PTR_ERR_OR_ZERO(hwmon);
}
-static void
-mt7921_regd_channel_update(struct wiphy *wiphy, struct mt792x_dev *dev)
-{
-#define IS_UNII_INVALID(idx, sfreq, efreq) \
- (!(dev->phy.clc_chan_conf & BIT(idx)) && (cfreq) >= (sfreq) && (cfreq) <= (efreq))
- struct ieee80211_supported_band *sband;
- struct mt76_dev *mdev = &dev->mt76;
- struct device_node *np, *band_np;
- struct ieee80211_channel *ch;
- int i, cfreq;
-
- np = mt76_find_power_limits_node(mdev);
-
- sband = wiphy->bands[NL80211_BAND_5GHZ];
- band_np = np ? of_get_child_by_name(np, "txpower-5g") : NULL;
- for (i = 0; i < sband->n_channels; i++) {
- ch = &sband->channels[i];
- cfreq = ch->center_freq;
-
- if (np && (!band_np || !mt76_find_channel_node(band_np, ch))) {
- ch->flags |= IEEE80211_CHAN_DISABLED;
- continue;
- }
-
- /* UNII-4 */
- if (IS_UNII_INVALID(0, 5845, 5925))
- ch->flags |= IEEE80211_CHAN_DISABLED;
- }
-
- sband = wiphy->bands[NL80211_BAND_6GHZ];
- if (!sband)
- return;
-
- band_np = np ? of_get_child_by_name(np, "txpower-6g") : NULL;
- for (i = 0; i < sband->n_channels; i++) {
- ch = &sband->channels[i];
- cfreq = ch->center_freq;
-
- if (np && (!band_np || !mt76_find_channel_node(band_np, ch))) {
- ch->flags |= IEEE80211_CHAN_DISABLED;
- continue;
- }
-
- /* UNII-5/6/7/8 */
- if (IS_UNII_INVALID(1, 5925, 6425) ||
- IS_UNII_INVALID(2, 6425, 6525) ||
- IS_UNII_INVALID(3, 6525, 6875) ||
- IS_UNII_INVALID(4, 6875, 7125))
- ch->flags |= IEEE80211_CHAN_DISABLED;
- }
-}
-
-void mt7921_regd_update(struct mt792x_dev *dev)
-{
- struct mt76_dev *mdev = &dev->mt76;
- struct ieee80211_hw *hw = mdev->hw;
- struct wiphy *wiphy = hw->wiphy;
-
- mt7921_mcu_set_clc(dev, mdev->alpha2, dev->country_ie_env);
- mt7921_regd_channel_update(wiphy, dev);
- mt76_connac_mcu_set_channel_domain(hw->priv);
- mt7921_set_tx_sar_pwr(hw, NULL);
-}
-EXPORT_SYMBOL_GPL(mt7921_regd_update);
-
-static void
-mt7921_regd_notifier(struct wiphy *wiphy,
- struct regulatory_request *request)
-{
- struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
- struct mt792x_dev *dev = mt792x_hw_dev(hw);
- struct mt76_connac_pm *pm = &dev->pm;
-
- memcpy(dev->mt76.alpha2, request->alpha2, sizeof(dev->mt76.alpha2));
- dev->mt76.region = request->dfs_region;
- dev->country_ie_env = request->country_ie_env;
-
- if (request->initiator == NL80211_REGDOM_SET_BY_USER) {
- if (dev->mt76.alpha2[0] == '0' && dev->mt76.alpha2[1] == '0')
- wiphy->regulatory_flags &= ~REGULATORY_COUNTRY_IE_IGNORE;
- else
- wiphy->regulatory_flags |= REGULATORY_COUNTRY_IE_IGNORE;
- }
-
- if (pm->suspended)
- return;
-
- dev->regd_in_progress = true;
-
- mt792x_mutex_acquire(dev);
- mt7921_regd_update(dev);
- mt792x_mutex_release(dev);
-
- dev->regd_in_progress = false;
- wake_up(&dev->wait);
-}
-
int mt7921_mac_init(struct mt792x_dev *dev)
{
int i;
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/main.c b/drivers/net/wireless/mediatek/mt76/mt7921/main.c
index 42b9514e04e7..00ca3d3f3ef0 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/main.c
@@ -7,6 +7,7 @@
#include <linux/module.h>
#include <net/ipv6.h>
#include "mt7921.h"
+#include "regd.h"
#include "mcu.h"
static int
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mt7921.h b/drivers/net/wireless/mediatek/mt76/mt7921/mt7921.h
index ad92af98e314..5239ea970d24 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/mt7921.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/mt7921.h
@@ -246,7 +246,6 @@ mt7921_l1_rmw(struct mt792x_dev *dev, u32 addr, u32 mask, u32 val)
#define mt7921_l1_set(dev, addr, val) mt7921_l1_rmw(dev, addr, 0, val)
#define mt7921_l1_clear(dev, addr, val) mt7921_l1_rmw(dev, addr, val, 0)
-void mt7921_regd_update(struct mt792x_dev *dev);
int mt7921_mac_init(struct mt792x_dev *dev);
bool mt7921_mac_wtbl_update(struct mt792x_dev *dev, int idx, u32 mask);
int mt7921_mac_sta_add(struct mt76_dev *mdev, struct ieee80211_vif *vif,
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c
index 65c7fe671137..a173a61f2b49 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c
@@ -12,6 +12,7 @@
#include "../mt76_connac2_mac.h"
#include "../dma.h"
#include "mcu.h"
+#include "regd.h"
static const struct pci_device_id mt7921_pci_device_table[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_MEDIATEK, 0x7961),
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/regd.c b/drivers/net/wireless/mediatek/mt76/mt7921/regd.c
new file mode 100644
index 000000000000..6e6c81189222
--- /dev/null
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/regd.c
@@ -0,0 +1,104 @@
+/* SPDX-License-Identifier: ISC */
+/* Copyright (C) 2025 MediaTek Inc. */
+
+#include <linux/of_net.h>
+#include <linux/of.h>
+#include "mt7921.h"
+#include "regd.h"
+#include "mcu.h"
+
+static void
+mt7921_regd_channel_update(struct wiphy *wiphy, struct mt792x_dev *dev)
+{
+#define IS_UNII_INVALID(idx, sfreq, efreq) \
+ (!(dev->phy.clc_chan_conf & BIT(idx)) && (cfreq) >= (sfreq) && (cfreq) <= (efreq))
+ struct ieee80211_supported_band *sband;
+ struct mt76_dev *mdev = &dev->mt76;
+ struct device_node *np, *band_np;
+ struct ieee80211_channel *ch;
+ int i, cfreq;
+
+ np = mt76_find_power_limits_node(mdev);
+
+ sband = wiphy->bands[NL80211_BAND_5GHZ];
+ band_np = np ? of_get_child_by_name(np, "txpower-5g") : NULL;
+ for (i = 0; i < sband->n_channels; i++) {
+ ch = &sband->channels[i];
+ cfreq = ch->center_freq;
+
+ if (np && (!band_np || !mt76_find_channel_node(band_np, ch))) {
+ ch->flags |= IEEE80211_CHAN_DISABLED;
+ continue;
+ }
+
+ /* UNII-4 */
+ if (IS_UNII_INVALID(0, 5845, 5925))
+ ch->flags |= IEEE80211_CHAN_DISABLED;
+ }
+
+ sband = wiphy->bands[NL80211_BAND_6GHZ];
+ if (!sband)
+ return;
+
+ band_np = np ? of_get_child_by_name(np, "txpower-6g") : NULL;
+ for (i = 0; i < sband->n_channels; i++) {
+ ch = &sband->channels[i];
+ cfreq = ch->center_freq;
+
+ if (np && (!band_np || !mt76_find_channel_node(band_np, ch))) {
+ ch->flags |= IEEE80211_CHAN_DISABLED;
+ continue;
+ }
+
+ /* UNII-5/6/7/8 */
+ if (IS_UNII_INVALID(1, 5925, 6425) ||
+ IS_UNII_INVALID(2, 6425, 6525) ||
+ IS_UNII_INVALID(3, 6525, 6875) ||
+ IS_UNII_INVALID(4, 6875, 7125))
+ ch->flags |= IEEE80211_CHAN_DISABLED;
+ }
+}
+
+void mt7921_regd_update(struct mt792x_dev *dev)
+{
+ struct mt76_dev *mdev = &dev->mt76;
+ struct ieee80211_hw *hw = mdev->hw;
+ struct wiphy *wiphy = hw->wiphy;
+
+ mt7921_mcu_set_clc(dev, mdev->alpha2, dev->country_ie_env);
+ mt7921_regd_channel_update(wiphy, dev);
+ mt76_connac_mcu_set_channel_domain(hw->priv);
+ mt7921_set_tx_sar_pwr(hw, NULL);
+}
+EXPORT_SYMBOL_GPL(mt7921_regd_update);
+
+void mt7921_regd_notifier(struct wiphy *wiphy,
+ struct regulatory_request *request)
+{
+ struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
+ struct mt792x_dev *dev = mt792x_hw_dev(hw);
+ struct mt76_connac_pm *pm = &dev->pm;
+
+ memcpy(dev->mt76.alpha2, request->alpha2, sizeof(dev->mt76.alpha2));
+ dev->mt76.region = request->dfs_region;
+ dev->country_ie_env = request->country_ie_env;
+
+ if (request->initiator == NL80211_REGDOM_SET_BY_USER) {
+ if (dev->mt76.alpha2[0] == '0' && dev->mt76.alpha2[1] == '0')
+ wiphy->regulatory_flags &= ~REGULATORY_COUNTRY_IE_IGNORE;
+ else
+ wiphy->regulatory_flags |= REGULATORY_COUNTRY_IE_IGNORE;
+ }
+
+ if (pm->suspended)
+ return;
+
+ dev->regd_in_progress = true;
+
+ mt792x_mutex_acquire(dev);
+ mt7921_regd_update(dev);
+ mt792x_mutex_release(dev);
+
+ dev->regd_in_progress = false;
+ wake_up(&dev->wait);
+}
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/regd.h b/drivers/net/wireless/mediatek/mt76/mt7921/regd.h
new file mode 100644
index 000000000000..0ba6161e1919
--- /dev/null
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/regd.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: ISC */
+/* Copyright (C) 2025 MediaTek Inc. */
+
+#ifndef __MT7921_REGD_H
+#define __MT7921_REGD_H
+
+#include "mt7921.h"
+
+void mt7921_regd_update(struct mt792x_dev *dev);
+void mt7921_regd_notifier(struct wiphy *wiphy,
+ struct regulatory_request *request);
+
+#endif
--
2.45.2
^ permalink raw reply related
* [PATCH 4/5] wifi: mt76: mt7921: add auto regdomain switch support
From: JB Tsai @ 2026-02-23 7:38 UTC (permalink / raw)
To: nbd, lorenzo
Cc: linux-wireless, linux-mediatek, Deren.Wu, Sean.Wang, Quan.Zhou,
Ryder.Lee, Leon.Yen, litien.chang, jb.tsai
In-Reply-To: <20260223073854.2464232-1-jb.tsai@mediatek.com>
Implement 802.11d-based automatic regulatory domain switching to
dynamically determine the regulatory domain at runtime.
Signed-off-by: JB Tsai <jb.tsai@mediatek.com>
---
.../wireless/mediatek/mt76/mt76_connac_mcu.h | 3 +-
.../net/wireless/mediatek/mt76/mt7921/mac.c | 3 +
.../net/wireless/mediatek/mt76/mt7921/main.c | 12 ++-
.../net/wireless/mediatek/mt76/mt7921/mcu.c | 3 +-
.../net/wireless/mediatek/mt76/mt7921/regd.c | 81 +++++++++++++++++--
.../net/wireless/mediatek/mt76/mt7921/regd.h | 2 +
6 files changed, 93 insertions(+), 11 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
index f44977f9093d..263778be4a34 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
@@ -1586,7 +1586,7 @@ struct mt76_connac_hw_scan_done {
u8 pno_enabled;
u8 pad2[3];
u8 sparse_channel_valid_num;
- u8 pad3[3];
+ u8 alpha2[3];
u8 channel_num[MT76_CONNAC_SCAN_DONE_EVENT_MAX_CHANNEL_NUM];
/* idle format for channel_idle_time
* 0: first bytes: idle time(ms) 2nd byte: dwell time(ms)
@@ -1599,6 +1599,7 @@ struct mt76_connac_hw_scan_done {
u8 mdrdy_count[MT76_CONNAC_SCAN_DONE_EVENT_MAX_CHANNEL_NUM];
__le32 beacon_2g_num;
__le32 beacon_5g_num;
+ __le16 channel_scan_time[MT76_CONNAC_SCAN_DONE_EVENT_MAX_CHANNEL_NUM];
} __packed;
struct mt76_connac_sched_scan_req {
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mac.c b/drivers/net/wireless/mediatek/mt76/mt7921/mac.c
index 03b4960db73f..bcca4b17e8f2 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/mac.c
@@ -7,6 +7,7 @@
#include "mt7921.h"
#include "../dma.h"
#include "../mt76_connac2_mac.h"
+#include "regd.h"
#include "mcu.h"
#define MT_WTBL_TXRX_CAP_RATE_OFFSET 7
@@ -697,6 +698,8 @@ void mt7921_mac_reset_work(struct work_struct *work)
IEEE80211_IFACE_ITER_RESUME_ALL,
mt7921_vif_connect_iter, NULL);
mt76_connac_power_save_sched(&dev->mt76.phy, pm);
+
+ mt7921_regd_change(&dev->phy, "00");
}
void mt7921_coredump_work(struct work_struct *work)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/main.c b/drivers/net/wireless/mediatek/mt76/mt7921/main.c
index 00ca3d3f3ef0..dfe8cbd7dfa5 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/main.c
@@ -1027,8 +1027,16 @@ void mt7921_scan_work(struct work_struct *work)
rxd = (struct mt76_connac2_mcu_rxd *)skb->data;
if (rxd->eid == MCU_EVENT_SCHED_SCAN_DONE) {
ieee80211_sched_scan_results(phy->mt76->hw);
- } else if (test_and_clear_bit(MT76_HW_SCANNING,
- &phy->mt76->state)) {
+ } else if (rxd->eid == MCU_EVENT_SCAN_DONE) {
+ struct mt76_connac_hw_scan_done *event = NULL;
+
+ skb_pull(skb, sizeof(*rxd));
+ event = (struct mt76_connac_hw_scan_done *)skb->data;
+ mt7921_regd_change(phy, event->alpha2);
+ }
+
+ if (test_and_clear_bit(MT76_HW_SCANNING,
+ &phy->mt76->state)) {
struct cfg80211_scan_info info = {
.aborted = false,
};
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c
index 1e2afa736cdf..4e455aad2f4c 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c
@@ -487,7 +487,8 @@ static int mt7921_load_clc(struct mt792x_dev *dev, const char *fw_name)
goto out;
}
}
- ret = mt7921_mcu_set_clc(dev, "00", ENVIRON_INDOOR);
+
+ ret = mt7921_regd_init(phy);
out:
release_firmware(fw);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/regd.c b/drivers/net/wireless/mediatek/mt76/mt7921/regd.c
index f795ee2eb446..b53fa1f5c6db 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/regd.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/regd.c
@@ -111,26 +111,93 @@ int mt7921_mcu_regd_update(struct mt792x_dev *dev, u8 *alpha2,
EXPORT_SYMBOL_GPL(mt7921_mcu_regd_update);
void mt7921_regd_notifier(struct wiphy *wiphy,
- struct regulatory_request *request)
+ struct regulatory_request *req)
{
struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
struct mt792x_dev *dev = mt792x_hw_dev(hw);
struct mt76_connac_pm *pm = &dev->pm;
+ struct mt76_dev *mdev = &dev->mt76;
+
+ /* do not need to update the same country twice */
+ if (!memcmp(req->alpha2, mdev->alpha2, 2) &&
+ dev->country_ie_env == req->country_ie_env)
+ return;
- memcpy(dev->mt76.alpha2, request->alpha2, sizeof(dev->mt76.alpha2));
- dev->mt76.region = request->dfs_region;
- dev->country_ie_env = request->country_ie_env;
+ memcpy(mdev->alpha2, req->alpha2, 2);
+ mdev->region = req->dfs_region;
+ dev->country_ie_env = req->country_ie_env;
- if (request->initiator == NL80211_REGDOM_SET_BY_USER) {
+ if (req->initiator == NL80211_REGDOM_SET_BY_USER) {
if (dev->mt76.alpha2[0] == '0' && dev->mt76.alpha2[1] == '0')
wiphy->regulatory_flags &= ~REGULATORY_COUNTRY_IE_IGNORE;
else
wiphy->regulatory_flags |= REGULATORY_COUNTRY_IE_IGNORE;
}
+ dev->regd_change = true;
+
if (pm->suspended)
return;
- mt7921_mcu_regd_update(dev, request->alpha2,
- request->country_ie_env);
+ mt7921_mcu_regd_update(dev, req->alpha2,
+ req->country_ie_env);
+}
+
+static bool
+mt7921_regd_is_valid_alpha2(const char *alpha2)
+{
+ if (!alpha2)
+ return false;
+
+ if (alpha2[0] == '0' && alpha2[1] == '0')
+ return true;
+
+ if (isalpha(alpha2[0]) && isalpha(alpha2[1]))
+ return true;
+
+ return false;
+}
+
+int mt7921_regd_change(struct mt792x_phy *phy, char *alpha2)
+{
+ struct wiphy *wiphy = phy->mt76->hw->wiphy;
+ struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
+ struct mt792x_dev *dev = mt792x_hw_dev(hw);
+ struct mt76_dev *mdev = &dev->mt76;
+
+ if (dev->hw_full_reset)
+ return 0;
+
+ if (!mt7921_regd_is_valid_alpha2(alpha2) ||
+ !mt7921_regd_clc_supported(dev))
+ return -EINVAL;
+
+ if (mdev->alpha2[0] != '0' && mdev->alpha2[1] != '0')
+ return 0;
+
+ /* do not need to update the same country twice */
+ if (!memcmp(alpha2, mdev->alpha2, 2))
+ return 0;
+
+ if (phy->chip_cap & MT792x_CHIP_CAP_11D_EN)
+ return regulatory_hint(wiphy, alpha2);
+ else
+ return mt7921_mcu_set_clc(dev, alpha2, ENVIRON_INDOOR);
+}
+EXPORT_SYMBOL_GPL(mt7921_regd_change);
+
+int mt7921_regd_init(struct mt792x_phy *phy)
+{
+ struct wiphy *wiphy = phy->mt76->hw->wiphy;
+ struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy);
+ struct mt792x_dev *dev = mt792x_hw_dev(hw);
+ struct mt76_dev *mdev = &dev->mt76;
+
+ if (phy->chip_cap & MT792x_CHIP_CAP_11D_EN)
+ wiphy->regulatory_flags |= REGULATORY_COUNTRY_IE_IGNORE |
+ REGULATORY_DISABLE_BEACON_HINTS;
+ else
+ memzero_explicit(&mdev->alpha2, sizeof(mdev->alpha2));
+
+ return 0;
}
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/regd.h b/drivers/net/wireless/mediatek/mt76/mt7921/regd.h
index da5bd4450312..f125427192b6 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/regd.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/regd.h
@@ -11,5 +11,7 @@ int mt7921_mcu_regd_update(struct mt792x_dev *dev, u8 *alpha2,
void mt7921_regd_notifier(struct wiphy *wiphy,
struct regulatory_request *request);
bool mt7921_regd_clc_supported(struct mt792x_dev *dev);
+int mt7921_regd_change(struct mt792x_phy *phy, char *alpha2);
+int mt7921_regd_init(struct mt792x_phy *phy);
#endif
--
2.45.2
^ permalink raw reply related
* [PATCH 5/5] wifi: mt76: mt7921: disable auto regd changes after user set
From: JB Tsai @ 2026-02-23 7:38 UTC (permalink / raw)
To: nbd, lorenzo
Cc: linux-wireless, linux-mediatek, Deren.Wu, Sean.Wang, Quan.Zhou,
Ryder.Lee, Leon.Yen, litien.chang, jb.tsai
In-Reply-To: <20260223073854.2464232-1-jb.tsai@mediatek.com>
Add regd_user flag to block automatic regulatory domain updates
if set by user.
Signed-off-by: JB Tsai <jb.tsai@mediatek.com>
---
drivers/net/wireless/mediatek/mt76/mt7921/mcu.c | 1 +
drivers/net/wireless/mediatek/mt76/mt7921/regd.c | 7 ++++++-
2 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c
index 4e455aad2f4c..3806798081a2 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c
@@ -419,6 +419,7 @@ static int mt7921_load_clc(struct mt792x_dev *dev, const char *fw_name)
u8 *clc_base = NULL, hw_encap = 0;
dev->phy.clc_chan_conf = 0xff;
+ dev->regd_user = false;
if (!mt7921_regd_clc_supported(dev))
return 0;
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/regd.c b/drivers/net/wireless/mediatek/mt76/mt7921/regd.c
index b53fa1f5c6db..58d68d7cf1a1 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/regd.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/regd.c
@@ -118,6 +118,10 @@ void mt7921_regd_notifier(struct wiphy *wiphy,
struct mt76_connac_pm *pm = &dev->pm;
struct mt76_dev *mdev = &dev->mt76;
+ if (req->initiator == NL80211_REGDOM_SET_BY_USER &&
+ !dev->regd_user)
+ dev->regd_user = true;
+
/* do not need to update the same country twice */
if (!memcmp(req->alpha2, mdev->alpha2, 2) &&
dev->country_ie_env == req->country_ie_env)
@@ -169,7 +173,8 @@ int mt7921_regd_change(struct mt792x_phy *phy, char *alpha2)
return 0;
if (!mt7921_regd_is_valid_alpha2(alpha2) ||
- !mt7921_regd_clc_supported(dev))
+ !mt7921_regd_clc_supported(dev) ||
+ dev->regd_user)
return -EINVAL;
if (mdev->alpha2[0] != '0' && mdev->alpha2[1] != '0')
--
2.45.2
^ permalink raw reply related
* Re: [PATCH] wifi: rt2x00: use non of nvmem_cell_get
From: Johannes Berg @ 2026-02-23 8:27 UTC (permalink / raw)
To: Rosen Penev, linux-wireless; +Cc: Stanislaw Gruszka, open list
In-Reply-To: <20260220020908.40115-1-rosenp@gmail.com>
On Thu, 2026-02-19 at 18:09 -0800, Rosen Penev wrote:
> The library doesn't necessarily depend on OF. This codepath is used by
> both soc (OF only) and pci (no such requirement). After this, the only
> of specific function is of_get_mac_address, which is needed for nvmem.
That makes sense, but at least to me the subject doesn't - can you
please rewrite it?
johannes
^ permalink raw reply
* Re: [PATCH wireless-next v3] wifi: mac80211: Fix AAD/Nonce computation for management frames with MLO
From: Jouni Malinen @ 2026-02-23 9:17 UTC (permalink / raw)
To: Sai Pratyusha Magam; +Cc: johannes, linux-wireless, quic_drohan
In-Reply-To: <20260212042601.2250514-1-sai.magam@oss.qualcomm.com>
On Thu, Feb 12, 2026 at 09:56:01AM +0530, Sai Pratyusha Magam wrote:
> diff --git a/drivers/net/wireless/virtual/mac80211_hwsim.c b/drivers/net/wireless/virtual/mac80211_hwsim.c
> @@ -4189,6 +4225,7 @@ static int mac80211_hwsim_change_nan_config(struct ieee80211_hw *hw,
> .start_nan = mac80211_hwsim_start_nan, \
> .stop_nan = mac80211_hwsim_stop_nan, \
> .nan_change_conf = mac80211_hwsim_change_nan_config, \
> + .set_key = mac80211_hwsim_set_key, \
> HWSIM_DEBUGFS_OPS
Defining the set_key callback makes mac80211 disable various features
and not all of them were covered below:
> @@ -5621,6 +5658,7 @@ static int mac80211_hwsim_new_radio(struct genl_info *info,
> + hw->wiphy->flags |= WIPHY_FLAG_IBSS_RSN;
That's one of features that get disabled by default in mac80211 if a
driver defines ops->set_key. In addition to that, these ext_features
need to be enabled in mac80211_hwsim:
wiphy_ext_feature_set(wiphy,
NL80211_EXT_FEATURE_SPP_AMSDU_SUPPORT);
wiphy_ext_feature_set(local->hw.wiphy,
NL80211_EXT_FEATURE_CAN_REPLACE_PTK0);
wiphy_ext_feature_set(local->hw.wiphy,
NL80211_EXT_FEATURE_EXT_KEY_ID);
--
Jouni Malinen PGP id EFC895FA
^ permalink raw reply
* Re: [PATCH ath-next v3] wifi: ath12k: add basic hwmon temperature reporting
From: Nicolas Escande @ 2026-02-23 10:05 UTC (permalink / raw)
To: Maharaja Kennadyrajan, ath12k; +Cc: linux-wireless, Aishwarya R
In-Reply-To: <20260223060238.7038-1-maharaja.kennadyrajan@oss.qualcomm.com>
On Mon Feb 23, 2026 at 7:02 AM CET, Maharaja Kennadyrajan wrote:
[...]
> +static ssize_t ath12k_thermal_temp_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct ath12k *ar = dev_get_drvdata(dev);
> + unsigned long time_left;
> + int ret, temperature;
> +
> + guard(wiphy)(ath12k_ar_to_hw(ar)->wiphy);
> + guard(mutex)(&ar->thermal.lock);
Hello,
Is this mutex really needed ? It is used in conjuction with the wiphy_lock but
it's never used elsewhere. So even though there can be multiple ar for a single
wiphy, there is no code path that that would lock this ar->thermal.lock without
locking first the wiphy. And the wiphy lock already protect for reconfiguration
and recovery right ?
> +
> + if (ar->ah->state != ATH12K_HW_STATE_ON)
> + return -ENETDOWN;
> +
> + reinit_completion(&ar->thermal.wmi_sync);
> + ret = ath12k_wmi_send_pdev_temperature_cmd(ar);
> + if (ret) {
> + ath12k_warn(ar->ab, "failed to read temperature %d\n", ret);
> + return ret;
> + }
^ permalink raw reply
* Re: [PATCH] net: wireless: rsi: Don't default to -EOPNOTSUPP in rsi_mac80211_config
From: Johannes Berg @ 2026-02-23 10:42 UTC (permalink / raw)
To: Sebastian Krzyszkowiak; +Cc: kernel, linux-wireless, linux-kernel
In-Reply-To: <20260221-rsi-config-ret-v1-1-9a8f805e2f31@puri.sm>
Should have "wifi: rsi: ..." prefix
On Sat, 2026-02-21 at 17:28 +0100, Sebastian Krzyszkowiak wrote:
> This triggers a WARN_ON in ieee80211_hw_conf_init and isn't the expected
> behavior from the driver - other drivers default to 0 too.
This makes sense, but
> Fixes: 0a44dfc07074 ("wifi: mac80211: simplify non-chanctx drivers")
not sure this makes much sense? I guess it added the warning though.
> Signed-off-by: Sebastian Krzyszkowiak <sebastian.krzyszkowiak@puri.sm>
> ---
> drivers/net/wireless/rsi/rsi_91x_mac80211.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/drivers/net/wireless/rsi/rsi_91x_mac80211.c b/drivers/net/wireless/rsi/rsi_91x_mac80211.c
> index 8c8e074a3a70..c7ae8031436a 100644
> --- a/drivers/net/wireless/rsi/rsi_91x_mac80211.c
> +++ b/drivers/net/wireless/rsi/rsi_91x_mac80211.c
> @@ -668,7 +668,7 @@ static int rsi_mac80211_config(struct ieee80211_hw *hw,
> struct rsi_hw *adapter = hw->priv;
> struct rsi_common *common = adapter->priv;
> struct ieee80211_conf *conf = &hw->conf;
> - int status = -EOPNOTSUPP;
> + int status = 0;
I'll note that this just fixes a minor thing in this driver, if you want
to actually use it you probably also want to fix
- status is overwritten if multiple changes happen
- WIPHY_PARAM_RTS_THRESHOLD there max absolutely no sense at all
johannes
^ permalink raw reply
* Re: [PATCH ath-next v2] wifi: ath12k: add basic hwmon temperature reporting
From: Maharaja Kennadyrajan @ 2026-02-23 11:21 UTC (permalink / raw)
To: Nicolas Escande, ath12k; +Cc: linux-wireless, Aishwarya R
In-Reply-To: <DGITONL8L200.PJE38TDXYRQC@gmail.com>
On 2/19/2026 2:45 PM, Nicolas Escande wrote:
> On Thu Feb 19, 2026 at 8:34 AM CET, Maharaja Kennadyrajan wrote:
>> Add initial thermal support by wiring up a per-radio (pdev) hwmon temperature
>> sensor backed by the existing WMI pdev temperature command and event.
>> When userspace reads the sysfs file temp1_input, the driver sends
>> WMI_PDEV_GET_TEMPERATURE_CMDID (tag WMI_TAG_PDEV_GET_TEMPERATURE_CMD) and waits
>> for the corresponding WMI_PDEV_TEMPERATURE_EVENTID
>> (tag WMI_TAG_PDEV_TEMPERATURE_EVENT) to get the temperature and pdev_id.
>>
>> Export the reported value in millidegrees Celsius as required by hwmon.
>> The temperature reported is per-radio (pdev). In a multi-radio wiphy under a
>> single phy, a separate hwmon device is created for each radio.
>>
>> Sample command and output:
>> $ cat /sys/devices/pci0000:00/.../ieee80211/phyX/hwmonY/temp1_input
>> $ 50000
>>
> Hello,
>
> In ath10k & ath11k you guys also had the throtling feature. Do you guys plan to
> add this also at some point ?
Yes, we have a plan to add that throttling feature as well.
>
> [...]
>
>> --- a/drivers/net/wireless/ath/ath12k/Makefile
>> +++ b/drivers/net/wireless/ath/ath12k/Makefile
>> @@ -32,6 +32,7 @@ ath12k-$(CONFIG_ATH12K_TRACING) += trace.o
>> ath12k-$(CONFIG_PM) += wow.o
>> ath12k-$(CONFIG_ATH12K_COREDUMP) += coredump.o
>> ath12k-$(CONFIG_NL80211_TESTMODE) += testmode.o
>> +ath12k-$(CONFIG_THERMAL) += thermal.o
>>
> I may be wrong but I do not see Kconfig changes that allows to actually build
> the new files. Is this intended ?
>
>> # for tracing framework to find trace.h
>> CFLAGS_trace.o := -I$(src)
>> diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c
^ permalink raw reply
* Re: [PATCH ath-next v3] wifi: ath12k: add basic hwmon temperature reporting
From: Maharaja Kennadyrajan @ 2026-02-23 11:32 UTC (permalink / raw)
To: Nicolas Escande, ath12k; +Cc: linux-wireless, Aishwarya R
In-Reply-To: <DGM9946HTER7.2HAN0CPP3R2ZZ@gmail.com>
On 2/23/2026 3:35 PM, Nicolas Escande wrote:
> On Mon Feb 23, 2026 at 7:02 AM CET, Maharaja Kennadyrajan wrote:
> [...]
>> +static ssize_t ath12k_thermal_temp_show(struct device *dev,
>> + struct device_attribute *attr,
>> + char *buf)
>> +{
>> + struct ath12k *ar = dev_get_drvdata(dev);
>> + unsigned long time_left;
>> + int ret, temperature;
>> +
>> + guard(wiphy)(ath12k_ar_to_hw(ar)->wiphy);
>> + guard(mutex)(&ar->thermal.lock);
> Hello,
>
> Is this mutex really needed ? It is used in conjuction with the wiphy_lock but
> it's never used elsewhere. So even though there can be multiple ar for a single
> wiphy, there is no code path that that would lock this ar->thermal.lock without
> locking first the wiphy. And the wiphy lock already protect for reconfiguration
> and recovery right ?
Thanks for the review. ar->thermal.lock is redundant here since the path is already serialized by wiphy_lock .
I’ll drop the extra mutex and its init in the next version.
>
>> +
>> + if (ar->ah->state != ATH12K_HW_STATE_ON)
>> + return -ENETDOWN;
>> +
>> + reinit_completion(&ar->thermal.wmi_sync);
>> + ret = ath12k_wmi_send_pdev_temperature_cmd(ar);
>> + if (ret) {
>> + ath12k_warn(ar->ab, "failed to read temperature %d\n", ret);
>> + return ret;
>> + }
^ permalink raw reply
* [PATCH net] wifi: rtw88: properly drop usb interface reference on error
From: Greg Kroah-Hartman @ 2026-02-23 11:37 UTC (permalink / raw)
To: linux-wireless; +Cc: linux-kernel, Greg Kroah-Hartman, Ping-Ke Shih, stable
If an error happens in the usb probe path, in rtw_usb_intf_init(), the
usb interface reference needs to be properly dropped, otherwise is is
incorrectly increased when returning to the USB core.
Cc: Ping-Ke Shih <pkshih@realtek.com>
Cc: stable <stable@kernel.org>
Assisted-by: gkh_clanker_2000
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
drivers/net/wireless/realtek/rtw88/usb.c | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/drivers/net/wireless/realtek/rtw88/usb.c b/drivers/net/wireless/realtek/rtw88/usb.c
index 433b06c8d8a6..36ac20039ce2 100644
--- a/drivers/net/wireless/realtek/rtw88/usb.c
+++ b/drivers/net/wireless/realtek/rtw88/usb.c
@@ -1046,13 +1046,17 @@ static int rtw_usb_intf_init(struct rtw_dev *rtwdev,
rtwusb->udev = udev;
ret = rtw_usb_parse(rtwdev, intf);
- if (ret)
+ if (ret) {
+ usb_put_dev(udev);
return ret;
+ }
rtwusb->usb_data = kcalloc(RTW_USB_MAX_RXTX_COUNT, sizeof(u32),
GFP_KERNEL);
- if (!rtwusb->usb_data)
+ if (!rtwusb->usb_data) {
+ usb_put_dev(udev);
return -ENOMEM;
+ }
usb_set_intfdata(intf, rtwdev->hw);
--
2.53.0
^ permalink raw reply related
* Re: [PATCH wireless] wifi: mac80211: Fix ADDBA update when HW supports reordering
From: Johannes Berg @ 2026-02-23 11:50 UTC (permalink / raw)
To: Remi Pommarel; +Cc: linux-wireless, linux-kernel, Pablo MARTIN-GOMEZ
In-Reply-To: <aZsmk0lUpL0UBd6Q@pilgrim>
On Sun, 2026-02-22 at 17:06 +0100, Remi Pommarel wrote:
> > > That does make sense. However, if I understand correctly, it means that
> > > even if we end up storing the timeout for drivers that support
> > > reordering, a new IEEE80211_AMPDU_RX_UPDATE should still be introduced,
> > > right?
> >
> > I guess in order to do a no-op update that doesn't change the timeout,
> > we could? But not sure it's all worth it?
>
> I was going to say it would not be a no-op for a buf_size update but,
> if I understand correctly, even when SUPPORTS_REORDERING_BUFFER is not
> set the buf_size update is ignored completely and the reorder_buf is
> not resized yet a successful addba response is sent. Don't we need to
> check for buf_size change as well as timeout also?
I was going to say that I thought buf_size is not allowed to change, but
(re)reading the spec doesn't seem to bear that out.
I guess we could just unconditionally reject any updates. I'm not sure
now why we ever added the update in the first place. Perhaps some
implementation was doing no-op updates and failing on negative
responses?
> > Pablo seems to have looked up that it _is_ supported - which I didn't
> > expect because it's not part of the API contract, so the drivers
> > implemented something that can't actually ever get hit? Seems odd. And
> > I'm pretty sure e.g. iwlwifi wouldn't support it.
> >
> > But I basically also think it's not worth it in practice; why try to
> > support something that's never going to happen?
>
> Just to confirm, you mean that updating the timeout is not worth it, but
> fixing this issue is still needed right?
Right.
johannes
^ permalink raw reply
* Re: [RFC PATCH 4/8] wifi: mac80211: rework RX packet handling
From: Benjamin Berg @ 2026-02-23 12:35 UTC (permalink / raw)
To: Jeff Johnson, linux-wireless; +Cc: Rameshkumar Sundaram, Ramasamy Kaliappan
In-Reply-To: <66d57a5e-339b-40be-bad8-5348e70a6a35@oss.qualcomm.com>
On Fri, 2026-02-20 at 10:19 -0800, Jeff Johnson wrote:
> On 2/20/2026 6:19 AM, Benjamin Berg wrote:
> > @@ -5292,92 +5284,102 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw,
> > ieee80211_is_s1g_beacon(hdr->frame_control)))
> > ieee80211_scan_rx(local, skb);
> >
> > + /*
> > + * RX of a data frame should only happen to existing stations.
> > + * It is therefore more efficient to use the one provided by the driver
> > + * or search based on the station's address.
> > + *
> > + * Note we will fall through and handle a spurious data frame in the
>
> Note that checkpatch doesn't like the use of "fall through" in a comment since
> that term is usually associated with switch code where one case does some
> processing and then falls through to a subsequent case instead of having a
> break. Should we reword so that checkpatch doesn't complain?
I feel that "fall through" captures quite nicely what we do here and
there is really no need to change the wording just to make checkpatch
happy.
That said, I'll change the if/else that was also flagged.
Benjamin
> CHECK: Prefer 'fallthrough;' over fallthrough comment
> #5293: FILE: net/mac80211/rx.c:5293:
> + * Note we will fall through and handle a spurious data frame in the
>
>
> > + * same way as a management frame.
> > + */
> > if (ieee80211_is_data(fc)) {
> > - struct sta_info *sta, *prev_sta;
> > -
> > if (link_pubsta) {
> > - sta = container_of(link_pubsta->sta,
> > - struct sta_info, sta);
> > - if (!ieee80211_rx_data_set_sta(&rx, sta,
> > - link_pubsta->link_id))
> > - goto out;
> > + sta = container_of(link_pubsta->sta, struct sta_info,
> > + sta);
> > + link_sta = rcu_dereference(sta->link[link_pubsta->link_id]);
> >
> > - if (ieee80211_prepare_and_rx_handle(&rx, skb, true))
> > + rx.sdata = sta->sdata;
> > + if (ieee80211_rx_data_set_link_sta(&rx, link_sta) &&
> > + ieee80211_prepare_and_rx_handle(&rx, skb, true))
> > return;
> > +
> > goto out;
> > }
> >
> > - prev_sta = NULL;
> > + rx_data_pending = false;
> >
> > + /* Search for stations on non-MLD interfaces */
> > for_each_sta_info(local, hdr->addr2, sta, tmp) {
> > - int link_id;
> > + struct ieee80211_link_data *link;
> >
> > - if (!prev_sta) {
> > - prev_sta = sta;
> > + if (ieee80211_vif_is_mld(&sta->sdata->vif))
> > continue;
> > - }
> > -
> > - rx.sdata = prev_sta->sdata;
> > -
> > - /*
> > - * FIXME: This is not correct as the addr2 cannot be a
> > - * link address if the loop itself is iterated.
> > - */
> > - if (prev_sta->sta.mlo) {
> > - struct link_sta_info *link_sta;
> >
> > - link_sta = link_sta_info_get_bss(rx.sdata,
> > - hdr->addr2);
> > - if (!link_sta)
> > - continue;
> > + link = &sta->sdata->deflink;
> > + if (!ieee80211_rx_valid_freq(status->freq, link))
> > + continue;
> >
> > - link_id = link_sta->link_id;
> > - } else {
> > - link_id = sta->deflink.link_id;
> > + if (rx_data_pending) {
> > + ieee80211_prepare_and_rx_handle(&rx, skb,
> > + false);
> > + rx_data_pending = false;
> > }
> >
> > - if (!ieee80211_rx_data_set_sta(&rx, prev_sta, link_id))
> > - goto out;
> > -
> > - ieee80211_prepare_and_rx_handle(&rx, skb, false);
> > + rx.sdata = sta->sdata;
> > + if (!ieee80211_rx_data_set_link_sta(&rx, &sta->deflink))
> > + continue;
> >
> > - prev_sta = sta;
> > + rx_data_pending = true;
> > }
> >
> > - if (prev_sta) {
> > - int link_id;
> > -
> > - rx.sdata = prev_sta->sdata;
> > + /* And search stations on MLD interfaces */
> > + for_each_link_sta_info(local, hdr->addr2, link_sta, tmp) {
> > + struct ieee80211_link_data *link;
> >
> > - /*
> > - * FIXME: This is not correct as the addr2 cannot be a
> > - * link address if the loop itself is iterated.
> > - */
> > - if (prev_sta->sta.mlo) {
> > - struct link_sta_info *link_sta;
> > + sta = link_sta->sta;
> > + sdata = sta->sdata;
> > + link = rcu_dereference(sdata->link[link_sta->link_id]);
> >
> > - link_sta = link_sta_info_get_bss(rx.sdata,
> > - hdr->addr2);
> > - if (!link_sta)
> > - goto out;
> > + if (!ieee80211_rx_valid_freq(status->freq, link))
> > + continue;
> >
> > - link_id = link_sta->link_id;
> > - } else {
> > - link_id = sta->deflink.link_id;
> > + if (rx_data_pending) {
> > + ieee80211_prepare_and_rx_handle(&rx, skb,
> > + false);
> > + rx_data_pending = false;
> > }
> >
> > - if (!ieee80211_rx_data_set_sta(&rx, prev_sta, link_id))
> > - goto out;
> > + rx.sdata = sta->sdata;
> > + if (!ieee80211_rx_data_set_link_sta(&rx, link_sta))
> > + continue;
> > +
> > + rx_data_pending = true;
> > + }
> >
> > + if (rx_data_pending) {
> > if (ieee80211_prepare_and_rx_handle(&rx, skb, true))
> > return;
> > - goto out;
> > + else
> > + goto out;
>
> why change this?
> kernel convention is to avoid else after an if that ends in a change in flow.
>
> WARNING: else is not generally useful after a break or return
> #5363: FILE: net/mac80211/rx.c:5363:
> + return;
> + else
>
> > }
> > +
> > + /* fall through, e.g. for spurious frame notification */
>
> here again
>
> CHECK: Prefer 'fallthrough;' over fallthrough comment
> #5367: FILE: net/mac80211/rx.c:5367:
> + /* fall through, e.g. for spurious frame notification */
>
> > }
> >
> > - prev = NULL;
> > + /*
> > + * This is a management frame (or a data frame without a station) and
> > + * it will be delivered independent of whether a station exists,
> > + * so iterate the interfaces.
> > + */
> > + rx_data_pending = false;
> > +
> > + /* We expect the driver to provide frequency information */
> > + if (WARN_ON_ONCE(!local->emulate_chanctx && !status->freq))
> > + goto out;
> >
> > list_for_each_entry_rcu(sdata, &local->interfaces, list) {
> > + struct ieee80211_link_data *link = NULL;
> > +
> > if (!ieee80211_sdata_running(sdata))
> > continue;
> >
^ permalink raw reply
* [RFC PATCH v2 0/8] Adding NO_STA flag and reworking RX link resolution
From: Benjamin Berg @ 2026-02-23 12:38 UTC (permalink / raw)
To: linux-wireless; +Cc: Rameshkumar Sundaram, Ramasamy Kaliappan, Benjamin Berg
From: Benjamin Berg <benjamin.berg@intel.com>
Hi,
This patchset refactors the RX link resolution a bit to fix some issues
where mac80211 might accept frames on the wrong link and incorrectly
translate the address. It also adds a new NL80211_ATTR_FRAME_CMD_NO_STA
flag so that userspace can know whether address translation was done by
the kernel on RX and can also prevent address translation for management
frames during TX.
This together should be enough to fix the existing issues in hostapd
where stations that are still associated try to authenticate again but
hostapd for example ends up sending the frame to an old link address.
I would appreciate if you test the patches and work on the hostapd side.
Note that I have not properly verified the new nl80211 API, so it could
well be that I missed something.
Benjamin
Changes in RFCv2:
* Port other drivers to new API (untested)
* Fix a checkpatch warning
Benjamin Berg (8):
wifi: iwlwifi: use link_sta internally to the driver
wifi: mac80211: change public RX API to use link stations
wifi: mac80211: refactor RX link_id and station handling
wifi: mac80211: rework RX packet handling
wifi: cfg80211: add attribute for TX/RX denoting there is no station
wifi: mac80211: report to cfg80211 when no STA is known for a frame
wifi: mac80211: pass station to ieee80211_tx_skb_tid
wifi: mac80211: pass error station if non-STA transmit was requested
drivers/net/wireless/ath/ath11k/dp_rx.c | 2 +-
drivers/net/wireless/ath/ath12k/dp_mon.c | 18 +-
drivers/net/wireless/ath/ath12k/dp_rx.c | 15 +-
drivers/net/wireless/intel/iwlwifi/mld/agg.c | 21 +-
drivers/net/wireless/intel/iwlwifi/mld/agg.h | 4 +-
drivers/net/wireless/intel/iwlwifi/mld/rx.c | 50 +-
drivers/net/wireless/intel/iwlwifi/mld/rx.h | 2 +-
.../wireless/intel/iwlwifi/mld/tests/agg.c | 7 +-
drivers/net/wireless/intel/iwlwifi/mvm/rx.c | 2 +-
drivers/net/wireless/intel/iwlwifi/mvm/rxmq.c | 6 +-
drivers/net/wireless/mediatek/mt76/mac80211.c | 22 +-
drivers/net/wireless/realtek/rtw89/core.c | 6 -
drivers/net/wireless/virtual/mac80211_hwsim.c | 3 -
include/net/cfg80211.h | 4 +
include/net/mac80211.h | 25 +-
include/uapi/linux/nl80211.h | 7 +
net/mac80211/agg-tx.c | 6 +-
net/mac80211/eht.c | 3 -
net/mac80211/ht.c | 4 +-
net/mac80211/ieee80211_i.h | 14 +-
net/mac80211/iface.c | 7 +-
net/mac80211/mlme.c | 9 +-
net/mac80211/offchannel.c | 13 +-
net/mac80211/rx.c | 436 ++++++++++--------
net/mac80211/scan.c | 10 +-
net/mac80211/tdls.c | 4 +-
net/mac80211/tx.c | 8 +-
net/wireless/nl80211.c | 8 +-
28 files changed, 390 insertions(+), 326 deletions(-)
--
2.53.0
^ permalink raw reply
* [RFC PATCH v2 1/8] wifi: iwlwifi: use link_sta internally to the driver
From: Benjamin Berg @ 2026-02-23 12:38 UTC (permalink / raw)
To: linux-wireless; +Cc: Rameshkumar Sundaram, Ramasamy Kaliappan, Benjamin Berg
In-Reply-To: <20260223123818.384184-10-benjamin@sipsolutions.net>
From: Benjamin Berg <benjamin.berg@intel.com>
Using the link_sta is a natural way to pass both the STA and the link ID
information at the same time. Use that internally to the driver in
preparation to mac80211 changing its API and adopting the same method.
Signed-off-by: Benjamin Berg <benjamin.berg@intel.com>
---
drivers/net/wireless/intel/iwlwifi/mld/agg.c | 21 +++++----
drivers/net/wireless/intel/iwlwifi/mld/agg.h | 4 +-
drivers/net/wireless/intel/iwlwifi/mld/rx.c | 45 ++++++++++---------
drivers/net/wireless/intel/iwlwifi/mld/rx.h | 2 +-
.../wireless/intel/iwlwifi/mld/tests/agg.c | 7 +--
5 files changed, 42 insertions(+), 37 deletions(-)
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/agg.c b/drivers/net/wireless/intel/iwlwifi/mld/agg.c
index 3bf36f8f6874..c4f3552bd2ab 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/agg.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/agg.c
@@ -1,13 +1,14 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
- * Copyright (C) 2024-2025 Intel Corporation
+ * Copyright (C) 2024-2026 Intel Corporation
*/
#include "agg.h"
#include "sta.h"
#include "hcmd.h"
static void
-iwl_mld_reorder_release_frames(struct iwl_mld *mld, struct ieee80211_sta *sta,
+iwl_mld_reorder_release_frames(struct iwl_mld *mld,
+ struct ieee80211_link_sta *link_sta,
struct napi_struct *napi,
struct iwl_mld_baid_data *baid_data,
struct iwl_mld_reorder_buffer *reorder_buf,
@@ -32,7 +33,7 @@ iwl_mld_reorder_release_frames(struct iwl_mld *mld, struct ieee80211_sta *sta,
while ((skb = __skb_dequeue(skb_list))) {
iwl_mld_pass_packet_to_mac80211(mld, napi, skb,
reorder_buf->queue,
- sta);
+ link_sta);
reorder_buf->num_stored--;
}
}
@@ -71,7 +72,7 @@ static void iwl_mld_release_frames_from_notif(struct iwl_mld *mld,
reorder_buf = &ba_data->reorder_buf[queue];
- iwl_mld_reorder_release_frames(mld, link_sta->sta, napi, ba_data,
+ iwl_mld_reorder_release_frames(mld, link_sta, napi, ba_data,
reorder_buf, nssn);
out_unlock:
rcu_read_unlock();
@@ -174,7 +175,7 @@ void iwl_mld_del_ba(struct iwl_mld *mld, int queue,
reorder_buf = &ba_data->reorder_buf[queue];
/* release all frames that are in the reorder buffer to the stack */
- iwl_mld_reorder_release_frames(mld, link_sta->sta, NULL,
+ iwl_mld_reorder_release_frames(mld, link_sta, NULL,
ba_data, reorder_buf,
ieee80211_sn_add(reorder_buf->head_sn,
ba_data->buf_size));
@@ -187,14 +188,14 @@ void iwl_mld_del_ba(struct iwl_mld *mld, int queue,
*/
enum iwl_mld_reorder_result
iwl_mld_reorder(struct iwl_mld *mld, struct napi_struct *napi,
- int queue, struct ieee80211_sta *sta,
+ int queue, struct ieee80211_link_sta *link_sta,
struct sk_buff *skb, struct iwl_rx_mpdu_desc *desc)
{
struct ieee80211_hdr *hdr = (void *)skb_mac_header(skb);
struct iwl_mld_baid_data *baid_data;
struct iwl_mld_reorder_buffer *buffer;
struct iwl_mld_reorder_buf_entry *entries;
- struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta);
+ struct iwl_mld_sta *mld_sta;
struct iwl_mld_link_sta *mld_link_sta;
u32 reorder = le32_to_cpu(desc->reorder_data);
bool amsdu, last_subframe, is_old_sn, is_dup;
@@ -217,10 +218,12 @@ iwl_mld_reorder(struct iwl_mld *mld, struct napi_struct *napi,
return IWL_MLD_PASS_SKB;
/* no sta yet */
- if (WARN_ONCE(!sta,
+ if (WARN_ONCE(!link_sta,
"Got valid BAID without a valid station assigned\n"))
return IWL_MLD_PASS_SKB;
+ mld_sta = iwl_mld_sta_from_mac80211(link_sta->sta);
+
/* not a data packet */
if (!ieee80211_is_data_qos(hdr->frame_control) ||
is_multicast_ether_addr(hdr->addr1))
@@ -310,7 +313,7 @@ iwl_mld_reorder(struct iwl_mld *mld, struct napi_struct *napi,
* will be released when the frame release notification arrives.
*/
if (!amsdu || last_subframe)
- iwl_mld_reorder_release_frames(mld, sta, napi, baid_data,
+ iwl_mld_reorder_release_frames(mld, link_sta, napi, baid_data,
buffer, nssn);
else if (buffer->num_stored == 1)
buffer->head_sn = nssn;
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/agg.h b/drivers/net/wireless/intel/iwlwifi/mld/agg.h
index 651c80d1c7cd..c6cd5fa219be 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/agg.h
+++ b/drivers/net/wireless/intel/iwlwifi/mld/agg.h
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
/*
- * Copyright (C) 2024 Intel Corporation
+ * Copyright (C) 2024, 2026 Intel Corporation
*/
#ifndef __iwl_agg_h__
#define __iwl_agg_h__
@@ -106,7 +106,7 @@ int iwl_mld_ampdu_rx_stop(struct iwl_mld *mld, struct ieee80211_sta *sta,
enum iwl_mld_reorder_result
iwl_mld_reorder(struct iwl_mld *mld, struct napi_struct *napi,
- int queue, struct ieee80211_sta *sta,
+ int queue, struct ieee80211_link_sta *link_sta,
struct sk_buff *skb, struct iwl_rx_mpdu_desc *desc);
void iwl_mld_handle_frame_release_notif(struct iwl_mld *mld,
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/rx.c b/drivers/net/wireless/intel/iwlwifi/mld/rx.c
index 214dcfde2fb4..de2feeb74009 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/rx.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/rx.c
@@ -48,7 +48,8 @@ iwl_mld_fill_phy_data_from_mpdu(struct iwl_mld *mld,
}
static inline int iwl_mld_check_pn(struct iwl_mld *mld, struct sk_buff *skb,
- int queue, struct ieee80211_sta *sta)
+ int queue,
+ struct ieee80211_link_sta *link_sta)
{
struct ieee80211_hdr *hdr = (void *)skb_mac_header(skb);
struct ieee80211_rx_status *stats = IEEE80211_SKB_RXCB(skb);
@@ -72,13 +73,13 @@ static inline int iwl_mld_check_pn(struct iwl_mld *mld, struct sk_buff *skb,
return 0;
/* if we are here - this for sure is either CCMP or GCMP */
- if (!sta) {
+ if (!link_sta) {
IWL_DEBUG_DROP(mld,
"expected hw-decrypted unicast frame for station\n");
return -1;
}
- mld_sta = iwl_mld_sta_from_mac80211(sta);
+ mld_sta = iwl_mld_sta_from_mac80211(link_sta->sta);
extiv = (u8 *)hdr + ieee80211_hdrlen(hdr->frame_control);
keyidx = extiv[3] >> 6;
@@ -120,17 +121,17 @@ static inline int iwl_mld_check_pn(struct iwl_mld *mld, struct sk_buff *skb,
void iwl_mld_pass_packet_to_mac80211(struct iwl_mld *mld,
struct napi_struct *napi,
struct sk_buff *skb, int queue,
- struct ieee80211_sta *sta)
+ struct ieee80211_link_sta *link_sta)
{
KUNIT_STATIC_STUB_REDIRECT(iwl_mld_pass_packet_to_mac80211,
- mld, napi, skb, queue, sta);
+ mld, napi, skb, queue, link_sta);
- if (unlikely(iwl_mld_check_pn(mld, skb, queue, sta))) {
+ if (unlikely(iwl_mld_check_pn(mld, skb, queue, link_sta))) {
kfree_skb(skb);
return;
}
- ieee80211_rx_napi(mld->hw, sta, skb, napi);
+ ieee80211_rx_napi(mld->hw, link_sta->sta, skb, napi);
}
EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(iwl_mld_pass_packet_to_mac80211);
@@ -1728,7 +1729,7 @@ static void iwl_mld_update_last_rx_timestamp(struct iwl_mld *mld, u8 baid)
* Sets *drop to true if the packet should be dropped.
* Returns the station if found, or NULL otherwise.
*/
-static struct ieee80211_sta *
+static struct ieee80211_link_sta *
iwl_mld_rx_with_sta(struct iwl_mld *mld, struct ieee80211_hdr *hdr,
struct sk_buff *skb,
const struct iwl_rx_mpdu_desc *mpdu_desc,
@@ -1803,10 +1804,10 @@ iwl_mld_rx_with_sta(struct iwl_mld *mld, struct ieee80211_hdr *hdr,
queue);
}
- return sta;
+ return link_sta;
}
-static int iwl_mld_rx_mgmt_prot(struct ieee80211_sta *sta,
+static int iwl_mld_rx_mgmt_prot(struct ieee80211_link_sta *link_sta,
struct ieee80211_hdr *hdr,
struct ieee80211_rx_status *rx_status,
u32 mpdu_status,
@@ -1820,7 +1821,6 @@ static int iwl_mld_rx_mgmt_prot(struct ieee80211_sta *sta,
struct ieee80211_key_conf *key;
const u8 *frame = (void *)hdr;
const u8 *mmie;
- u8 link_id;
if ((mpdu_status & IWL_RX_MPDU_STATUS_SEC_MASK) ==
IWL_RX_MPDU_STATUS_SEC_NONE)
@@ -1836,10 +1836,10 @@ static int iwl_mld_rx_mgmt_prot(struct ieee80211_sta *sta,
if (!ieee80211_is_beacon(hdr->frame_control))
return 0;
- if (!sta)
+ if (!link_sta)
return -1;
- mld_sta = iwl_mld_sta_from_mac80211(sta);
+ mld_sta = iwl_mld_sta_from_mac80211(link_sta->sta);
mld_vif = iwl_mld_vif_from_mac80211(mld_sta->vif);
/* key mismatch - will also report !MIC_OK but we shouldn't count it */
@@ -1853,8 +1853,7 @@ static int iwl_mld_rx_mgmt_prot(struct ieee80211_sta *sta,
return 0;
}
- link_id = rx_status->link_valid ? rx_status->link_id : 0;
- link = rcu_dereference(mld_vif->link[link_id]);
+ link = rcu_dereference(mld_vif->link[link_sta->link_id]);
if (WARN_ON_ONCE(!link))
return -1;
@@ -1905,7 +1904,7 @@ static int iwl_mld_rx_mgmt_prot(struct ieee80211_sta *sta,
}
static int iwl_mld_rx_crypto(struct iwl_mld *mld,
- struct ieee80211_sta *sta,
+ struct ieee80211_link_sta *link_sta,
struct ieee80211_hdr *hdr,
struct ieee80211_rx_status *rx_status,
struct iwl_rx_mpdu_desc *desc, int queue,
@@ -1915,7 +1914,7 @@ static int iwl_mld_rx_crypto(struct iwl_mld *mld,
if (unlikely(ieee80211_is_mgmt(hdr->frame_control) &&
!ieee80211_has_protected(hdr->frame_control)))
- return iwl_mld_rx_mgmt_prot(sta, hdr, rx_status, status,
+ return iwl_mld_rx_mgmt_prot(link_sta, hdr, rx_status, status,
le16_to_cpu(desc->mpdu_len));
if (!ieee80211_has_protected(hdr->frame_control) ||
@@ -2023,7 +2022,7 @@ void iwl_mld_rx_mpdu(struct iwl_mld *mld, struct napi_struct *napi,
struct iwl_rx_packet *pkt = rxb_addr(rxb);
struct iwl_mld_rx_phy_data phy_data = {};
struct iwl_rx_mpdu_desc *mpdu_desc = (void *)pkt->data;
- struct ieee80211_sta *sta;
+ struct ieee80211_link_sta *link_sta;
struct ieee80211_hdr *hdr;
struct sk_buff *skb;
size_t mpdu_desc_size = sizeof(*mpdu_desc);
@@ -2086,7 +2085,8 @@ void iwl_mld_rx_mpdu(struct iwl_mld *mld, struct napi_struct *napi,
rcu_read_lock();
- sta = iwl_mld_rx_with_sta(mld, hdr, skb, mpdu_desc, pkt, queue, &drop);
+ link_sta = iwl_mld_rx_with_sta(mld, hdr, skb, mpdu_desc, pkt, queue,
+ &drop);
if (drop)
goto drop;
@@ -2127,7 +2127,7 @@ void iwl_mld_rx_mpdu(struct iwl_mld *mld, struct napi_struct *napi,
iwl_mld_rx_fill_status(mld, link_id, hdr, skb, &phy_data);
- if (iwl_mld_rx_crypto(mld, sta, hdr, rx_status, mpdu_desc, queue,
+ if (iwl_mld_rx_crypto(mld, link_sta, hdr, rx_status, mpdu_desc, queue,
le32_to_cpu(pkt->len_n_flags), &crypto_len))
goto drop;
@@ -2140,7 +2140,8 @@ void iwl_mld_rx_mpdu(struct iwl_mld *mld, struct napi_struct *napi,
if (iwl_mld_time_sync_frame(mld, skb, hdr->addr2))
goto out;
- reorder_res = iwl_mld_reorder(mld, napi, queue, sta, skb, mpdu_desc);
+ reorder_res = iwl_mld_reorder(mld, napi, queue, link_sta, skb,
+ mpdu_desc);
switch (reorder_res) {
case IWL_MLD_PASS_SKB:
break;
@@ -2153,7 +2154,7 @@ void iwl_mld_rx_mpdu(struct iwl_mld *mld, struct napi_struct *napi,
goto drop;
}
- iwl_mld_pass_packet_to_mac80211(mld, napi, skb, queue, sta);
+ iwl_mld_pass_packet_to_mac80211(mld, napi, skb, queue, link_sta);
goto out;
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/rx.h b/drivers/net/wireless/intel/iwlwifi/mld/rx.h
index 09dddbd40f55..a54f1a6146ee 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/rx.h
+++ b/drivers/net/wireless/intel/iwlwifi/mld/rx.h
@@ -64,7 +64,7 @@ void iwl_mld_handle_rx_queues_sync_notif(struct iwl_mld *mld,
void iwl_mld_pass_packet_to_mac80211(struct iwl_mld *mld,
struct napi_struct *napi,
struct sk_buff *skb, int queue,
- struct ieee80211_sta *sta);
+ struct ieee80211_link_sta *link_sta);
void iwl_mld_handle_phy_air_sniffer_notif(struct iwl_mld *mld,
struct napi_struct *napi,
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/tests/agg.c b/drivers/net/wireless/intel/iwlwifi/mld/tests/agg.c
index 29b0248cec3d..e9efe2996f07 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/tests/agg.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/tests/agg.c
@@ -2,7 +2,7 @@
/*
* KUnit tests for channel helper functions
*
- * Copyright (C) 2024-2025 Intel Corporation
+ * Copyright (C) 2024-2026 Intel Corporation
*/
#include <kunit/test.h>
#include <kunit/static_stub.h>
@@ -441,7 +441,7 @@ static void
fake_iwl_mld_pass_packet_to_mac80211(struct iwl_mld *mld,
struct napi_struct *napi,
struct sk_buff *skb, int queue,
- struct ieee80211_sta *sta)
+ struct ieee80211_link_sta *link_sta)
{
__skb_queue_tail(&g_released_skbs, skb);
g_num_released_skbs++;
@@ -630,7 +630,8 @@ static void test_reorder_buffer(struct kunit *test)
mpdu_desc = setup_mpdu_desc();
rcu_read_lock();
- reorder_res = iwl_mld_reorder(mld, NULL, QUEUE, sta, skb, mpdu_desc);
+ reorder_res = iwl_mld_reorder(mld, NULL, QUEUE, &sta->deflink, skb,
+ mpdu_desc);
rcu_read_unlock();
KUNIT_ASSERT_EQ(test, reorder_res, param->expected.reorder_res);
--
2.53.0
^ permalink raw reply related
* [RFC PATCH v2 2/8] wifi: mac80211: change public RX API to use link stations
From: Benjamin Berg @ 2026-02-23 12:38 UTC (permalink / raw)
To: linux-wireless; +Cc: Rameshkumar Sundaram, Ramasamy Kaliappan, Benjamin Berg
In-Reply-To: <20260223123818.384184-10-benjamin@sipsolutions.net>
From: Benjamin Berg <benjamin.berg@intel.com>
If a station is passed then the link ID also needs to be known. As such,
it is a more natural API to simply pass the link station directly rather
than pushing the link information into the RX status.
Furthermore, having the link ID in the RX status is not actually correct
because the link IDs are VIF specific and there may be multiple VIFs. In
the case of a station this relationship is clear, but then one may as
well use the link station.
This patch only changes the API and emulates the old (incorrect)
behaviour for now. The mac80211 RX code will be updated in later
patches.
Signed-off-by: Benjamin Berg <benjamin.berg@intel.com>
---
drivers/net/wireless/ath/ath11k/dp_rx.c | 2 +-
drivers/net/wireless/ath/ath12k/dp_mon.c | 18 +++++++--------
drivers/net/wireless/ath/ath12k/dp_rx.c | 15 +++++++++----
drivers/net/wireless/intel/iwlwifi/mld/rx.c | 7 +-----
drivers/net/wireless/intel/iwlwifi/mvm/rx.c | 2 +-
drivers/net/wireless/intel/iwlwifi/mvm/rxmq.c | 6 ++---
drivers/net/wireless/mediatek/mt76/mac80211.c | 22 +++++++++++--------
drivers/net/wireless/realtek/rtw89/core.c | 6 -----
drivers/net/wireless/virtual/mac80211_hwsim.c | 3 ---
include/net/mac80211.h | 16 ++++++++++----
net/mac80211/rx.c | 20 ++++++++++++-----
11 files changed, 65 insertions(+), 52 deletions(-)
diff --git a/drivers/net/wireless/ath/ath11k/dp_rx.c b/drivers/net/wireless/ath/ath11k/dp_rx.c
index b9e976ddcbbf..911e40178234 100644
--- a/drivers/net/wireless/ath/ath11k/dp_rx.c
+++ b/drivers/net/wireless/ath/ath11k/dp_rx.c
@@ -2500,7 +2500,7 @@ static void ath11k_dp_rx_deliver_msdu(struct ath11k *ar, struct napi_struct *nap
!(is_mcbc && rx_status->flag & RX_FLAG_DECRYPTED))
rx_status->flag |= RX_FLAG_8023;
- ieee80211_rx_napi(ar->hw, pubsta, msdu, napi);
+ ieee80211_rx_napi(ar->hw, &pubsta->deflink, msdu, napi);
}
static int ath11k_dp_rx_process_msdu(struct ath11k *ar,
diff --git a/drivers/net/wireless/ath/ath12k/dp_mon.c b/drivers/net/wireless/ath/ath12k/dp_mon.c
index 737287a9aa46..71f14f2e15bd 100644
--- a/drivers/net/wireless/ath/ath12k/dp_mon.c
+++ b/drivers/net/wireless/ath/ath12k/dp_mon.c
@@ -508,7 +508,7 @@ void ath12k_dp_mon_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev,
};
struct ieee80211_rx_status *rx_status;
struct ieee80211_radiotap_he *he = NULL;
- struct ieee80211_sta *pubsta = NULL;
+ struct ieee80211_link_sta *link_pubsta = NULL;
struct ath12k_dp_link_peer *peer;
struct ath12k_skb_rxcb *rxcb = ATH12K_SKB_RXCB(msdu);
struct hal_rx_desc_data rx_info;
@@ -517,8 +517,6 @@ void ath12k_dp_mon_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev,
struct hal_rx_desc *rx_desc = (struct hal_rx_desc *)msdu->data;
u8 addr[ETH_ALEN] = {};
- status->link_valid = 0;
-
if ((status->encoding == RX_ENC_HE) && !(status->flag & RX_FLAG_RADIOTAP_HE) &&
!(status->flag & RX_FLAG_SKIP_MONITOR)) {
he = skb_push(msdu, sizeof(known));
@@ -532,12 +530,11 @@ void ath12k_dp_mon_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev,
spin_lock_bh(&dp->dp_lock);
peer = ath12k_dp_rx_h_find_link_peer(dp_pdev, msdu, &rx_info);
if (peer && peer->sta) {
- pubsta = peer->sta;
- memcpy(addr, peer->addr, ETH_ALEN);
- if (pubsta->valid_links) {
- status->link_valid = 1;
- status->link_id = peer->link_id;
- }
+ if (peer->sta->valid_links)
+ link_pubsta =
+ rcu_dereference(peer->sta->link[peer->link_id]);
+ else
+ link_pubsta = &peer->sta->deflink;
}
spin_unlock_bh(&dp->dp_lock);
@@ -583,7 +580,8 @@ void ath12k_dp_mon_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev,
!(is_mcbc && rx_status->flag & RX_FLAG_DECRYPTED))
rx_status->flag |= RX_FLAG_8023;
- ieee80211_rx_napi(ath12k_pdev_dp_to_hw(dp_pdev), pubsta, msdu, napi);
+ ieee80211_rx_napi(ath12k_pdev_dp_to_hw(dp_pdev), link_pubsta, msdu,
+ napi);
}
EXPORT_SYMBOL(ath12k_dp_mon_rx_deliver_msdu);
diff --git a/drivers/net/wireless/ath/ath12k/dp_rx.c b/drivers/net/wireless/ath/ath12k/dp_rx.c
index a32ee9f8061a..49b161c5a878 100644
--- a/drivers/net/wireless/ath/ath12k/dp_rx.c
+++ b/drivers/net/wireless/ath/ath12k/dp_rx.c
@@ -1329,6 +1329,7 @@ void ath12k_dp_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev, struct napi_struc
struct ath12k_dp *dp = dp_pdev->dp;
struct ieee80211_rx_status *rx_status;
struct ieee80211_sta *pubsta;
+ struct ieee80211_link_sta *link_pubsta = NULL;
struct ath12k_dp_peer *peer;
struct ath12k_skb_rxcb *rxcb = ATH12K_SKB_RXCB(msdu);
struct ieee80211_rx_status *status = rx_info->rx_status;
@@ -1340,9 +1341,14 @@ void ath12k_dp_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev, struct napi_struc
pubsta = peer ? peer->sta : NULL;
- if (pubsta && pubsta->valid_links) {
- status->link_valid = 1;
- status->link_id = peer->hw_links[rxcb->hw_link_id];
+ if (pubsta) {
+ if (pubsta->valid_links) {
+ u8 link_id = peer->hw_links[rxcb->hw_link_id];
+ link_pubsta =
+ rcu_dereference(pubsta->link[link_id]);
+ } else {
+ link_pubsta = &pubsta->deflink;
+ }
}
ath12k_dbg(dp->ab, ATH12K_DBG_DATA,
@@ -1388,7 +1394,8 @@ void ath12k_dp_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev, struct napi_struc
!(is_mcbc && rx_status->flag & RX_FLAG_DECRYPTED))
rx_status->flag |= RX_FLAG_8023;
- ieee80211_rx_napi(ath12k_pdev_dp_to_hw(dp_pdev), pubsta, msdu, napi);
+ ieee80211_rx_napi(ath12k_pdev_dp_to_hw(dp_pdev), link_pubsta, msdu,
+ napi);
}
EXPORT_SYMBOL(ath12k_dp_rx_deliver_msdu);
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/rx.c b/drivers/net/wireless/intel/iwlwifi/mld/rx.c
index de2feeb74009..2a12ae412bfd 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/rx.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/rx.c
@@ -131,7 +131,7 @@ void iwl_mld_pass_packet_to_mac80211(struct iwl_mld *mld,
return;
}
- ieee80211_rx_napi(mld->hw, link_sta->sta, skb, napi);
+ ieee80211_rx_napi(mld->hw, link_sta, skb, napi);
}
EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(iwl_mld_pass_packet_to_mac80211);
@@ -1765,11 +1765,6 @@ iwl_mld_rx_with_sta(struct iwl_mld *mld, struct ieee80211_hdr *hdr,
rx_status = IEEE80211_SKB_RXCB(skb);
- if (link_sta && sta->valid_links) {
- rx_status->link_valid = true;
- rx_status->link_id = link_sta->link_id;
- }
-
/* fill checksum */
if (ieee80211_is_data(hdr->frame_control) &&
pkt->len_n_flags & cpu_to_le32(FH_RSCSR_RPA_EN)) {
diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/rx.c b/drivers/net/wireless/intel/iwlwifi/mvm/rx.c
index d0c0faae0122..a83bede06487 100644
--- a/drivers/net/wireless/intel/iwlwifi/mvm/rx.c
+++ b/drivers/net/wireless/intel/iwlwifi/mvm/rx.c
@@ -90,7 +90,7 @@ static void iwl_mvm_pass_packet_to_mac80211(struct iwl_mvm *mvm,
fraglen, rxb->truesize);
}
- ieee80211_rx_napi(mvm->hw, sta, skb, napi);
+ ieee80211_rx_napi(mvm->hw, &sta->deflink, skb, napi);
}
/*
diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/rxmq.c b/drivers/net/wireless/intel/iwlwifi/mvm/rxmq.c
index 7f0b4f5daa21..fe5a2d0a798b 100644
--- a/drivers/net/wireless/intel/iwlwifi/mvm/rxmq.c
+++ b/drivers/net/wireless/intel/iwlwifi/mvm/rxmq.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
- * Copyright (C) 2012-2014, 2018-2025 Intel Corporation
+ * Copyright (C) 2012-2014, 2018-2026 Intel Corporation
* Copyright (C) 2013-2015 Intel Mobile Communications GmbH
* Copyright (C) 2015-2017 Intel Deutschland GmbH
*/
@@ -243,7 +243,7 @@ static void iwl_mvm_pass_packet_to_mac80211(struct iwl_mvm *mvm,
return;
}
- ieee80211_rx_napi(mvm->hw, sta, skb, napi);
+ ieee80211_rx_napi(mvm->hw, &sta->deflink, skb, napi);
}
static bool iwl_mvm_used_average_energy(struct iwl_mvm *mvm,
@@ -2528,7 +2528,7 @@ void iwl_mvm_rx_monitor_no_data(struct iwl_mvm *mvm, struct napi_struct *napi,
}
rcu_read_lock();
- ieee80211_rx_napi(mvm->hw, sta, skb, napi);
+ ieee80211_rx_napi(mvm->hw, &sta->deflink, skb, napi);
rcu_read_unlock();
}
diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c
index 75772979f438..a0b887c83591 100644
--- a/drivers/net/wireless/mediatek/mt76/mac80211.c
+++ b/drivers/net/wireless/mediatek/mt76/mac80211.c
@@ -1232,7 +1232,7 @@ EXPORT_SYMBOL(mt76_rx_signal);
static void
mt76_rx_convert(struct mt76_dev *dev, struct sk_buff *skb,
struct ieee80211_hw **hw,
- struct ieee80211_sta **sta)
+ struct ieee80211_link_sta **link_sta)
{
struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
struct ieee80211_hdr *hdr = mt76_skb_get_hdr(skb);
@@ -1279,11 +1279,15 @@ mt76_rx_convert(struct mt76_dev *dev, struct sk_buff *skb,
sizeof(mstat.chain_signal));
if (mstat.wcid) {
- status->link_valid = mstat.wcid->link_valid;
- status->link_id = mstat.wcid->link_id;
+ struct ieee80211_sta *sta = wcid_to_sta(mstat.wcid);
+
+ if (mstat.wcid->link_valid)
+ *link_sta =
+ rcu_dereference(sta->link[mstat.wcid->link_id]);
+ else
+ *link_sta = &sta->deflink;
}
- *sta = wcid_to_sta(mstat.wcid);
*hw = mt76_phy_hw(dev, mstat.phy_idx);
}
@@ -1507,7 +1511,7 @@ mt76_check_sta(struct mt76_dev *dev, struct sk_buff *skb)
void mt76_rx_complete(struct mt76_dev *dev, struct sk_buff_head *frames,
struct napi_struct *napi)
{
- struct ieee80211_sta *sta;
+ struct ieee80211_link_sta *link_sta;
struct ieee80211_hw *hw;
struct sk_buff *skb, *tmp;
LIST_HEAD(list);
@@ -1518,8 +1522,8 @@ void mt76_rx_complete(struct mt76_dev *dev, struct sk_buff_head *frames,
mt76_check_ccmp_pn(skb);
skb_shinfo(skb)->frag_list = NULL;
- mt76_rx_convert(dev, skb, &hw, &sta);
- ieee80211_rx_list(hw, sta, skb, &list);
+ mt76_rx_convert(dev, skb, &hw, &link_sta);
+ ieee80211_rx_list(hw, link_sta, skb, &list);
/* subsequent amsdu frames */
while (nskb) {
@@ -1527,8 +1531,8 @@ void mt76_rx_complete(struct mt76_dev *dev, struct sk_buff_head *frames,
nskb = nskb->next;
skb->next = NULL;
- mt76_rx_convert(dev, skb, &hw, &sta);
- ieee80211_rx_list(hw, sta, skb, &list);
+ mt76_rx_convert(dev, skb, &hw, &link_sta);
+ ieee80211_rx_list(hw, link_sta, skb, &list);
}
}
spin_unlock(&dev->rx_lock);
diff --git a/drivers/net/wireless/realtek/rtw89/core.c b/drivers/net/wireless/realtek/rtw89/core.c
index 6e77522bcd8f..f6bcf309936d 100644
--- a/drivers/net/wireless/realtek/rtw89/core.c
+++ b/drivers/net/wireless/realtek/rtw89/core.c
@@ -2953,7 +2953,6 @@ static void rtw89_vif_rx_stats_iter(void *data, u8 *mac,
struct rtw89_pkt_stat *pkt_stat = &rtwdev->phystat.cur_pkt_stat;
struct rtw89_rx_desc_info *desc_info = iter_data->desc_info;
struct sk_buff *skb = iter_data->skb;
- struct ieee80211_rx_status *rx_status = IEEE80211_SKB_RXCB(skb);
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
struct rtw89_rx_phy_ppdu *phy_ppdu = iter_data->phy_ppdu;
bool is_mld = ieee80211_vif_is_mld(vif);
@@ -2988,11 +2987,6 @@ static void rtw89_vif_rx_stats_iter(void *data, u8 *mac,
if (!ether_addr_equal(target_bssid, bssid))
goto out;
- if (is_mld) {
- rx_status->link_valid = true;
- rx_status->link_id = rtwvif_link->link_id;
- }
-
if (ieee80211_is_beacon(hdr->frame_control)) {
if (vif->type == NL80211_IFTYPE_STATION &&
!test_bit(RTW89_FLAG_WOWLAN, rtwdev->flags)) {
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim.c b/drivers/net/wireless/virtual/mac80211_hwsim.c
index 4d9f5f87e814..7d529aa129f8 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim.c
@@ -1755,9 +1755,6 @@ static void mac80211_hwsim_rx(struct mac80211_hwsim_data *data,
sp->active_links_rx &= ~BIT(link_id);
else
sp->active_links_rx |= BIT(link_id);
-
- rx_status->link_valid = true;
- rx_status->link_id = link_id;
}
rcu_read_unlock();
}
diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 7f9d96939a4e..4d9dbd35369b 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -5207,14 +5207,18 @@ void ieee80211_restart_hw(struct ieee80211_hw *hw);
* mixed for a single hardware. Must not run concurrently with
* ieee80211_tx_status_skb() or ieee80211_tx_status_ni().
*
+ * For data frames, when hardware has done address translation, a link station
+ * has to be provided and the frequency information may be skipped.
+ *
* This function must be called with BHs disabled and RCU read lock
*
* @hw: the hardware this frame came in on
- * @sta: the station the frame was received from, or %NULL
+ * @link_sta: the link station the data frame was received from, or %NULL
* @skb: the buffer to receive, owned by mac80211 after this call
* @list: the destination list
*/
-void ieee80211_rx_list(struct ieee80211_hw *hw, struct ieee80211_sta *sta,
+void ieee80211_rx_list(struct ieee80211_hw *hw,
+ struct ieee80211_link_sta *link_sta,
struct sk_buff *skb, struct list_head *list);
/**
@@ -5232,14 +5236,18 @@ void ieee80211_rx_list(struct ieee80211_hw *hw, struct ieee80211_sta *sta,
* mixed for a single hardware. Must not run concurrently with
* ieee80211_tx_status_skb() or ieee80211_tx_status_ni().
*
+ * For data frames, when hardware has done address translation, a link station
+ * has to be provided and the frequency information may be skipped.
+ *
* This function must be called with BHs disabled.
*
* @hw: the hardware this frame came in on
- * @sta: the station the frame was received from, or %NULL
+ * @link_sta: the link station the data frame was received from, or %NULL
* @skb: the buffer to receive, owned by mac80211 after this call
* @napi: the NAPI context
*/
-void ieee80211_rx_napi(struct ieee80211_hw *hw, struct ieee80211_sta *sta,
+void ieee80211_rx_napi(struct ieee80211_hw *hw,
+ struct ieee80211_link_sta *link_sta,
struct sk_buff *skb, struct napi_struct *napi);
/**
diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index 11d6c56c9d7e..4098f63ec824 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -5432,7 +5432,8 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw,
* This is the receive path handler. It is called by a low level driver when an
* 802.11 MPDU is received from the hardware.
*/
-void ieee80211_rx_list(struct ieee80211_hw *hw, struct ieee80211_sta *pubsta,
+void ieee80211_rx_list(struct ieee80211_hw *hw,
+ struct ieee80211_link_sta *link_pubsta,
struct sk_buff *skb, struct list_head *list)
{
struct ieee80211_local *local = hw_to_local(hw);
@@ -5440,6 +5441,7 @@ void ieee80211_rx_list(struct ieee80211_hw *hw, struct ieee80211_sta *pubsta,
struct ieee80211_supported_band *sband;
struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
+ struct ieee80211_sta *pubsta;
WARN_ON_ONCE(softirq_count() == 0);
@@ -5562,8 +5564,15 @@ void ieee80211_rx_list(struct ieee80211_hw *hw, struct ieee80211_sta *pubsta,
}
}
- if (WARN_ON_ONCE(status->link_id >= IEEE80211_LINK_UNSPECIFIED))
- goto drop;
+ /* FIXME: Emulate the old driver behaviour for now */
+ if (link_pubsta) {
+ status->link_valid = 1;
+ status->link_id = link_pubsta->link_id;
+ pubsta = link_pubsta->sta;
+ } else {
+ status->link_valid = 0;
+ pubsta = NULL;
+ }
status->rx_flags = 0;
@@ -5595,7 +5604,8 @@ void ieee80211_rx_list(struct ieee80211_hw *hw, struct ieee80211_sta *pubsta,
}
EXPORT_SYMBOL(ieee80211_rx_list);
-void ieee80211_rx_napi(struct ieee80211_hw *hw, struct ieee80211_sta *pubsta,
+void ieee80211_rx_napi(struct ieee80211_hw *hw,
+ struct ieee80211_link_sta *link_pubsta,
struct sk_buff *skb, struct napi_struct *napi)
{
struct sk_buff *tmp;
@@ -5608,7 +5618,7 @@ void ieee80211_rx_napi(struct ieee80211_hw *hw, struct ieee80211_sta *pubsta,
* receive processing
*/
rcu_read_lock();
- ieee80211_rx_list(hw, pubsta, skb, &list);
+ ieee80211_rx_list(hw, link_pubsta, skb, &list);
rcu_read_unlock();
if (!napi) {
--
2.53.0
^ permalink raw reply related
* [RFC PATCH v2 3/8] wifi: mac80211: refactor RX link_id and station handling
From: Benjamin Berg @ 2026-02-23 12:38 UTC (permalink / raw)
To: linux-wireless; +Cc: Rameshkumar Sundaram, Ramasamy Kaliappan, Benjamin Berg
In-Reply-To: <20260223123818.384184-10-benjamin@sipsolutions.net>
From: Benjamin Berg <benjamin.berg@intel.com>
The link ID for one frequency may be different between VIFs. As such,
the sensible thing is to set the link ID later in the flow once the SKB
is duplicated for each VIF. As the link ID is not passed in from outside
mac80211 it is always valid and the corresponding bit can be dropped.
Also switch a few more places to pass the link STA as that is a natural
way to pass the link information around.
This fixes the per-link statistics when frames are reordered.
Note that this patch deliberately leaves some places unchanged and even
incorrect as this will be addressed by further refactorings.
Signed-off-by: Benjamin Berg <benjamin.berg@intel.com>
---
include/net/mac80211.h | 9 +-
net/mac80211/eht.c | 3 -
net/mac80211/iface.c | 7 +-
net/mac80211/mlme.c | 9 +-
net/mac80211/rx.c | 209 ++++++++++++++++++-----------------------
net/mac80211/scan.c | 10 +-
6 files changed, 105 insertions(+), 142 deletions(-)
diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 4d9dbd35369b..6db15227d4e1 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -1655,10 +1655,9 @@ enum mac80211_rx_encoding {
* @ampdu_reference: A-MPDU reference number, must be a different value for
* each A-MPDU but the same for each subframe within one A-MPDU
* @zero_length_psdu_type: radiotap type of the 0-length PSDU
- * @link_valid: if the link which is identified by @link_id is valid. This flag
- * is set only when connection is MLO.
- * @link_id: id of the link used to receive the packet. This is used along with
- * @link_valid.
+ * @link_id: id of the link used to receive the packet. Set and used by
+ * mac80211 internally, it uses @freq set by the driver to identify the
+ * correct link per vif.
*/
struct ieee80211_rx_status {
u64 mactime;
@@ -1698,7 +1697,7 @@ struct ieee80211_rx_status {
u8 chains;
s8 chain_signal[IEEE80211_MAX_CHAINS];
u8 zero_length_psdu_type;
- u8 link_valid:1, link_id:4;
+ u8 link_id:4;
};
static inline u32
diff --git a/net/mac80211/eht.c b/net/mac80211/eht.c
index 75096b2195d2..f49c4729e8e8 100644
--- a/net/mac80211/eht.c
+++ b/net/mac80211/eht.c
@@ -174,9 +174,6 @@ void ieee80211_rx_eml_op_mode_notif(struct ieee80211_sub_if_data *sdata,
if (!ift_ext_capa)
return;
- if (!status->link_valid)
- return;
-
sta = sta_info_get_bss(sdata, mgmt->sa);
if (!sta)
return;
diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c
index 676b2a43c9f2..ddb384d7cfa2 100644
--- a/net/mac80211/iface.c
+++ b/net/mac80211/iface.c
@@ -1611,11 +1611,8 @@ static void ieee80211_iface_process_skb(struct ieee80211_local *local,
break;
status = IEEE80211_SKB_RXCB(skb);
- if (!status->link_valid)
- link_sta = &sta->deflink;
- else
- link_sta = rcu_dereference_protected(sta->link[status->link_id],
- lockdep_is_held(&local->hw.wiphy->mtx));
+ link_sta = wiphy_dereference(local->hw.wiphy,
+ sta->link[status->link_id]);
if (link_sta)
ieee80211_ht_handle_chanwidth_notif(local, sdata, sta,
link_sta, chanwidth,
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index e83582b2c377..4190c2830ac5 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -8273,12 +8273,9 @@ void ieee80211_sta_rx_queued_mgmt(struct ieee80211_sub_if_data *sdata,
mgmt = (struct ieee80211_mgmt *) skb->data;
fc = le16_to_cpu(mgmt->frame_control);
- if (rx_status->link_valid) {
- link = sdata_dereference(sdata->link[rx_status->link_id],
- sdata);
- if (!link)
- return;
- }
+ link = sdata_dereference(sdata->link[rx_status->link_id], sdata);
+ if (!link)
+ return;
switch (fc & IEEE80211_FCTL_STYPE) {
case IEEE80211_STYPE_BEACON:
diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index 4098f63ec824..1b8ec391991f 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -222,43 +222,21 @@ ieee80211_rx_radiotap_hdrlen(struct ieee80211_local *local,
}
static void __ieee80211_queue_skb_to_iface(struct ieee80211_sub_if_data *sdata,
- int link_id,
- struct sta_info *sta,
+ struct link_sta_info *link_sta,
struct sk_buff *skb)
{
- struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
-
- if (link_id >= 0) {
- status->link_valid = 1;
- status->link_id = link_id;
- } else {
- status->link_valid = 0;
- }
-
skb_queue_tail(&sdata->skb_queue, skb);
wiphy_work_queue(sdata->local->hw.wiphy, &sdata->work);
- if (sta) {
- struct link_sta_info *link_sta_info;
-
- if (link_id >= 0) {
- link_sta_info = rcu_dereference(sta->link[link_id]);
- if (!link_sta_info)
- return;
- } else {
- link_sta_info = &sta->deflink;
- }
-
- link_sta_info->rx_stats.packets++;
- }
+ if (link_sta)
+ link_sta->rx_stats.packets++;
}
static void ieee80211_queue_skb_to_iface(struct ieee80211_sub_if_data *sdata,
- int link_id,
- struct sta_info *sta,
+ struct link_sta_info *link_sta,
struct sk_buff *skb)
{
skb->protocol = 0;
- __ieee80211_queue_skb_to_iface(sdata, link_id, sta, skb);
+ __ieee80211_queue_skb_to_iface(sdata, link_sta, skb);
}
static void ieee80211_handle_mu_mimo_mon(struct ieee80211_sub_if_data *sdata,
@@ -301,7 +279,7 @@ static void ieee80211_handle_mu_mimo_mon(struct ieee80211_sub_if_data *sdata,
if (!skb)
return;
- ieee80211_queue_skb_to_iface(sdata, -1, NULL, skb);
+ ieee80211_queue_skb_to_iface(sdata, NULL, skb);
}
/*
@@ -1496,7 +1474,7 @@ static void ieee80211_rx_reorder_ampdu(struct ieee80211_rx_data *rx,
/* if this mpdu is fragmented - terminate rx aggregation session */
sc = le16_to_cpu(hdr->seq_ctrl);
if (sc & IEEE80211_SCTL_FRAG) {
- ieee80211_queue_skb_to_iface(rx->sdata, rx->link_id, NULL, skb);
+ ieee80211_queue_skb_to_iface(rx->sdata, NULL, skb);
return;
}
@@ -2498,7 +2476,7 @@ ieee80211_rx_h_defragment(struct ieee80211_rx_data *rx)
out:
ieee80211_led_rx(rx->local);
- if (rx->sta)
+ if (rx->link_sta)
rx->link_sta->rx_stats.packets++;
return RX_CONTINUE;
}
@@ -3309,8 +3287,8 @@ ieee80211_rx_h_data(struct ieee80211_rx_data *rx)
(tf->action_code == WLAN_TDLS_CHANNEL_SWITCH_REQUEST ||
tf->action_code == WLAN_TDLS_CHANNEL_SWITCH_RESPONSE)) {
rx->skb->protocol = cpu_to_be16(ETH_P_TDLS);
- __ieee80211_queue_skb_to_iface(sdata, rx->link_id,
- rx->sta, rx->skb);
+ __ieee80211_queue_skb_to_iface(sdata, rx->link_sta,
+ rx->skb);
return RX_QUEUED;
}
}
@@ -3956,7 +3934,7 @@ ieee80211_rx_h_action(struct ieee80211_rx_data *rx)
return RX_QUEUED;
queue:
- ieee80211_queue_skb_to_iface(sdata, rx->link_id, rx->sta, rx->skb);
+ ieee80211_queue_skb_to_iface(sdata, rx->link_sta, rx->skb);
return RX_QUEUED;
}
@@ -4113,7 +4091,7 @@ ieee80211_rx_h_ext(struct ieee80211_rx_data *rx)
return RX_DROP_U_UNEXPECTED_EXT_FRAME;
/* for now only beacons are ext, so queue them */
- ieee80211_queue_skb_to_iface(sdata, rx->link_id, rx->sta, rx->skb);
+ ieee80211_queue_skb_to_iface(sdata, rx->link_sta, rx->skb);
return RX_QUEUED;
}
@@ -4170,7 +4148,7 @@ ieee80211_rx_h_mgmt(struct ieee80211_rx_data *rx)
return RX_DROP_U_UNHANDLED_MGMT_STYPE;
}
- ieee80211_queue_skb_to_iface(sdata, rx->link_id, rx->sta, rx->skb);
+ ieee80211_queue_skb_to_iface(sdata, rx->link_sta, rx->skb);
return RX_QUEUED;
}
@@ -4214,12 +4192,28 @@ static void ieee80211_rx_handlers(struct ieee80211_rx_data *rx,
spin_lock_bh(&rx->local->rx_path_lock);
while ((skb = __skb_dequeue(frames))) {
+ struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
+
/*
- * all the other fields are valid across frames
- * that belong to an aMPDU since they are on the
- * same TID from the same station
+ * Most fields are valid across frames. However,
+ * in the case of reordering frames may have arrived
+ * on different links, so adjust the link_id, link
+ * and link_sta accordingly.
*/
rx->skb = skb;
+ if (ieee80211_vif_is_mld(&rx->sdata->vif) &&
+ unlikely(rx->link_id != status->link_id)) {
+ if (rx->sta) {
+ rx->link_sta =
+ rcu_dereference(rx->sta->link[rx->link_id]);
+
+ /* Link got disabled, just use deflink */
+ if (!rx->link_sta)
+ rx->link_sta = &rx->sta->deflink;
+ }
+ rx->link_id = status->link_id;
+ rx->link = rcu_dereference(rx->sdata->link[status->link_id]);
+ }
if (WARN_ON_ONCE(!rx->link)) {
res = RX_DROP_U_NO_LINK;
@@ -4289,7 +4283,10 @@ static void ieee80211_invoke_rx_handlers(struct ieee80211_rx_data *rx)
static bool
ieee80211_rx_is_valid_sta_link_id(struct ieee80211_sta *sta, u8 link_id)
{
- return !!(sta->valid_links & BIT(link_id));
+ if (sta->mlo)
+ return !!(sta->valid_links & BIT(link_id));
+ else
+ return sta->deflink.link_id == link_id;
}
static bool ieee80211_rx_data_set_link(struct ieee80211_rx_data *rx,
@@ -4326,11 +4323,13 @@ static bool ieee80211_rx_data_set_sta(struct ieee80211_rx_data *rx,
if (link_id < 0) {
if (ieee80211_vif_is_mld(&rx->sdata->vif) &&
- sta && !sta->sta.valid_links)
+ sta && !sta->sta.valid_links) {
rx->link =
rcu_dereference(rx->sdata->link[sta->deflink.link_id]);
- else
+ rx->link_sta = rcu_dereference(rx->sta->link[link_id]);
+ } else {
rx->link = &rx->sdata->deflink;
+ }
} else if (!ieee80211_rx_data_set_link(rx, link_id)) {
return false;
}
@@ -4353,7 +4352,7 @@ void ieee80211_release_reorder_timeout(struct sta_info *sta, int tid)
struct tid_ampdu_rx *tid_agg_rx;
int link_id = -1;
- /* FIXME: statistics won't be right with this */
+ /* NOTE: the correct link STA for the frame is set later */
if (sta->sta.valid_links)
link_id = ffs(sta->sta.valid_links) - 1;
@@ -4824,20 +4823,14 @@ static void ieee80211_rx_8023(struct ieee80211_rx_data *rx,
{
struct ieee80211_sta_rx_stats *stats;
struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(rx->skb);
- struct sta_info *sta = rx->sta;
- struct link_sta_info *link_sta;
+ struct link_sta_info *link_sta = rx->link_sta;
struct sk_buff *skb = rx->skb;
void *sa = skb->data + ETH_ALEN;
void *da = skb->data;
- if (rx->link_id >= 0) {
- link_sta = rcu_dereference(sta->link[rx->link_id]);
- if (WARN_ON_ONCE(!link_sta)) {
- dev_kfree_skb(rx->skb);
- return;
- }
- } else {
- link_sta = &sta->deflink;
+ if (WARN_ON_ONCE(!link_sta)) {
+ dev_kfree_skb(rx->skb);
+ return;
}
stats = &link_sta->rx_stats;
@@ -5060,6 +5053,9 @@ static bool ieee80211_invoke_fast_rx(struct ieee80211_rx_data *rx,
goto drop;
}
+ status = IEEE80211_SKB_RXCB(skb);
+ status->link_id = rx->link_id < 0 ? 0 : rx->link_id;
+
ieee80211_rx_8023(rx, fast_rx, orig_len);
return true;
@@ -5084,6 +5080,7 @@ static bool ieee80211_prepare_and_rx_handle(struct ieee80211_rx_data *rx,
struct ieee80211_hdr *hdr = (void *)skb->data;
struct link_sta_info *link_sta = rx->link_sta;
struct ieee80211_link_data *link = rx->link;
+ struct ieee80211_rx_status *status;
rx->skb = skb;
@@ -5127,6 +5124,9 @@ static bool ieee80211_prepare_and_rx_handle(struct ieee80211_rx_data *rx,
hdr = (struct ieee80211_hdr *)rx->skb->data;
}
+ status = IEEE80211_SKB_RXCB(rx->skb);
+ status->link_id = rx->link_id < 0 ? 0 : rx->link_id;
+
if (unlikely(rx->sta && rx->sta->sta.mlo) &&
is_unicast_ether_addr(hdr->addr1) &&
!ieee80211_is_probe_resp(hdr->frame_control) &&
@@ -5152,22 +5152,19 @@ static bool ieee80211_prepare_and_rx_handle(struct ieee80211_rx_data *rx,
}
static void __ieee80211_rx_handle_8023(struct ieee80211_hw *hw,
- struct ieee80211_sta *pubsta,
+ struct ieee80211_link_sta *link_pubsta,
struct sk_buff *skb,
struct list_head *list)
{
struct ieee80211_local *local = hw_to_local(hw);
- struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
struct ieee80211_fast_rx *fast_rx;
struct ieee80211_rx_data rx;
struct sta_info *sta;
- int link_id = -1;
memset(&rx, 0, sizeof(rx));
rx.skb = skb;
rx.local = local;
rx.list = list;
- rx.link_id = -1;
I802_DEBUG_INC(local->dot11ReceivedFragmentCount);
@@ -5175,23 +5172,15 @@ static void __ieee80211_rx_handle_8023(struct ieee80211_hw *hw,
if (skb->len < sizeof(struct ethhdr))
goto drop;
- if (!pubsta)
+ if (!link_pubsta)
goto drop;
- if (status->link_valid)
- link_id = status->link_id;
-
- /*
- * TODO: Should the frame be dropped if the right link_id is not
- * available? Or may be it is fine in the current form to proceed with
- * the frame processing because with frame being in 802.3 format,
- * link_id is used only for stats purpose and updating the stats on
- * the deflink is fine?
- */
- sta = container_of(pubsta, struct sta_info, sta);
- if (!ieee80211_rx_data_set_sta(&rx, sta, link_id))
+ sta = container_of(link_pubsta->sta, struct sta_info, sta);
+ if (!ieee80211_rx_data_set_sta(&rx, sta, link_pubsta->link_id))
goto drop;
+ rx.link_id = link_pubsta->link_id;
+
fast_rx = rcu_dereference(rx.sta->fast_rx);
if (!fast_rx)
goto drop;
@@ -5212,6 +5201,9 @@ static bool ieee80211_rx_for_interface(struct ieee80211_rx_data *rx,
int link_id = -1;
/*
+ * FIXME: Here we can assume that link addresses have not
+ * been translated.
+ *
* Look up link station first, in case there's a
* chance that they might have a link address that
* is identical to the MLD address, that way we'll
@@ -5225,10 +5217,8 @@ static bool ieee80211_rx_for_interface(struct ieee80211_rx_data *rx,
struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
sta = sta_info_get_bss(rx->sdata, hdr->addr2);
- if (status->link_valid) {
- link_id = status->link_id;
- } else if (ieee80211_vif_is_mld(&rx->sdata->vif) &&
- status->freq) {
+ if (ieee80211_vif_is_mld(&rx->sdata->vif) &&
+ status->freq) {
struct ieee80211_link_data *link;
struct ieee80211_chanctx_conf *conf;
@@ -5256,12 +5246,11 @@ static bool ieee80211_rx_for_interface(struct ieee80211_rx_data *rx,
* be called with rcu_read_lock protection.
*/
static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw,
- struct ieee80211_sta *pubsta,
+ struct ieee80211_link_sta *link_pubsta,
struct sk_buff *skb,
struct list_head *list)
{
struct ieee80211_local *local = hw_to_local(hw);
- struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
struct ieee80211_sub_if_data *sdata;
struct ieee80211_hdr *hdr;
__le16 fc;
@@ -5275,7 +5264,6 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw,
rx.skb = skb;
rx.local = local;
rx.list = list;
- rx.link_id = -1;
if (ieee80211_is_data(fc) || ieee80211_is_mgmt(fc))
I802_DEBUG_INC(local->dot11ReceivedFragmentCount);
@@ -5306,35 +5294,14 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw,
if (ieee80211_is_data(fc)) {
struct sta_info *sta, *prev_sta;
- int link_id = -1;
-
- if (status->link_valid)
- link_id = status->link_id;
- if (pubsta) {
- sta = container_of(pubsta, struct sta_info, sta);
- if (!ieee80211_rx_data_set_sta(&rx, sta, link_id))
+ if (link_pubsta) {
+ sta = container_of(link_pubsta->sta,
+ struct sta_info, sta);
+ if (!ieee80211_rx_data_set_sta(&rx, sta,
+ link_pubsta->link_id))
goto out;
- /*
- * In MLO connection, fetch the link_id using addr2
- * when the driver does not pass link_id in status.
- * When the address translation is already performed by
- * driver/hw, the valid link_id must be passed in
- * status.
- */
-
- if (!status->link_valid && pubsta->mlo) {
- struct link_sta_info *link_sta;
-
- link_sta = link_sta_info_get_bss(rx.sdata,
- hdr->addr2);
- if (!link_sta)
- goto out;
-
- ieee80211_rx_data_set_link(&rx, link_sta->link_id);
- }
-
if (ieee80211_prepare_and_rx_handle(&rx, skb, true))
return;
goto out;
@@ -5343,13 +5310,20 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw,
prev_sta = NULL;
for_each_sta_info(local, hdr->addr2, sta, tmp) {
+ int link_id;
+
if (!prev_sta) {
prev_sta = sta;
continue;
}
rx.sdata = prev_sta->sdata;
- if (!status->link_valid && prev_sta->sta.mlo) {
+
+ /*
+ * FIXME: This is not correct as the addr2 cannot be a
+ * link address if the loop itself is iterated.
+ */
+ if (prev_sta->sta.mlo) {
struct link_sta_info *link_sta;
link_sta = link_sta_info_get_bss(rx.sdata,
@@ -5358,6 +5332,8 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw,
continue;
link_id = link_sta->link_id;
+ } else {
+ link_id = sta->deflink.link_id;
}
if (!ieee80211_rx_data_set_sta(&rx, prev_sta, link_id))
@@ -5369,8 +5345,15 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw,
}
if (prev_sta) {
+ int link_id;
+
rx.sdata = prev_sta->sdata;
- if (!status->link_valid && prev_sta->sta.mlo) {
+
+ /*
+ * FIXME: This is not correct as the addr2 cannot be a
+ * link address if the loop itself is iterated.
+ */
+ if (prev_sta->sta.mlo) {
struct link_sta_info *link_sta;
link_sta = link_sta_info_get_bss(rx.sdata,
@@ -5379,6 +5362,8 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw,
goto out;
link_id = link_sta->link_id;
+ } else {
+ link_id = sta->deflink.link_id;
}
if (!ieee80211_rx_data_set_sta(&rx, prev_sta, link_id))
@@ -5441,7 +5426,6 @@ void ieee80211_rx_list(struct ieee80211_hw *hw,
struct ieee80211_supported_band *sband;
struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
- struct ieee80211_sta *pubsta;
WARN_ON_ONCE(softirq_count() == 0);
@@ -5564,16 +5548,6 @@ void ieee80211_rx_list(struct ieee80211_hw *hw,
}
}
- /* FIXME: Emulate the old driver behaviour for now */
- if (link_pubsta) {
- status->link_valid = 1;
- status->link_id = link_pubsta->link_id;
- pubsta = link_pubsta->sta;
- } else {
- status->link_valid = 0;
- pubsta = NULL;
- }
-
status->rx_flags = 0;
kcov_remote_start_common(skb_get_kcov_handle(skb));
@@ -5592,9 +5566,10 @@ void ieee80211_rx_list(struct ieee80211_hw *hw,
ieee80211_tpt_led_trig_rx(local, skb->len);
if (status->flag & RX_FLAG_8023)
- __ieee80211_rx_handle_8023(hw, pubsta, skb, list);
+ __ieee80211_rx_handle_8023(hw, link_pubsta, skb, list);
else
- __ieee80211_rx_handle_packet(hw, pubsta, skb, list);
+ __ieee80211_rx_handle_packet(hw, link_pubsta, skb,
+ list);
}
kcov_remote_stop();
diff --git a/net/mac80211/scan.c b/net/mac80211/scan.c
index 4823c8d45639..bcbae67e2f49 100644
--- a/net/mac80211/scan.c
+++ b/net/mac80211/scan.c
@@ -9,7 +9,7 @@
* Copyright 2007, Michael Wu <flamingice@sourmilk.net>
* Copyright 2013-2015 Intel Mobile Communications GmbH
* Copyright 2016-2017 Intel Deutschland GmbH
- * Copyright (C) 2018-2025 Intel Corporation
+ * Copyright (C) 2018-2026 Intel Corporation
*/
#include <linux/if_arp.h>
@@ -204,12 +204,10 @@ ieee80211_bss_info_update(struct ieee80211_local *local,
* an indication on which of the links the frame was received
*/
if (ieee80211_vif_is_mld(&scan_sdata->vif)) {
- if (rx_status->link_valid) {
- s8 link_id = rx_status->link_id;
+ s8 link_id = rx_status->link_id;
- link_conf =
- rcu_dereference(scan_sdata->vif.link_conf[link_id]);
- }
+ link_conf =
+ rcu_dereference(scan_sdata->vif.link_conf[link_id]);
} else {
link_conf = &scan_sdata->vif.bss_conf;
}
--
2.53.0
^ permalink raw reply related
* [RFC PATCH v2 4/8] wifi: mac80211: rework RX packet handling
From: Benjamin Berg @ 2026-02-23 12:38 UTC (permalink / raw)
To: linux-wireless; +Cc: Rameshkumar Sundaram, Ramasamy Kaliappan, Benjamin Berg
In-Reply-To: <20260223123818.384184-10-benjamin@sipsolutions.net>
From: Benjamin Berg <benjamin.berg@intel.com>
The code has grown over time and it was not correctly handling all
cases. Examples of issues are that for management frames we would match
the received address against the MLD address even though we should not
resolve the station in that case. Another issue was that for data frames
we would not correctly fall back to use link addresses when the driver
does not provide the pubsta already.
The new code still uses the same approach as before. For data frames,
assume that we a valid station exists and interfaces do not need to be
iterated. On the other hand, for non-data frames (or if a data frame
without a station is received) all interfaces must be iterated.
Note that this rework makes mac80211 slightly more strict. For example,
previously mac80211 would have incorrectly accepted a data frame that
was transmitted on the wrong link.
Signed-off-by: Benjamin Berg <benjamin.berg@intel.com>
---
net/mac80211/rx.c | 282 +++++++++++++++++++++++++++-------------------
1 file changed, 169 insertions(+), 113 deletions(-)
diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index 1b8ec391991f..d76e8ee1a607 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -4284,9 +4284,33 @@ static bool
ieee80211_rx_is_valid_sta_link_id(struct ieee80211_sta *sta, u8 link_id)
{
if (sta->mlo)
- return !!(sta->valid_links & BIT(link_id));
- else
- return sta->deflink.link_id == link_id;
+ return sta->valid_links & BIT(link_id);
+
+ return sta->deflink.link_id == link_id;
+}
+
+static bool ieee80211_rx_data_set_link_sta(struct ieee80211_rx_data *rx,
+ struct link_sta_info *link_sta)
+{
+ struct sta_info *sta;
+
+ if (WARN_ON_ONCE(!link_sta) ||
+ !ieee80211_rx_is_valid_sta_link_id(&link_sta->sta->sta,
+ link_sta->link_id))
+ return false;
+
+ sta = link_sta->sta;
+
+ rx->sta = sta;
+ rx->local = sta->sdata->local;
+ rx->link = rcu_dereference(sta->sdata->link[link_sta->link_id]);
+ if (!rx->link)
+ return false;
+
+ rx->link_id = rx->link->link_id;
+ rx->link_sta = link_sta;
+
+ return true;
}
static bool ieee80211_rx_data_set_link(struct ieee80211_rx_data *rx,
@@ -5192,53 +5216,18 @@ static void __ieee80211_rx_handle_8023(struct ieee80211_hw *hw,
dev_kfree_skb(skb);
}
-static bool ieee80211_rx_for_interface(struct ieee80211_rx_data *rx,
- struct sk_buff *skb, bool consume)
+static bool ieee80211_rx_valid_freq(int freq, struct ieee80211_link_data *link)
{
- struct link_sta_info *link_sta;
- struct ieee80211_hdr *hdr = (void *)skb->data;
- struct sta_info *sta;
- int link_id = -1;
+ struct ieee80211_chanctx_conf *conf;
- /*
- * FIXME: Here we can assume that link addresses have not
- * been translated.
- *
- * Look up link station first, in case there's a
- * chance that they might have a link address that
- * is identical to the MLD address, that way we'll
- * have the link information if needed.
- */
- link_sta = link_sta_info_get_bss(rx->sdata, hdr->addr2);
- if (link_sta) {
- sta = link_sta->sta;
- link_id = link_sta->link_id;
- } else {
- struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
-
- sta = sta_info_get_bss(rx->sdata, hdr->addr2);
- if (ieee80211_vif_is_mld(&rx->sdata->vif) &&
- status->freq) {
- struct ieee80211_link_data *link;
- struct ieee80211_chanctx_conf *conf;
-
- for_each_link_data_rcu(rx->sdata, link) {
- conf = rcu_dereference(link->conf->chanctx_conf);
- if (!conf || !conf->def.chan)
- continue;
-
- if (status->freq == conf->def.chan->center_freq) {
- link_id = link->link_id;
- break;
- }
- }
- }
- }
+ if (!freq || link->sdata->vif.type == NL80211_IFTYPE_NAN)
+ return true;
- if (!ieee80211_rx_data_set_sta(rx, sta, link_id))
+ conf = rcu_dereference(link->conf->chanctx_conf);
+ if (!conf || !conf->def.chan)
return false;
- return ieee80211_prepare_and_rx_handle(rx, skb, consume);
+ return freq == conf->def.chan->center_freq;
}
/*
@@ -5252,11 +5241,14 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw,
{
struct ieee80211_local *local = hw_to_local(hw);
struct ieee80211_sub_if_data *sdata;
+ struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
struct ieee80211_hdr *hdr;
+ struct link_sta_info *link_sta;
+ struct sta_info *sta;
__le16 fc;
struct ieee80211_rx_data rx;
- struct ieee80211_sub_if_data *prev;
struct rhlist_head *tmp;
+ bool rx_data_pending;
int err = 0;
fc = ((struct ieee80211_hdr *)skb->data)->frame_control;
@@ -5292,92 +5284,102 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw,
ieee80211_is_s1g_beacon(hdr->frame_control)))
ieee80211_scan_rx(local, skb);
+ /*
+ * RX of a data frame should only happen to existing stations.
+ * It is therefore more efficient to use the one provided by the driver
+ * or search based on the station's address.
+ *
+ * Note we will fall through and handle a spurious data frame in the
+ * same way as a management frame.
+ */
if (ieee80211_is_data(fc)) {
- struct sta_info *sta, *prev_sta;
-
if (link_pubsta) {
- sta = container_of(link_pubsta->sta,
- struct sta_info, sta);
- if (!ieee80211_rx_data_set_sta(&rx, sta,
- link_pubsta->link_id))
- goto out;
+ sta = container_of(link_pubsta->sta, struct sta_info,
+ sta);
+ link_sta = rcu_dereference(sta->link[link_pubsta->link_id]);
- if (ieee80211_prepare_and_rx_handle(&rx, skb, true))
+ rx.sdata = sta->sdata;
+ if (ieee80211_rx_data_set_link_sta(&rx, link_sta) &&
+ ieee80211_prepare_and_rx_handle(&rx, skb, true))
return;
+
goto out;
}
- prev_sta = NULL;
+ rx_data_pending = false;
+ /* Search for stations on non-MLD interfaces */
for_each_sta_info(local, hdr->addr2, sta, tmp) {
- int link_id;
+ struct ieee80211_link_data *link;
- if (!prev_sta) {
- prev_sta = sta;
+ if (ieee80211_vif_is_mld(&sta->sdata->vif))
continue;
- }
-
- rx.sdata = prev_sta->sdata;
-
- /*
- * FIXME: This is not correct as the addr2 cannot be a
- * link address if the loop itself is iterated.
- */
- if (prev_sta->sta.mlo) {
- struct link_sta_info *link_sta;
- link_sta = link_sta_info_get_bss(rx.sdata,
- hdr->addr2);
- if (!link_sta)
- continue;
+ link = &sta->sdata->deflink;
+ if (!ieee80211_rx_valid_freq(status->freq, link))
+ continue;
- link_id = link_sta->link_id;
- } else {
- link_id = sta->deflink.link_id;
+ if (rx_data_pending) {
+ ieee80211_prepare_and_rx_handle(&rx, skb,
+ false);
+ rx_data_pending = false;
}
- if (!ieee80211_rx_data_set_sta(&rx, prev_sta, link_id))
- goto out;
-
- ieee80211_prepare_and_rx_handle(&rx, skb, false);
+ rx.sdata = sta->sdata;
+ if (!ieee80211_rx_data_set_link_sta(&rx, &sta->deflink))
+ continue;
- prev_sta = sta;
+ rx_data_pending = true;
}
- if (prev_sta) {
- int link_id;
+ /* And search stations on MLD interfaces */
+ for_each_link_sta_info(local, hdr->addr2, link_sta, tmp) {
+ struct ieee80211_link_data *link;
- rx.sdata = prev_sta->sdata;
+ sta = link_sta->sta;
+ sdata = sta->sdata;
+ link = rcu_dereference(sdata->link[link_sta->link_id]);
- /*
- * FIXME: This is not correct as the addr2 cannot be a
- * link address if the loop itself is iterated.
- */
- if (prev_sta->sta.mlo) {
- struct link_sta_info *link_sta;
-
- link_sta = link_sta_info_get_bss(rx.sdata,
- hdr->addr2);
- if (!link_sta)
- goto out;
+ if (!ieee80211_rx_valid_freq(status->freq, link))
+ continue;
- link_id = link_sta->link_id;
- } else {
- link_id = sta->deflink.link_id;
+ if (rx_data_pending) {
+ ieee80211_prepare_and_rx_handle(&rx, skb,
+ false);
+ rx_data_pending = false;
}
- if (!ieee80211_rx_data_set_sta(&rx, prev_sta, link_id))
- goto out;
+ rx.sdata = sta->sdata;
+ if (!ieee80211_rx_data_set_link_sta(&rx, link_sta))
+ continue;
+
+ rx_data_pending = true;
+ }
+ if (rx_data_pending) {
if (ieee80211_prepare_and_rx_handle(&rx, skb, true))
return;
+
goto out;
}
+
+ /* fall through, e.g. for spurious frame notification */
}
- prev = NULL;
+ /*
+ * This is a management frame (or a data frame without a station) and
+ * it will be delivered independent of whether a station exists,
+ * so iterate the interfaces.
+ */
+ rx_data_pending = false;
+
+ /* We expect the driver to provide frequency information */
+ if (WARN_ON_ONCE(!local->emulate_chanctx && !status->freq))
+ goto out;
list_for_each_entry_rcu(sdata, &local->interfaces, list) {
+ struct ieee80211_link_data *link = NULL;
+
if (!ieee80211_sdata_running(sdata))
continue;
@@ -5385,30 +5387,84 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw,
sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
continue;
+ /* Try to resolve the station */
+ if (!ieee80211_vif_is_mld(&sdata->vif)) {
+ sta = sta_info_get_bss(sdata, hdr->addr2);
+
+ if (sta) {
+ link_sta = &sta->deflink;
+ link = &sdata->deflink;
+ }
+ } else {
+ link_sta = link_sta_info_get_bss(sdata, hdr->addr2);
+
+ if (link_sta) {
+ sta = link_sta->sta;
+ link = rcu_dereference(sdata->link[link_sta->link_id]);
+ }
+ }
+
/*
- * frame is destined for this interface, but if it's
- * not also for the previous one we handle that after
- * the loop to avoid copying the SKB once too much
+ * If we found a STA and it has a valid link information, then
+ * RX using the station.
*/
+ if (link_sta && link &&
+ ieee80211_rx_valid_freq(status->freq, link)) {
+ if (rx_data_pending) {
+ ieee80211_prepare_and_rx_handle(&rx, skb, false);
+ rx_data_pending = false;
+ }
+
+ /* No valid_links check as we need to RX beacons */
+
+ rx.sdata = sdata;
+ if (ieee80211_rx_data_set_link_sta(&rx, link_sta))
+ rx_data_pending = true;
- if (!prev) {
- prev = sdata;
continue;
}
- rx.sdata = prev;
- ieee80211_rx_for_interface(&rx, skb, false);
+ /* No station, try to resolve the link and RX */
+ if (ieee80211_vif_is_mld(&sdata->vif)) {
+ struct ieee80211_chanctx_conf *conf;
+ bool found = false;
- prev = sdata;
- }
+ for_each_link_data_rcu(sdata, link) {
+ conf = rcu_dereference(link->conf->chanctx_conf);
+ if (!conf || !conf->def.chan)
+ continue;
+
+ if (status->freq == conf->def.chan->center_freq) {
+ found = true;
+ break;
+ }
+ }
- if (prev) {
- rx.sdata = prev;
+ if (!found)
+ link = &sdata->deflink;
+ } else {
+ link = &sdata->deflink;
+ }
- if (ieee80211_rx_for_interface(&rx, skb, true))
- return;
+ if (rx_data_pending) {
+ ieee80211_prepare_and_rx_handle(&rx, skb, false);
+ rx_data_pending = false;
+ }
+
+ rx.sdata = sdata;
+ rx.local = sdata->local;
+ rx.link = link;
+ rx.link_id = link->link_id;
+ rx.sta = NULL;
+ rx.link_sta = NULL;
+
+ rx_data_pending = true;
}
+ if (rx_data_pending &&
+ ieee80211_prepare_and_rx_handle(&rx, skb, true))
+ return;
+
out:
dev_kfree_skb(skb);
}
--
2.53.0
^ permalink raw reply related
* [RFC PATCH v2 5/8] wifi: cfg80211: add attribute for TX/RX denoting there is no station
From: Benjamin Berg @ 2026-02-23 12:38 UTC (permalink / raw)
To: linux-wireless; +Cc: Rameshkumar Sundaram, Ramasamy Kaliappan, Benjamin Berg
In-Reply-To: <20260223123818.384184-10-benjamin@sipsolutions.net>
From: Benjamin Berg <benjamin.berg@intel.com>
For MLD stations, userspace may need to explicitly transmit a frame to
a specific link address. In that case, it needs to ensure that no
address translation happens.
In the reverse case of an RX, userspace may need to know the link
address for a frame. By passing the information whether a STA is known
for the frame, userspace knows whether link translation happened and
can do the reverse lookup when needed.
This is important for flows where a STA is still registered but the
connection has been lost and it is returning again.
Signed-off-by: Benjamin Berg <benjamin.berg@intel.com>
---
include/net/cfg80211.h | 4 ++++
include/uapi/linux/nl80211.h | 7 +++++++
net/wireless/nl80211.c | 8 +++++++-
3 files changed, 18 insertions(+), 1 deletion(-)
diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index fc01de19c798..5063911cba56 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -3919,6 +3919,7 @@ struct cfg80211_update_ft_ies_params {
* @link_id: for MLO, the link ID to transmit on, -1 if not given; note
* that the link ID isn't validated (much), it's in range but the
* link might not exist (or be used by the receiver STA)
+ * @no_sta: set if the frame should not be transmitted using an existing STA
*/
struct cfg80211_mgmt_tx_params {
struct ieee80211_channel *chan;
@@ -3931,6 +3932,7 @@ struct cfg80211_mgmt_tx_params {
int n_csa_offsets;
const u16 *csa_offsets;
int link_id;
+ bool no_sta;
};
/**
@@ -9025,6 +9027,7 @@ void cfg80211_conn_failed(struct net_device *dev, const u8 *mac_addr,
* @flags: flags, as defined in &enum nl80211_rxmgmt_flags
* @rx_tstamp: Hardware timestamp of frame RX in nanoseconds
* @ack_tstamp: Hardware timestamp of ack TX in nanoseconds
+ * @no_sta: set if no station is known for the frame (relevant for MLD)
*/
struct cfg80211_rx_info {
int freq;
@@ -9036,6 +9039,7 @@ struct cfg80211_rx_info {
u32 flags;
u64 rx_tstamp;
u64 ack_tstamp;
+ bool no_sta;
};
/**
diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h
index b63f71850906..1466c043974c 100644
--- a/include/uapi/linux/nl80211.h
+++ b/include/uapi/linux/nl80211.h
@@ -2984,6 +2984,11 @@ enum nl80211_commands {
* this feature during association. This is a flag attribute.
* Currently only supported in mac80211 drivers.
*
+ * @NL80211_ATTR_FRAME_CMD_NO_STA: Valid for NL80211_CMD_FRAME to denote that
+ * the kernel had no station for a received frame or should not use a
+ * known station to transmit a frame. This is relevant to know whether
+ * MLD address translation happened or to disable it when sending a frame.
+ *
* @NUM_NL80211_ATTR: total number of nl80211_attrs available
* @NL80211_ATTR_MAX: highest attribute number currently defined
* @__NL80211_ATTR_AFTER_LAST: internal use
@@ -3557,6 +3562,8 @@ enum nl80211_attrs {
NL80211_ATTR_UHR_CAPABILITY,
NL80211_ATTR_DISABLE_UHR,
+ NL80211_ATTR_FRAME_CMD_NO_STA,
+
/* add attributes here, update the policy in nl80211.c */
__NL80211_ATTR_AFTER_LAST,
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index 6e58b238a1f8..56a9c63ddd76 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -946,6 +946,7 @@ static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = {
[NL80211_ATTR_UHR_CAPABILITY] =
NLA_POLICY_VALIDATE_FN(NLA_BINARY, validate_uhr_capa, 255),
[NL80211_ATTR_DISABLE_UHR] = { .type = NLA_FLAG },
+ [NL80211_ATTR_FRAME_CMD_NO_STA] = { .type = NLA_FLAG },
};
/* policy for the key attributes */
@@ -14020,6 +14021,9 @@ static int nl80211_tx_mgmt(struct sk_buff *skb, struct genl_info *info)
!(wdev->valid_links & BIT(params.link_id)))
return -EINVAL;
+ params.no_sta =
+ nla_get_flag(info->attrs[NL80211_ATTR_FRAME_CMD_NO_STA]);
+
params.buf = nla_data(info->attrs[NL80211_ATTR_FRAME]);
params.len = nla_len(info->attrs[NL80211_ATTR_FRAME]);
@@ -20581,7 +20585,9 @@ int nl80211_send_mgmt(struct cfg80211_registered_device *rdev,
(info->ack_tstamp && nla_put_u64_64bit(msg,
NL80211_ATTR_TX_HW_TIMESTAMP,
info->ack_tstamp,
- NL80211_ATTR_PAD)))
+ NL80211_ATTR_PAD)) ||
+ (info->no_sta &&
+ nla_put_flag(msg, NL80211_ATTR_FRAME_CMD_NO_STA)))
goto nla_put_failure;
genlmsg_end(msg, hdr);
--
2.53.0
^ 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