* [PATCH ath-next v7] wifi: ath12k: avoid dynamic alloc when parsing wmi tb
From: Nicolas Escande @ 2026-04-07 9:54 UTC (permalink / raw)
To: ath12k; +Cc: linux-wireless
On each WMI message received from the hardware, we alloc a temporary array
of WMI_TAG_MAX entries of type void *. This array is then populated with
pointers of parsed structs depending on the WMI type, and then freed. This
alloc can fail when memory pressure in the system is high enough.
Given the fact that it is scheduled in softirq with the system_bh_wq, we
should not be able to parse more than one WMI message per CPU at any time.
So instead lets move to a per cpu allocated array, that is reused across
calls. This memory is allocated as needed and refcounted to exist only
as long as one struct ath12k_base lives.
ath12k_wmi_tlv_parse_alloc() and ath12k_wmi_tlv_parse() are merged
together as it no longer allocs mem but returns the existing per-cpu one.
Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.3.1-00218-QCAHKSWPL_SILICONZ-1
Signed-off-by: Nicolas Escande <nico.escande@gmail.com>
---
changes from v6:
- fix useless ath12k_wmi_tb NULL init
- do not use refcount_inc when refount is at 0, it can warn
changes from v5:
- use of the refount_t kernel api
changes from v4:
- moved to a single instance, refcounted per cpu memory alloc
changes from v3:
- simplified ath12k_core_init() with a single statement
- move perpcu.h include directly to wmi.c
changes from v2:
- removed now superfluous return in ath12k_wmi_event_teardown_complete()
- moved ath12k_wmi_tb declaration to wmi.c & added two functions to
alloc / free it
- removed useless error message on memory allocation failure
changes from v1:
- rebased on ath-next 27401c9b1432
- changed wording according to Jeff's comment
- moved alloc/cleanup to new module_init/exit functions in the
ath12k module as per Baochen's comment
changes from RFC:
- rebased on ath-next 8e0ab5b9adb7
- converted missing call sites ath12k_wmi_obss_color_collision_event()
& ath12k_wmi_pdev_temperature_event()
- changed alloc order & cleanup path in ath12k_core_alloc() as it seems
it confused people
- used sizeof(*tb) in ath12k_wmi_tlv_parse()
---
drivers/net/wireless/ath/ath12k/core.c | 6 +
drivers/net/wireless/ath/ath12k/wmi.c | 217 +++++++++----------------
drivers/net/wireless/ath/ath12k/wmi.h | 3 +
3 files changed, 87 insertions(+), 139 deletions(-)
diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c
index c31c47fb5a73..6f0f4bfbf699 100644
--- a/drivers/net/wireless/ath/ath12k/core.c
+++ b/drivers/net/wireless/ath/ath12k/core.c
@@ -2256,6 +2256,7 @@ void ath12k_core_deinit(struct ath12k_base *ab)
void ath12k_core_free(struct ath12k_base *ab)
{
timer_delete_sync(&ab->rx_replenish_retry);
+ ath12k_wmi_free();
destroy_workqueue(ab->workqueue_aux);
destroy_workqueue(ab->workqueue);
kfree(ab);
@@ -2280,6 +2281,9 @@ struct ath12k_base *ath12k_core_alloc(struct device *dev, size_t priv_size,
if (!ab->workqueue_aux)
goto err_free_wq;
+ if (ath12k_wmi_alloc() < 0)
+ goto err_free_wq_aux;
+
mutex_init(&ab->core_lock);
spin_lock_init(&ab->base_lock);
init_completion(&ab->reset_complete);
@@ -2314,6 +2318,8 @@ struct ath12k_base *ath12k_core_alloc(struct device *dev, size_t priv_size,
return ab;
+err_free_wq_aux:
+ destroy_workqueue(ab->workqueue_aux);
err_free_wq:
destroy_workqueue(ab->workqueue);
err_sc_free:
diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c
index 65a05a9520ff..484fdd3b1b7f 100644
--- a/drivers/net/wireless/ath/ath12k/wmi.c
+++ b/drivers/net/wireless/ath/ath12k/wmi.c
@@ -15,6 +15,8 @@
#include <linux/time.h>
#include <linux/of.h>
#include <linux/cleanup.h>
+#include <linux/percpu.h>
+#include <linux/refcount.h>
#include "core.h"
#include "debugfs.h"
#include "debug.h"
@@ -134,6 +136,10 @@ struct wmi_pdev_set_obss_bitmap_arg {
const char *label;
};
+static DEFINE_MUTEX(ath12k_wmi_mutex);
+static refcount_t ath12k_wmi_refcount;
+static void __percpu *ath12k_wmi_tb;
+
static const struct ath12k_wmi_tlv_policy ath12k_wmi_tlv_policies[] = {
[WMI_TAG_ARRAY_BYTE] = { .min_len = 0 },
[WMI_TAG_ARRAY_UINT32] = { .min_len = 0 },
@@ -289,29 +295,19 @@ static int ath12k_wmi_tlv_iter_parse(struct ath12k_base *ab, u16 tag, u16 len,
return 0;
}
-static int ath12k_wmi_tlv_parse(struct ath12k_base *ar, const void **tb,
- const void *ptr, size_t len)
-{
- return ath12k_wmi_tlv_iter(ar, ptr, len, ath12k_wmi_tlv_iter_parse,
- (void *)tb);
-}
-
static const void **
-ath12k_wmi_tlv_parse_alloc(struct ath12k_base *ab,
- struct sk_buff *skb, gfp_t gfp)
+ath12k_wmi_tlv_parse(struct ath12k_base *ab, struct sk_buff *skb)
{
const void **tb;
int ret;
- tb = kzalloc_objs(*tb, WMI_TAG_MAX, gfp);
- if (!tb)
- return ERR_PTR(-ENOMEM);
+ tb = this_cpu_ptr(ath12k_wmi_tb);
+ memset(tb, 0, WMI_TAG_MAX * sizeof(*tb));
- ret = ath12k_wmi_tlv_parse(ab, tb, skb->data, skb->len);
- if (ret) {
- kfree(tb);
+ ret = ath12k_wmi_tlv_iter(ab, skb->data, skb->len,
+ ath12k_wmi_tlv_iter_parse, (void *)tb);
+ if (ret)
return ERR_PTR(ret);
- }
return tb;
}
@@ -3911,9 +3907,10 @@ ath12k_wmi_obss_color_collision_event(struct ath12k_base *ab, struct sk_buff *sk
const struct wmi_obss_color_collision_event *ev;
struct ath12k_link_vif *arvif;
u32 vdev_id, evt_type;
+ const void **tb;
u64 bitmap;
- const void **tb __free(kfree) = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
+ tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ath12k_warn(ab, "failed to parse OBSS color collision tlv %ld\n",
PTR_ERR(tb));
@@ -5714,7 +5711,7 @@ static int ath12k_pull_vdev_start_resp_tlv(struct ath12k_base *ab, struct sk_buf
const struct wmi_vdev_start_resp_event *ev;
int ret;
- tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
+ tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
@@ -5724,13 +5721,11 @@ static int ath12k_pull_vdev_start_resp_tlv(struct ath12k_base *ab, struct sk_buf
ev = tb[WMI_TAG_VDEV_START_RESPONSE_EVENT];
if (!ev) {
ath12k_warn(ab, "failed to fetch vdev start resp ev");
- kfree(tb);
return -EPROTO;
}
*vdev_rsp = *ev;
- kfree(tb);
return 0;
}
@@ -5809,7 +5804,7 @@ static int ath12k_pull_reg_chan_list_ext_update_ev(struct ath12k_base *ab,
ath12k_dbg(ab, ATH12K_DBG_WMI, "processing regulatory ext channel list\n");
- tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
+ tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
@@ -5819,7 +5814,6 @@ static int ath12k_pull_reg_chan_list_ext_update_ev(struct ath12k_base *ab,
ev = tb[WMI_TAG_REG_CHAN_LIST_CC_EXT_EVENT];
if (!ev) {
ath12k_warn(ab, "failed to fetch reg chan list ext update ev\n");
- kfree(tb);
return -EPROTO;
}
@@ -5849,7 +5843,6 @@ static int ath12k_pull_reg_chan_list_ext_update_ev(struct ath12k_base *ab,
if (num_2g_reg_rules > MAX_REG_RULES || num_5g_reg_rules > MAX_REG_RULES) {
ath12k_warn(ab, "Num reg rules for 2G/5G exceeds max limit (num_2g_reg_rules: %d num_5g_reg_rules: %d max_rules: %d)\n",
num_2g_reg_rules, num_5g_reg_rules, MAX_REG_RULES);
- kfree(tb);
return -EINVAL;
}
@@ -5859,7 +5852,6 @@ static int ath12k_pull_reg_chan_list_ext_update_ev(struct ath12k_base *ab,
if (num_6g_reg_rules_ap[i] > MAX_6GHZ_REG_RULES) {
ath12k_warn(ab, "Num 6G reg rules for AP mode(%d) exceeds max limit (num_6g_reg_rules_ap: %d, max_rules: %d)\n",
i, num_6g_reg_rules_ap[i], MAX_6GHZ_REG_RULES);
- kfree(tb);
return -EINVAL;
}
@@ -5884,14 +5876,12 @@ static int ath12k_pull_reg_chan_list_ext_update_ev(struct ath12k_base *ab,
num_6g_reg_rules_cl[WMI_REG_VLP_AP][i] > MAX_6GHZ_REG_RULES) {
ath12k_warn(ab, "Num 6g client reg rules exceeds max limit, for client(type: %d)\n",
i);
- kfree(tb);
return -EINVAL;
}
}
if (!total_reg_rules) {
ath12k_warn(ab, "No reg rules available\n");
- kfree(tb);
return -EINVAL;
}
@@ -5993,7 +5983,6 @@ static int ath12k_pull_reg_chan_list_ext_update_ev(struct ath12k_base *ab,
ext_wmi_reg_rule);
if (!reg_info->reg_rules_2g_ptr) {
- kfree(tb);
ath12k_warn(ab, "Unable to Allocate memory for 2g rules\n");
return -ENOMEM;
}
@@ -6027,7 +6016,6 @@ static int ath12k_pull_reg_chan_list_ext_update_ev(struct ath12k_base *ab,
ext_wmi_reg_rule);
if (!reg_info->reg_rules_5g_ptr) {
- kfree(tb);
ath12k_warn(ab, "Unable to Allocate memory for 5g rules\n");
return -ENOMEM;
}
@@ -6046,7 +6034,6 @@ static int ath12k_pull_reg_chan_list_ext_update_ev(struct ath12k_base *ab,
ext_wmi_reg_rule);
if (!reg_info->reg_rules_6g_ap_ptr[i]) {
- kfree(tb);
ath12k_warn(ab, "Unable to Allocate memory for 6g ap rules\n");
return -ENOMEM;
}
@@ -6061,7 +6048,6 @@ static int ath12k_pull_reg_chan_list_ext_update_ev(struct ath12k_base *ab,
ext_wmi_reg_rule);
if (!reg_info->reg_rules_6g_client_ptr[j][i]) {
- kfree(tb);
ath12k_warn(ab, "Unable to Allocate memory for 6g client rules\n");
return -ENOMEM;
}
@@ -6096,7 +6082,6 @@ static int ath12k_pull_reg_chan_list_ext_update_ev(struct ath12k_base *ab,
ath12k_dbg(ab, ATH12K_DBG_WMI, "processed regulatory ext channel list\n");
- kfree(tb);
return 0;
}
@@ -6107,7 +6092,7 @@ static int ath12k_pull_peer_del_resp_ev(struct ath12k_base *ab, struct sk_buff *
const struct wmi_peer_delete_resp_event *ev;
int ret;
- tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
+ tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
@@ -6117,7 +6102,6 @@ static int ath12k_pull_peer_del_resp_ev(struct ath12k_base *ab, struct sk_buff *
ev = tb[WMI_TAG_PEER_DELETE_RESP_EVENT];
if (!ev) {
ath12k_warn(ab, "failed to fetch peer delete resp ev");
- kfree(tb);
return -EPROTO;
}
@@ -6127,7 +6111,6 @@ static int ath12k_pull_peer_del_resp_ev(struct ath12k_base *ab, struct sk_buff *
ether_addr_copy(peer_del_resp->peer_macaddr.addr,
ev->peer_macaddr.addr);
- kfree(tb);
return 0;
}
@@ -6139,7 +6122,7 @@ static int ath12k_pull_vdev_del_resp_ev(struct ath12k_base *ab,
const struct wmi_vdev_delete_resp_event *ev;
int ret;
- tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
+ tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
@@ -6149,13 +6132,11 @@ static int ath12k_pull_vdev_del_resp_ev(struct ath12k_base *ab,
ev = tb[WMI_TAG_VDEV_DELETE_RESP_EVENT];
if (!ev) {
ath12k_warn(ab, "failed to fetch vdev delete resp ev");
- kfree(tb);
return -EPROTO;
}
*vdev_id = le32_to_cpu(ev->vdev_id);
- kfree(tb);
return 0;
}
@@ -6167,7 +6148,7 @@ static int ath12k_pull_bcn_tx_status_ev(struct ath12k_base *ab,
const struct wmi_bcn_tx_status_event *ev;
int ret;
- tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
+ tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
@@ -6177,14 +6158,12 @@ static int ath12k_pull_bcn_tx_status_ev(struct ath12k_base *ab,
ev = tb[WMI_TAG_OFFLOAD_BCN_TX_STATUS_EVENT];
if (!ev) {
ath12k_warn(ab, "failed to fetch bcn tx status ev");
- kfree(tb);
return -EPROTO;
}
*vdev_id = le32_to_cpu(ev->vdev_id);
*tx_status = le32_to_cpu(ev->tx_status);
- kfree(tb);
return 0;
}
@@ -6195,7 +6174,7 @@ static int ath12k_pull_vdev_stopped_param_tlv(struct ath12k_base *ab, struct sk_
const struct wmi_vdev_stopped_event *ev;
int ret;
- tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
+ tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
@@ -6205,13 +6184,11 @@ static int ath12k_pull_vdev_stopped_param_tlv(struct ath12k_base *ab, struct sk_
ev = tb[WMI_TAG_VDEV_STOPPED_EVENT];
if (!ev) {
ath12k_warn(ab, "failed to fetch vdev stop ev");
- kfree(tb);
return -EPROTO;
}
*vdev_id = le32_to_cpu(ev->vdev_id);
- kfree(tb);
return 0;
}
@@ -6350,7 +6327,7 @@ static int ath12k_pull_mgmt_tx_compl_param_tlv(struct ath12k_base *ab,
const struct wmi_mgmt_tx_compl_event *ev;
int ret;
- tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
+ tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
@@ -6360,7 +6337,6 @@ static int ath12k_pull_mgmt_tx_compl_param_tlv(struct ath12k_base *ab,
ev = tb[WMI_TAG_MGMT_TX_COMPL_EVENT];
if (!ev) {
ath12k_warn(ab, "failed to fetch mgmt tx compl ev");
- kfree(tb);
return -EPROTO;
}
@@ -6370,7 +6346,6 @@ static int ath12k_pull_mgmt_tx_compl_param_tlv(struct ath12k_base *ab,
param->ppdu_id = ev->ppdu_id;
param->ack_rssi = ev->ack_rssi;
- kfree(tb);
return 0;
}
@@ -6533,7 +6508,7 @@ static int ath12k_pull_scan_ev(struct ath12k_base *ab, struct sk_buff *skb,
const struct wmi_scan_event *ev;
int ret;
- tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
+ tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
@@ -6543,7 +6518,6 @@ static int ath12k_pull_scan_ev(struct ath12k_base *ab, struct sk_buff *skb,
ev = tb[WMI_TAG_SCAN_EVENT];
if (!ev) {
ath12k_warn(ab, "failed to fetch scan ev");
- kfree(tb);
return -EPROTO;
}
@@ -6555,7 +6529,6 @@ static int ath12k_pull_scan_ev(struct ath12k_base *ab, struct sk_buff *skb,
scan_evt_param->vdev_id = ev->vdev_id;
scan_evt_param->tsf_timestamp = ev->tsf_timestamp;
- kfree(tb);
return 0;
}
@@ -6566,7 +6539,7 @@ static int ath12k_pull_peer_sta_kickout_ev(struct ath12k_base *ab, struct sk_buf
const struct wmi_peer_sta_kickout_event *ev;
int ret;
- tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
+ tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
@@ -6576,7 +6549,6 @@ static int ath12k_pull_peer_sta_kickout_ev(struct ath12k_base *ab, struct sk_buf
ev = tb[WMI_TAG_PEER_STA_KICKOUT_EVENT];
if (!ev) {
ath12k_warn(ab, "failed to fetch peer sta kickout ev");
- kfree(tb);
return -EPROTO;
}
@@ -6584,7 +6556,6 @@ static int ath12k_pull_peer_sta_kickout_ev(struct ath12k_base *ab, struct sk_buf
arg->reason = le32_to_cpu(ev->reason);
arg->rssi = le32_to_cpu(ev->rssi);
- kfree(tb);
return 0;
}
@@ -6595,7 +6566,7 @@ static int ath12k_pull_roam_ev(struct ath12k_base *ab, struct sk_buff *skb,
const struct wmi_roam_event *ev;
int ret;
- tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
+ tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
@@ -6605,7 +6576,6 @@ static int ath12k_pull_roam_ev(struct ath12k_base *ab, struct sk_buff *skb,
ev = tb[WMI_TAG_ROAM_EVENT];
if (!ev) {
ath12k_warn(ab, "failed to fetch roam ev");
- kfree(tb);
return -EPROTO;
}
@@ -6613,7 +6583,6 @@ static int ath12k_pull_roam_ev(struct ath12k_base *ab, struct sk_buff *skb,
roam_ev->reason = ev->reason;
roam_ev->rssi = ev->rssi;
- kfree(tb);
return 0;
}
@@ -6647,7 +6616,7 @@ static int ath12k_pull_chan_info_ev(struct ath12k_base *ab, struct sk_buff *skb,
const struct wmi_chan_info_event *ev;
int ret;
- tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
+ tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
@@ -6657,7 +6626,6 @@ static int ath12k_pull_chan_info_ev(struct ath12k_base *ab, struct sk_buff *skb,
ev = tb[WMI_TAG_CHAN_INFO_EVENT];
if (!ev) {
ath12k_warn(ab, "failed to fetch chan info ev");
- kfree(tb);
return -EPROTO;
}
@@ -6674,7 +6642,6 @@ static int ath12k_pull_chan_info_ev(struct ath12k_base *ab, struct sk_buff *skb,
ch_info_ev->mac_clk_mhz = ev->mac_clk_mhz;
ch_info_ev->vdev_id = ev->vdev_id;
- kfree(tb);
return 0;
}
@@ -6686,7 +6653,7 @@ ath12k_pull_pdev_bss_chan_info_ev(struct ath12k_base *ab, struct sk_buff *skb,
const struct wmi_pdev_bss_chan_info_event *ev;
int ret;
- tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
+ tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
@@ -6696,7 +6663,6 @@ ath12k_pull_pdev_bss_chan_info_ev(struct ath12k_base *ab, struct sk_buff *skb,
ev = tb[WMI_TAG_PDEV_BSS_CHAN_INFO_EVENT];
if (!ev) {
ath12k_warn(ab, "failed to fetch pdev bss chan info ev");
- kfree(tb);
return -EPROTO;
}
@@ -6714,7 +6680,6 @@ ath12k_pull_pdev_bss_chan_info_ev(struct ath12k_base *ab, struct sk_buff *skb,
bss_ch_info_ev->rx_bss_cycle_count_low = ev->rx_bss_cycle_count_low;
bss_ch_info_ev->rx_bss_cycle_count_high = ev->rx_bss_cycle_count_high;
- kfree(tb);
return 0;
}
@@ -6726,7 +6691,7 @@ ath12k_pull_vdev_install_key_compl_ev(struct ath12k_base *ab, struct sk_buff *sk
const struct wmi_vdev_install_key_compl_event *ev;
int ret;
- tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
+ tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
@@ -6736,7 +6701,6 @@ ath12k_pull_vdev_install_key_compl_ev(struct ath12k_base *ab, struct sk_buff *sk
ev = tb[WMI_TAG_VDEV_INSTALL_KEY_COMPLETE_EVENT];
if (!ev) {
ath12k_warn(ab, "failed to fetch vdev install key compl ev");
- kfree(tb);
return -EPROTO;
}
@@ -6746,7 +6710,6 @@ ath12k_pull_vdev_install_key_compl_ev(struct ath12k_base *ab, struct sk_buff *sk
arg->key_flags = le32_to_cpu(ev->key_flags);
arg->status = le32_to_cpu(ev->status);
- kfree(tb);
return 0;
}
@@ -6757,7 +6720,7 @@ static int ath12k_pull_peer_assoc_conf_ev(struct ath12k_base *ab, struct sk_buff
const struct wmi_peer_assoc_conf_event *ev;
int ret;
- tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
+ tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
@@ -6767,14 +6730,12 @@ static int ath12k_pull_peer_assoc_conf_ev(struct ath12k_base *ab, struct sk_buff
ev = tb[WMI_TAG_PEER_ASSOC_CONF_EVENT];
if (!ev) {
ath12k_warn(ab, "failed to fetch peer assoc conf ev");
- kfree(tb);
return -EPROTO;
}
peer_assoc_conf->vdev_id = le32_to_cpu(ev->vdev_id);
peer_assoc_conf->macaddr = ev->peer_macaddr.addr;
- kfree(tb);
return 0;
}
@@ -6792,7 +6753,7 @@ static int ath12k_reg_11d_new_cc_event(struct ath12k_base *ab, struct sk_buff *s
const void **tb;
int ret, i;
- tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
+ tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
@@ -6801,7 +6762,6 @@ static int ath12k_reg_11d_new_cc_event(struct ath12k_base *ab, struct sk_buff *s
ev = tb[WMI_TAG_11D_NEW_COUNTRY_EVENT];
if (!ev) {
- kfree(tb);
ath12k_warn(ab, "failed to fetch 11d new cc ev");
return -EPROTO;
}
@@ -6814,8 +6774,6 @@ static int ath12k_reg_11d_new_cc_event(struct ath12k_base *ab, struct sk_buff *s
ab->new_alpha2[0],
ab->new_alpha2[1]);
- kfree(tb);
-
for (i = 0; i < ab->num_radios; i++) {
pdev = &ab->pdevs[i];
ar = pdev->ar;
@@ -8557,7 +8515,7 @@ static void ath12k_pdev_ctl_failsafe_check_event(struct ath12k_base *ab,
const struct wmi_pdev_ctl_failsafe_chk_event *ev;
int ret;
- tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
+ tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
@@ -8567,7 +8525,6 @@ static void ath12k_pdev_ctl_failsafe_check_event(struct ath12k_base *ab,
ev = tb[WMI_TAG_PDEV_CTL_FAILSAFE_CHECK_EVENT];
if (!ev) {
ath12k_warn(ab, "failed to fetch pdev ctl failsafe check ev");
- kfree(tb);
return;
}
@@ -8581,8 +8538,6 @@ static void ath12k_pdev_ctl_failsafe_check_event(struct ath12k_base *ab,
if (ev->ctl_failsafe_status != 0)
ath12k_warn(ab, "pdev ctl failsafe failure status %d",
ev->ctl_failsafe_status);
-
- kfree(tb);
}
static void
@@ -8654,7 +8609,7 @@ ath12k_wmi_pdev_csa_switch_count_status_event(struct ath12k_base *ab,
const u32 *vdev_ids;
int ret;
- tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
+ tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
@@ -8666,7 +8621,6 @@ ath12k_wmi_pdev_csa_switch_count_status_event(struct ath12k_base *ab,
if (!ev || !vdev_ids) {
ath12k_warn(ab, "failed to fetch pdev csa switch count ev");
- kfree(tb);
return;
}
@@ -8676,8 +8630,6 @@ ath12k_wmi_pdev_csa_switch_count_status_event(struct ath12k_base *ab,
ev->num_vdevs);
ath12k_wmi_process_csa_switch_count_event(ab, ev, vdev_ids);
-
- kfree(tb);
}
static void
@@ -8689,7 +8641,7 @@ ath12k_wmi_pdev_dfs_radar_detected_event(struct ath12k_base *ab, struct sk_buff
struct ath12k *ar;
int ret;
- tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
+ tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
@@ -8700,7 +8652,6 @@ ath12k_wmi_pdev_dfs_radar_detected_event(struct ath12k_base *ab, struct sk_buff
if (!ev) {
ath12k_warn(ab, "failed to fetch pdev dfs radar detected ev");
- kfree(tb);
return;
}
@@ -8739,8 +8690,6 @@ ath12k_wmi_pdev_dfs_radar_detected_event(struct ath12k_base *ab, struct sk_buff
exit:
rcu_read_unlock();
-
- kfree(tb);
}
static void ath12k_tm_wmi_event_segmented(struct ath12k_base *ab, u32 cmd_id,
@@ -8751,7 +8700,7 @@ static void ath12k_tm_wmi_event_segmented(struct ath12k_base *ab, u32 cmd_id,
int ret;
u16 length;
- tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
+ tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
@@ -8762,14 +8711,11 @@ static void ath12k_tm_wmi_event_segmented(struct ath12k_base *ab, u32 cmd_id,
ev = tb[WMI_TAG_ARRAY_BYTE];
if (!ev) {
ath12k_warn(ab, "failed to fetch ftm msg\n");
- kfree(tb);
return;
}
length = skb->len - TLV_HDR_SIZE;
ath12k_tm_process_event(ab, cmd_id, ev, length);
- kfree(tb);
- tb = NULL;
}
static void
@@ -8782,7 +8728,7 @@ ath12k_wmi_pdev_temperature_event(struct ath12k_base *ab,
int temp;
u32 pdev_id;
- tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
+ tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ath12k_warn(ab, "failed to parse tlv: %ld\n", PTR_ERR(tb));
return;
@@ -8791,15 +8737,12 @@ ath12k_wmi_pdev_temperature_event(struct ath12k_base *ab,
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 %u\n",
temp, pdev_id);
@@ -8826,7 +8769,7 @@ static void ath12k_fils_discovery_event(struct ath12k_base *ab,
const struct wmi_fils_discovery_event *ev;
int ret;
- tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
+ tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab,
@@ -8838,15 +8781,12 @@ static void ath12k_fils_discovery_event(struct ath12k_base *ab,
ev = tb[WMI_TAG_HOST_SWFDA_EVENT];
if (!ev) {
ath12k_warn(ab, "failed to fetch FILS discovery event\n");
- kfree(tb);
return;
}
ath12k_warn(ab,
"FILS discovery frame expected from host for vdev_id: %u, transmission scheduled at %u, next TBTT: %u\n",
ev->vdev_id, ev->fils_tt, ev->tbtt);
-
- kfree(tb);
}
static void ath12k_probe_resp_tx_status_event(struct ath12k_base *ab,
@@ -8856,7 +8796,7 @@ static void ath12k_probe_resp_tx_status_event(struct ath12k_base *ab,
const struct wmi_probe_resp_tx_status_event *ev;
int ret;
- tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
+ tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab,
@@ -8869,7 +8809,6 @@ static void ath12k_probe_resp_tx_status_event(struct ath12k_base *ab,
if (!ev) {
ath12k_warn(ab,
"failed to fetch probe response transmission status event");
- kfree(tb);
return;
}
@@ -8877,8 +8816,6 @@ static void ath12k_probe_resp_tx_status_event(struct ath12k_base *ab,
ath12k_warn(ab,
"Probe response transmission failed for vdev_id %u, status %u\n",
ev->vdev_id, ev->tx_status);
-
- kfree(tb);
}
static int ath12k_wmi_p2p_noa_event(struct ath12k_base *ab,
@@ -8890,7 +8827,7 @@ static int ath12k_wmi_p2p_noa_event(struct ath12k_base *ab,
struct ath12k *ar;
int ret, vdev_id;
- tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
+ tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse P2P NoA TLV: %d\n", ret);
@@ -8900,10 +8837,8 @@ static int ath12k_wmi_p2p_noa_event(struct ath12k_base *ab,
ev = tb[WMI_TAG_P2P_NOA_EVENT];
noa = tb[WMI_TAG_P2P_NOA_INFO];
- if (!ev || !noa) {
- ret = -EPROTO;
- goto out;
- }
+ if (!ev || !noa)
+ return -EPROTO;
vdev_id = __le32_to_cpu(ev->vdev_id);
@@ -8926,8 +8861,6 @@ static int ath12k_wmi_p2p_noa_event(struct ath12k_base *ab,
unlock:
rcu_read_unlock();
-out:
- kfree(tb);
return ret;
}
@@ -8938,7 +8871,7 @@ static void ath12k_rfkill_state_change_event(struct ath12k_base *ab,
const void **tb;
int ret;
- tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
+ tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
@@ -8946,10 +8879,8 @@ static void ath12k_rfkill_state_change_event(struct ath12k_base *ab,
}
ev = tb[WMI_TAG_RFKILL_EVENT];
- if (!ev) {
- kfree(tb);
+ if (!ev)
return;
- }
ath12k_dbg(ab, ATH12K_DBG_MAC,
"wmi tlv rfkill state change gpio %d type %d radio_state %d\n",
@@ -8962,7 +8893,6 @@ static void ath12k_rfkill_state_change_event(struct ath12k_base *ab,
spin_unlock_bh(&ab->base_lock);
queue_work(ab->workqueue, &ab->rfkill_work);
- kfree(tb);
}
static void
@@ -8978,7 +8908,7 @@ static void ath12k_wmi_twt_enable_event(struct ath12k_base *ab,
const struct wmi_twt_enable_event *ev;
int ret;
- tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
+ tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse wmi twt enable status event tlv: %d\n",
@@ -8989,15 +8919,12 @@ static void ath12k_wmi_twt_enable_event(struct ath12k_base *ab,
ev = tb[WMI_TAG_TWT_ENABLE_COMPLETE_EVENT];
if (!ev) {
ath12k_warn(ab, "failed to fetch twt enable wmi event\n");
- goto exit;
+ return;
}
ath12k_dbg(ab, ATH12K_DBG_MAC, "wmi twt enable event pdev id %u status %u\n",
le32_to_cpu(ev->pdev_id),
le32_to_cpu(ev->status));
-
-exit:
- kfree(tb);
}
static void ath12k_wmi_twt_disable_event(struct ath12k_base *ab,
@@ -9007,7 +8934,7 @@ static void ath12k_wmi_twt_disable_event(struct ath12k_base *ab,
const struct wmi_twt_disable_event *ev;
int ret;
- tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
+ tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse wmi twt disable status event tlv: %d\n",
@@ -9018,15 +8945,12 @@ static void ath12k_wmi_twt_disable_event(struct ath12k_base *ab,
ev = tb[WMI_TAG_TWT_DISABLE_COMPLETE_EVENT];
if (!ev) {
ath12k_warn(ab, "failed to fetch twt disable wmi event\n");
- goto exit;
+ return;
}
ath12k_dbg(ab, ATH12K_DBG_MAC, "wmi twt disable event pdev id %d status %u\n",
le32_to_cpu(ev->pdev_id),
le32_to_cpu(ev->status));
-
-exit:
- kfree(tb);
}
static int ath12k_wmi_wow_wakeup_host_parse(struct ath12k_base *ab,
@@ -9099,7 +9023,7 @@ static void ath12k_wmi_gtk_offload_status_event(struct ath12k_base *ab,
const void **tb;
int ret;
- tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
+ tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse tlv: %d\n", ret);
@@ -9109,7 +9033,6 @@ static void ath12k_wmi_gtk_offload_status_event(struct ath12k_base *ab,
ev = tb[WMI_TAG_GTK_OFFLOAD_STATUS_EVENT];
if (!ev) {
ath12k_warn(ab, "failed to fetch gtk offload status ev");
- kfree(tb);
return;
}
@@ -9119,7 +9042,6 @@ static void ath12k_wmi_gtk_offload_status_event(struct ath12k_base *ab,
rcu_read_unlock();
ath12k_warn(ab, "failed to get arvif for vdev_id:%d\n",
le32_to_cpu(ev->vdev_id));
- kfree(tb);
return;
}
@@ -9135,8 +9057,6 @@ static void ath12k_wmi_gtk_offload_status_event(struct ath12k_base *ab,
(void *)&replay_ctr_be, GFP_ATOMIC);
rcu_read_unlock();
-
- kfree(tb);
}
static void ath12k_wmi_event_mlo_setup_complete(struct ath12k_base *ab,
@@ -9148,7 +9068,7 @@ static void ath12k_wmi_event_mlo_setup_complete(struct ath12k_base *ab,
const void **tb;
int ret, i;
- tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
+ tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse mlo setup complete event tlv: %d\n",
@@ -9159,7 +9079,6 @@ static void ath12k_wmi_event_mlo_setup_complete(struct ath12k_base *ab,
ev = tb[WMI_TAG_MLO_SETUP_COMPLETE_EVENT];
if (!ev) {
ath12k_warn(ab, "failed to fetch mlo setup complete event\n");
- kfree(tb);
return;
}
@@ -9178,14 +9097,11 @@ static void ath12k_wmi_event_mlo_setup_complete(struct ath12k_base *ab,
if (!ar) {
ath12k_warn(ab, "invalid pdev_id %d status %u in setup complete event\n",
ev->pdev_id, ev->status);
- goto out;
+ return;
}
ar->mlo_setup_status = le32_to_cpu(ev->status);
complete(&ar->mlo_setup_done);
-
-out:
- kfree(tb);
}
static void ath12k_wmi_event_teardown_complete(struct ath12k_base *ab,
@@ -9195,7 +9111,7 @@ static void ath12k_wmi_event_teardown_complete(struct ath12k_base *ab,
const void **tb;
int ret;
- tb = ath12k_wmi_tlv_parse_alloc(ab, skb, GFP_ATOMIC);
+ tb = ath12k_wmi_tlv_parse(ab, skb);
if (IS_ERR(tb)) {
ret = PTR_ERR(tb);
ath12k_warn(ab, "failed to parse teardown complete event tlv: %d\n", ret);
@@ -9203,13 +9119,8 @@ static void ath12k_wmi_event_teardown_complete(struct ath12k_base *ab,
}
ev = tb[WMI_TAG_MLO_TEARDOWN_COMPLETE];
- if (!ev) {
+ if (!ev)
ath12k_warn(ab, "failed to fetch teardown complete event\n");
- kfree(tb);
- return;
- }
-
- kfree(tb);
}
#ifdef CONFIG_ATH12K_DEBUGFS
@@ -11239,3 +11150,31 @@ int ath12k_wmi_send_mlo_link_set_active_cmd(struct ath12k_base *ab,
dev_kfree_skb(skb);
return ret;
}
+
+int ath12k_wmi_alloc(void)
+{
+ guard(mutex)(&ath12k_wmi_mutex);
+
+ if (!ath12k_wmi_tb) {
+ ath12k_wmi_tb = __alloc_percpu(WMI_TAG_MAX * sizeof(void *),
+ __alignof__(void *));
+ if (!ath12k_wmi_tb)
+ return -ENOMEM;
+
+ refcount_set(&ath12k_wmi_refcount, 1);
+ } else {
+ refcount_inc(&ath12k_wmi_refcount);
+ }
+
+ return 0;
+}
+
+void ath12k_wmi_free(void)
+{
+ guard(mutex)(&ath12k_wmi_mutex);
+
+ if (refcount_dec_and_test(&ath12k_wmi_refcount)) {
+ free_percpu(ath12k_wmi_tb);
+ ath12k_wmi_tb = NULL;
+ }
+}
diff --git a/drivers/net/wireless/ath/ath12k/wmi.h b/drivers/net/wireless/ath/ath12k/wmi.h
index 5ba9b7d3a888..4a34b2ca99ea 100644
--- a/drivers/net/wireless/ath/ath12k/wmi.h
+++ b/drivers/net/wireless/ath/ath12k/wmi.h
@@ -6576,4 +6576,7 @@ int ath12k_wmi_send_vdev_set_tpc_power(struct ath12k *ar,
struct ath12k_reg_tpc_power_info *param);
int ath12k_wmi_send_mlo_link_set_active_cmd(struct ath12k_base *ab,
struct wmi_mlo_link_set_active_arg *param);
+int ath12k_wmi_alloc(void);
+void ath12k_wmi_free(void);
+
#endif
--
2.53.0
^ permalink raw reply related
* Re: [PATCH ath-next v6] wifi: ath12k: avoid dynamic alloc when parsing wmi tb
From: Nicolas Escande @ 2026-04-07 9:43 UTC (permalink / raw)
To: Rameshkumar Sundaram, Nicolas Escande, ath12k; +Cc: linux-wireless
In-Reply-To: <54887455-3ae5-4dc9-a0cf-80726a1d8465@oss.qualcomm.com>
On Sat Apr 4, 2026 at 11:48 AM CEST, Rameshkumar Sundaram wrote:
[...]
>> @@ -134,6 +136,10 @@ struct wmi_pdev_set_obss_bitmap_arg {
>> const char *label;
>> };
>>
>> +static DEFINE_MUTEX(ath12k_wmi_mutex);
>> +static refcount_t ath12k_wmi_refcount = REFCOUNT_INIT(0);
>> +static void __percpu *ath12k_wmi_tb = NULL;
>
> Checkpatch complains:
> drivers/net/wireless/ath/ath12k/wmi.c:141: do not initialise statics to NULL
I forgot to run checkpatch on this one.
>
>> +
>> static const struct ath12k_wmi_tlv_policy ath12k_wmi_tlv_policies[] = {
>> [WMI_TAG_ARRAY_BYTE] = { .min_len = 0 },
>> [WMI_TAG_ARRAY_UINT32] = { .min_len = 0 },
[...]
>> @@ -11239,3 +11150,28 @@ int ath12k_wmi_send_mlo_link_set_active_cmd(struct ath12k_base *ab,
>> dev_kfree_skb(skb);
>> return ret;
>> }
>> +
>> +int ath12k_wmi_alloc(void)
>> +{
>> + guard(mutex)(&ath12k_wmi_mutex);
>> +
>> + if (!ath12k_wmi_tb) {
>> + ath12k_wmi_tb = __alloc_percpu(WMI_TAG_MAX * sizeof(void *),
>> + __alignof__(void *));
>> + if (!ath12k_wmi_tb)
>> + return -ENOMEM;
>> + }
>> +
>> + refcount_inc(&ath12k_wmi_refcount);
>
> With ath12k_wmi_refcount initialized to zero, this will trigger addition
> on 0 - use-after-free warning in refcount.
Arf, that a bummer.
>
> We may have to do refcount_set(&ath12k_wmi_refcount, 1); on tb alloc.
>
It seems thats the best way to go forward indeed.
>
>> + return 0;
>> +}
>> +
>> +void ath12k_wmi_free(void)
>> +{
>> + guard(mutex)(&ath12k_wmi_mutex);
>> +
>> + if (refcount_dec_and_test(&ath12k_wmi_refcount)) {
>> + free_percpu(ath12k_wmi_tb);
>> + ath12k_wmi_tb = NULL;
>> + }
>> +}
^ permalink raw reply
* Re: [PATCH v2 5/7] arm64: dts: qcom: milos: Add WCN6755 WiFi node
From: Konrad Dybcio @ 2026-04-07 9:35 UTC (permalink / raw)
To: Luca Weiss, Bjorn Andersson, Konrad Dybcio, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Alexander Koskovich,
Liam Girdwood, Mark Brown, Bartosz Golaszewski, Marcel Holtmann,
Luiz Augusto von Dentz, Balakrishna Godavarthi, Rocky Liao,
Johannes Berg, Jeff Johnson
Cc: ~postmarketos/upstreaming, phone-devel, linux-arm-msm,
linux-kernel, devicetree, linux-bluetooth, linux-wireless, ath11k
In-Reply-To: <20260403-milos-fp6-bt-wifi-v2-5-393322b27c5f@fairphone.com>
On 4/3/26 3:52 PM, Luca Weiss wrote:
> Add a node for the WCN6755 WiFi found with the Milos SoC.
>
> Signed-off-by: Luca Weiss <luca.weiss@fairphone.com>
> ---
Acked-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
Konrad
^ permalink raw reply
* Re: [PATCH ath-next] wifi: ath12k: Support channel change stats
From: Rameshkumar Sundaram @ 2026-04-07 9:18 UTC (permalink / raw)
To: Roopni Devanathan, ath12k; +Cc: linux-wireless, Harish Rachakonda
In-Reply-To: <20260326050641.3066562-1-roopni.devanathan@oss.qualcomm.com>
On 3/26/2026 10:36 AM, Roopni Devanathan wrote:
> From: Harish Rachakonda <quic_rachakon@quicinc.com>
>
> Add support to request channel change stats from the firmware through
> HTT stats type 76. These stats give channel switch details like the
> channel that the radio changed to, its center frequency, time taken
> for the switch, chainmask details, etc.
>
> Sample output:
> echo 76 > /sys/kernel/debug/ath12k/pci-0000\:06\:00.0/mac0/htt_stats_type
> cat /sys/kernel/debug/ath12k/pci-0000\:06\:00.0/mac0/htt_stats
> Channel Change Timings:
> |PRIMARY CHANNEL FREQ|BANDWIDTH CENTER FREQ|PHYMODE|TX_CHAINMASK|RX_CHAINMASK|SWITCH TIME(us)|INI(us)|TPC+CTL(us)|CAL(us)|MISC(us)|CTL(us)|SW PROFILE|
> | 5200| 5200| 24| 15| 15| 448850| 2410| 10546| 434593| 1071| 1100| 4|
> | 5240| 5240| 24| 15| 15| 450730| 4106| 10524| 434528| 1306| 1150| 4|
> | 5180| 5210| 26| 15| 15| 467894| 4764| 10438| 451101| 1337| 1508| 4|
> | 5200| 5200| 0| 15| 15| 13838| 2692| 1736| 8558| 686| 802| 6|
> | 5180| 5180| 0| 15| 15| 13465| 3207| 855| 8579| 578| 760| 6|
> | 5200| 5200| 24| 15| 15| 570321| 2441| 10439| 555661| 1574| 949| 4|
>
> Note: WCN7850 firmware does not support HTT stats type 76.
>
> Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01181-QCAHKSWPL_SILICONZ-1
>
> Signed-off-by: Harish Rachakonda <quic_rachakon@quicinc.com>
> Signed-off-by: Roopni Devanathan <roopni.devanathan@oss.qualcomm.com>
> ---
> .../wireless/ath/ath12k/debugfs_htt_stats.c | 72 +++++++++++++++++++
> .../wireless/ath/ath12k/debugfs_htt_stats.h | 26 +++++++
> 2 files changed, 98 insertions(+)
>
> diff --git a/drivers/net/wireless/ath/ath12k/debugfs_htt_stats.c b/drivers/net/wireless/ath/ath12k/debugfs_htt_stats.c
> index 7f6ca07fb335..b772181a496e 100644
> --- a/drivers/net/wireless/ath/ath12k/debugfs_htt_stats.c
> +++ b/drivers/net/wireless/ath/ath12k/debugfs_htt_stats.c
> @@ -5722,6 +5722,75 @@ ath12k_htt_print_tx_hwq_stats_cmn_tlv(const void *tag_buf, u16 tag_len,
> stats_req->buf_len = len;
> }
>
> +static void
> +ath12k_htt_print_chan_switch_stats_tlv(const void *tag_buf, u16 tag_len,
> + struct debug_htt_stats_req *stats_req)
> +{
> + const struct ath12k_htt_chan_switch_stats_tlv *sbuf = tag_buf;
> + u32 buf_len = ATH12K_HTT_STATS_BUF_SIZE;
> + u32 switch_freq, switch_profile;
> + u32 len = stats_req->buf_len;
> + u8 *buf = stats_req->buf;
> + u8 i;
> +
> + if (tag_len < sizeof(*sbuf))
> + return;
> +
> + i = min(le32_to_cpu(sbuf->switch_count), ATH12K_HTT_CHAN_SWITCH_STATS_BUF_LEN);
> + if (!i)
> + return;
> +
> + len += scnprintf(buf + len, buf_len - len, "Channel Change Timings:\n");
> + len += scnprintf(buf + len, buf_len - len,
> + "|%-20s|%-21s|%-7s|%-12s|%-12s|%-15s|",
> + "PRIMARY CHANNEL FREQ", "BANDWIDTH CENTER FREQ", "PHYMODE",
> + "TX_CHAINMASK", "RX_CHAINMASK", "SWITCH TIME(us)");
> + len += scnprintf(buf + len, buf_len - len,
> + "%-7s|%-11s|%-7s|%-8s|%-7s|%-10s|\n",
> + "INI(us)", "TPC+CTL(us)", "CAL(us)", "MISC(us)", "CTL(us)",
> + "SW PROFILE");
> +
> + /*
> + * sbuf->switch_count has the number of successful channel changes. The firmware
> + * sends the record of channel change in such a way that sbuf->chan_stats[0] will
> + * point to the channel change that occurred first and the recent channel change
> + * records will be stored in sbuf->chan_stats[9]. As and when new channel change
> + * occurs, sbuf->chan_stats[0] will be replaced by records from the next index,
> + * sbuf->chan_stats[1]. While printing the records, reverse chronological order
> + * is followed, i.e., the most recent channel change records are printed first
> + * and the oldest one, last.
> + */
> + while (i--) {
> + switch_freq = le32_to_cpu(sbuf->chan_stats[i].chan_switch_freq);
> + switch_profile = le32_to_cpu(sbuf->chan_stats[i].chan_switch_profile);
> +
> + len += scnprintf(buf + len, buf_len - len,
> + "|%20u|%21u|%7u|%12u|%12u|%15u|",
> + u32_get_bits(switch_freq,
> + ATH12K_HTT_STATS_CHAN_SWITCH_BW_MHZ),
> + u32_get_bits(switch_freq,
> + ATH12K_HTT_STATS_CHAN_SWITCH_BAND_FREQ),
> + u32_get_bits(switch_profile,
> + ATH12K_HTT_STATS_CHAN_SWITCH_PHY_MODE),
> + u32_get_bits(switch_profile,
> + ATH12K_HTT_STATS_CHAN_SWITCH_TX_CHAINMASK),
> + u32_get_bits(switch_profile,
> + ATH12K_HTT_STATS_CHAN_SWITCH_RX_CHAINMASK),
> + le32_to_cpu(sbuf->chan_stats[i].chan_switch_time));
> + len += scnprintf(buf + len, buf_len - len,
> + "%7u|%11u|%7u|%8u|%7u|%10u|\n",
> + le32_to_cpu(sbuf->chan_stats[i].ini_module_time),
> + le32_to_cpu(sbuf->chan_stats[i].tpc_module_time),
> + le32_to_cpu(sbuf->chan_stats[i].cal_module_time),
> + le32_to_cpu(sbuf->chan_stats[i].misc_module_time),
> + le32_to_cpu(sbuf->chan_stats[i].ctl_module_time),
> + u32_get_bits(switch_profile,
> + ATH12K_HTT_STATS_CHAN_SWITCH_SW_PROFILE));
> + }
> +
> + stats_req->buf_len = len;
> +}
> +
> static int ath12k_dbg_htt_ext_stats_parse(struct ath12k_base *ab,
> u16 tag, u16 len, const void *tag_buf,
> void *user_data)
> @@ -6024,6 +6093,9 @@ static int ath12k_dbg_htt_ext_stats_parse(struct ath12k_base *ab,
> case HTT_STATS_TX_HWQ_CMN_TAG:
> ath12k_htt_print_tx_hwq_stats_cmn_tlv(tag_buf, len, stats_req);
> break;
> + case HTT_STATS_CHAN_SWITCH_STATS_TAG:
> + ath12k_htt_print_chan_switch_stats_tlv(tag_buf, len, stats_req);
> + break;
> default:
> break;
> }
> diff --git a/drivers/net/wireless/ath/ath12k/debugfs_htt_stats.h b/drivers/net/wireless/ath/ath12k/debugfs_htt_stats.h
> index bfabe6500d44..82ab7b9e4db9 100644
> --- a/drivers/net/wireless/ath/ath12k/debugfs_htt_stats.h
> +++ b/drivers/net/wireless/ath/ath12k/debugfs_htt_stats.h
> @@ -164,6 +164,7 @@ enum ath12k_dbg_htt_ext_stats_type {
> ATH12K_DBG_HTT_PDEV_MLO_IPC_STATS = 64,
> ATH12K_DBG_HTT_EXT_PDEV_RTT_RESP_STATS = 65,
> ATH12K_DBG_HTT_EXT_PDEV_RTT_INITIATOR_STATS = 66,
> + ATH12K_DBG_HTT_EXT_CHAN_SWITCH_STATS = 76,
>
> /* keep this last */
> ATH12K_DBG_HTT_NUM_EXT_STATS,
> @@ -267,6 +268,7 @@ enum ath12k_dbg_htt_tlv_tag {
> HTT_STATS_PDEV_RTT_HW_STATS_TAG = 196,
> HTT_STATS_PDEV_RTT_TBR_SELFGEN_QUEUED_STATS_TAG = 197,
> HTT_STATS_PDEV_RTT_TBR_CMD_RESULT_STATS_TAG = 198,
> + HTT_STATS_CHAN_SWITCH_STATS_TAG = 213,
>
> HTT_STATS_MAX_TAG,
> };
> @@ -2156,4 +2158,28 @@ struct htt_tx_hwq_stats_cmn_tlv {
> __le32 txq_timeout;
> } __packed;
>
> +#define ATH12K_HTT_CHAN_SWITCH_STATS_BUF_LEN 10
> +
> +#define ATH12K_HTT_STATS_CHAN_SWITCH_BW_MHZ GENMASK(15, 0)
> +#define ATH12K_HTT_STATS_CHAN_SWITCH_BAND_FREQ GENMASK(31, 16)
> +#define ATH12K_HTT_STATS_CHAN_SWITCH_PHY_MODE GENMASK(7, 0)
> +#define ATH12K_HTT_STATS_CHAN_SWITCH_TX_CHAINMASK GENMASK(15, 8)
> +#define ATH12K_HTT_STATS_CHAN_SWITCH_RX_CHAINMASK GENMASK(23, 16)
> +#define ATH12K_HTT_STATS_CHAN_SWITCH_SW_PROFILE GENMASK(31, 24)
> +
> +struct ath12k_htt_chan_switch_stats_tlv {
> + struct {
> + __le32 chan_switch_freq;
> + __le32 chan_switch_profile;
> + __le32 chan_switch_time;
> + __le32 cal_module_time;
> + __le32 ini_module_time;
> + __le32 tpc_module_time;
> + __le32 misc_module_time;
> + __le32 ctl_module_time;
> + __le32 reserved;
> + } chan_stats[ATH12K_HTT_CHAN_SWITCH_STATS_BUF_LEN];
> + __le32 switch_count; /* shows how many channel changes have occurred */
> +} __packed;
> +
> #endif
>
> base-commit: 37538641dac955f6690372f0ebb94e5e14a23418
Reviewed-by: Rameshkumar Sundaram <rameshkumar.sundaram@oss.qualcomm.com>
^ permalink raw reply
* Re: [PATCH v10 00/21] wifi: nxpwifi: create nxpwifi to support
From: Johannes Berg @ 2026-04-07 9:06 UTC (permalink / raw)
To: Jeff Chen; +Cc: linux-wireless, linux-kernel, francesco, wyatt.hsu, s.hauer
In-Reply-To: <acPm666IO+nso5to@nxpwireless-Inspiron-14-Plus-7440>
On Wed, 2026-03-25 at 21:45 +0800, Jeff Chen wrote:
> Ulf prefers not to rebase or drop the SDIO ID patch from the mmc tree,
> and his suggestion is for me to include an identical copy of that patch
> in the nxpwifi series for wireless-next, so that the driver can build
> independently during review. Since the change is trivial, he expects git
> to resolve the duplication automatically when the wireless and mmc trees
> are merged.
> Would this approach be acceptable for wireless-next?
Yeah I guess we don't have a choice ...
> For reference, please see Ulf’s feedback here:
>
> https://patchwork.kernel.org/project/linux-mmc/patch/20260113031517.244714-1-jeff.chen_1@nxp.com/
So I think we said before that for final merge you should send a pull
request anyway instead of all the individual patches.
Can you include it in the pull request, as a precise cherry-pick from
his tree?
johannes
^ permalink raw reply
* Re: [PATCH 0/3] EML Capabilities compliance changes
From: Johannes Berg @ 2026-04-07 9:00 UTC (permalink / raw)
To: Pablo Martin-Gomez; +Cc: linux-wireless
In-Reply-To: <20260327201135.905852-1-pmartin-gomez@freebox.fr>
On Fri, 2026-03-27 at 21:11 +0100, Pablo Martin-Gomez wrote:
> There is some discrepencies between our codebase and the final version
> of 802.11be-2024 regarding the EML Capabilities field. Given that no
> driver supports EMLMR or tries to use 128TUs transition timeout, those
> changes should not have any real impact.
Heh, funny, I noticed this a while ago too - it must've been in an
earlier draft. And UHR has the same - but *does* (for now :) ) define
128 TU (which is why I noticed it.)
johannes
^ permalink raw reply
* Re: [PATCH wireless-next 6/6] crypto: Remove michael_mic from crypto_shash API
From: Geert Uytterhoeven @ 2026-04-07 7:53 UTC (permalink / raw)
To: Eric Biggers
Cc: Johannes Berg, linux-wireless, linux-crypto, linux-kernel,
Herbert Xu
In-Reply-To: <20260405052734.130368-7-ebiggers@kernel.org>
On Sun, 5 Apr 2026 at 07:32, Eric Biggers <ebiggers@kernel.org> wrote:
> Remove the "michael_mic" crypto_shash algorithm, since it's no longer
> used. Its only users were wireless drivers, which have now been
> converted to use the michael_mic() function instead.
>
> It makes sense that no other users ever appeared: Michael MIC is an
> insecure algorithm that is specific to WPA TKIP, which itself was an
> interim security solution to replace the broken WEP standard.
>
> Signed-off-by: Eric Biggers <ebiggers@kernel.org>
> arch/m68k/configs/amiga_defconfig | 1 -
> arch/m68k/configs/apollo_defconfig | 1 -
> arch/m68k/configs/atari_defconfig | 1 -
> arch/m68k/configs/bvme6000_defconfig | 1 -
> arch/m68k/configs/hp300_defconfig | 1 -
> arch/m68k/configs/mac_defconfig | 1 -
> arch/m68k/configs/multi_defconfig | 1 -
> arch/m68k/configs/mvme147_defconfig | 1 -
> arch/m68k/configs/mvme16x_defconfig | 1 -
> arch/m68k/configs/q40_defconfig | 1 -
> arch/m68k/configs/sun3_defconfig | 1 -
> arch/m68k/configs/sun3x_defconfig | 1 -
Acked-by: Geert Uytterhoeven <geert@linux-m68k.org>
(although these would be removed during next defconfig refresh anyway)
Gr{oetje,eeting}s,
Geert
--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org
In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds
^ permalink raw reply
* [PATCH v5 3/3] arm64: dts: qcom: sdm845-xiaomi-beryllium: Enable ath10k host-cap skip quirk
From: David Heidelberg via B4 Relay @ 2026-04-07 6:43 UTC (permalink / raw)
To: Johannes Berg, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jeff Johnson, Bjorn Andersson, Konrad Dybcio, Paul Sajna
Cc: Baochen Qiang, Vasanthakumar Thiagarajan, Dmitry Baryshkov,
Amit Pundir, linux-wireless, devicetree, ath10k, linux-kernel,
linux-arm-msm, phone-devel, David Heidelberg
In-Reply-To: <20260407-skip-host-cam-qmi-req-v5-0-dfa8a05c6538@ixit.cz>
From: Amit Pundir <amit.pundir@linaro.org>
The Wi-Fi firmware used on Xiaomi Poco F1 (beryllium) phone doesn't
support the host-capability QMI request, so add a quirk to skip it on
this device.
Signed-off-by: Amit Pundir <amit.pundir@linaro.org>
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
Signed-off-by: David Heidelberg <david@ixit.cz>
---
arch/arm64/boot/dts/qcom/sdm845-xiaomi-beryllium-common.dtsi | 1 +
1 file changed, 1 insertion(+)
diff --git a/arch/arm64/boot/dts/qcom/sdm845-xiaomi-beryllium-common.dtsi b/arch/arm64/boot/dts/qcom/sdm845-xiaomi-beryllium-common.dtsi
index 1298485c42142..950bbcc3bf91f 100644
--- a/arch/arm64/boot/dts/qcom/sdm845-xiaomi-beryllium-common.dtsi
+++ b/arch/arm64/boot/dts/qcom/sdm845-xiaomi-beryllium-common.dtsi
@@ -661,5 +661,6 @@ &wifi {
vdd-3.3-ch1-supply = <&vreg_l23a_3p3>;
qcom,calibration-variant = "xiaomi_beryllium";
+ qcom,snoc-host-cap-skip-quirk;
};
--
2.53.0
^ permalink raw reply related
* [PATCH v5 0/3] ath10k: Introduce a devicetree quirk to skip host cap QMI requests
From: David Heidelberg via B4 Relay @ 2026-04-07 6:43 UTC (permalink / raw)
To: Johannes Berg, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jeff Johnson, Bjorn Andersson, Konrad Dybcio, Paul Sajna
Cc: Baochen Qiang, Vasanthakumar Thiagarajan, Dmitry Baryshkov,
Amit Pundir, linux-wireless, devicetree, ath10k, linux-kernel,
linux-arm-msm, phone-devel, David Heidelberg
This quirk is used so far used on:
- LG G7 ThinQ
- Xiaomi Poco F1
I'm resending it after ~ 4 years since initial send due to Snapdragon
845 being one of best supported platform for mobile phones running
Linux, so it would be shame to not have shiny support.
Original thread:
https://lore.kernel.org/all/b796bfee-b753-479a-a8d6-ba1fe3ee6222@ixit.cz/
I tried the embedding the information inside the firmware, but the
information is required *before* loading the firmware itself.
Firmware quirk thread:
https://lore.kernel.org/linux-wireless/20251111-xiaomi-beryllium-firmware-v1-0-836b9c51ad86@ixit.cz/
Until merged, available also at:
https://codeberg.org/sdm845/linux/commits/branch/b4/skip-host-cam-qmi-req
Signed-off-by: David Heidelberg <david@ixit.cz>
---
Changes in v5:
- Implement device-tree mutual-exclusion between
snoc-host-cap-8bit-quirk and snoc-host-cap-skip-quirk. (Krzysztof)
- Link to v4: https://lore.kernel.org/r/20260325-skip-host-cam-qmi-req-v4-0-bc08538487aa@ixit.cz
Changes in v4:
- Added my own missing SoB. (Dmitry)
- Improve the commit message. (Dmitry)
- Link to v3: https://lore.kernel.org/r/20260325-skip-host-cam-qmi-req-v3-0-b163cf7b3c81@ixit.cz
Changes in v3:
- Rebased on recent linux-next (next-20260325).
- Improved motivation and description. (Dmitry)
- Link to v2: https://lore.kernel.org/r/20251110-skip-host-cam-qmi-req-v2-0-0daf485a987a@ixit.cz
---
Amit Pundir (3):
dt-bindings: wireless: ath10k: Add quirk to skip host cap QMI requests
ath10k: Add device-tree quirk to skip host cap QMI requests
arm64: dts: qcom: sdm845-xiaomi-beryllium: Enable ath10k host-cap skip quirk
.../devicetree/bindings/net/wireless/qcom,ath10k.yaml | 11 +++++++++++
.../arm64/boot/dts/qcom/sdm845-xiaomi-beryllium-common.dtsi | 1 +
drivers/net/wireless/ath/ath10k/qmi.c | 13 ++++++++++---
drivers/net/wireless/ath/ath10k/snoc.c | 3 +++
drivers/net/wireless/ath/ath10k/snoc.h | 1 +
5 files changed, 26 insertions(+), 3 deletions(-)
---
base-commit: 816f193dd0d95246f208590924dd962b192def78
change-id: 20251110-skip-host-cam-qmi-req-e155628ebc39
Best regards,
--
David Heidelberg <david@ixit.cz>
^ permalink raw reply
* [PATCH v5 1/3] dt-bindings: wireless: ath10k: Add quirk to skip host cap QMI requests
From: David Heidelberg via B4 Relay @ 2026-04-07 6:43 UTC (permalink / raw)
To: Johannes Berg, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jeff Johnson, Bjorn Andersson, Konrad Dybcio, Paul Sajna
Cc: Baochen Qiang, Vasanthakumar Thiagarajan, Dmitry Baryshkov,
Amit Pundir, linux-wireless, devicetree, ath10k, linux-kernel,
linux-arm-msm, phone-devel, David Heidelberg
In-Reply-To: <20260407-skip-host-cam-qmi-req-v5-0-dfa8a05c6538@ixit.cz>
From: Amit Pundir <amit.pundir@linaro.org>
Some firmware versions do not support the host-capability QMI request.
Since this request occurs before firmware and board files are loaded,
the quirk cannot be expressed in the firmware itself and must be described
in the device tree.
Signed-off-by: Amit Pundir <amit.pundir@linaro.org>
Co-developed-by: David Heidelberg <david@ixit.cz>
Signed-off-by: David Heidelberg <david@ixit.cz>
---
.../devicetree/bindings/net/wireless/qcom,ath10k.yaml | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/Documentation/devicetree/bindings/net/wireless/qcom,ath10k.yaml b/Documentation/devicetree/bindings/net/wireless/qcom,ath10k.yaml
index f2440d39b7ebc..c21d66c7cd558 100644
--- a/Documentation/devicetree/bindings/net/wireless/qcom,ath10k.yaml
+++ b/Documentation/devicetree/bindings/net/wireless/qcom,ath10k.yaml
@@ -171,6 +171,12 @@ properties:
Quirk specifying that the firmware expects the 8bit version
of the host capability QMI request
+ qcom,snoc-host-cap-skip-quirk:
+ type: boolean
+ description:
+ Quirk specifying that the firmware wants to skip the host
+ capability QMI request
+
qcom,xo-cal-data:
$ref: /schemas/types.yaml#/definitions/uint32
description:
@@ -292,6 +298,11 @@ allOf:
required:
- interrupts
+ - not:
+ required:
+ - qcom,snoc-host-cap-8bit-quirk
+ - qcom,snoc-host-cap-skip-quirk
+
examples:
# SNoC
- |
--
2.53.0
^ permalink raw reply related
* [PATCH v5 2/3] ath10k: Add device-tree quirk to skip host cap QMI requests
From: David Heidelberg via B4 Relay @ 2026-04-07 6:43 UTC (permalink / raw)
To: Johannes Berg, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jeff Johnson, Bjorn Andersson, Konrad Dybcio, Paul Sajna
Cc: Baochen Qiang, Vasanthakumar Thiagarajan, Dmitry Baryshkov,
Amit Pundir, linux-wireless, devicetree, ath10k, linux-kernel,
linux-arm-msm, phone-devel, David Heidelberg
In-Reply-To: <20260407-skip-host-cam-qmi-req-v5-0-dfa8a05c6538@ixit.cz>
From: Amit Pundir <amit.pundir@linaro.org>
Some firmware versions do not support the host capability QMI request.
Since this request occurs before firmware-N.bin and board-M.bin are
loaded, the quirk cannot be expressed in the firmware itself.
The root cause is unclear, but there appears to be a generation of
firmware that lacks host capability support.
Without this quirk, ath10k_qmi_host_cap_send_sync() returns
QMI_ERR_MALFORMED_MSG_V01 before loading the firmware. This error is not
fatal - Wi-Fi services still come up successfully if the request is simply
skipped.
Add a device-tree quirk to skip the host capability QMI request on devices
whose firmware does not support it.
For example, firmware build
"QC_IMAGE_VERSION_STRING=WLAN.HL.2.0.c3-00257-QCAHLSWMTPLZ-1"
on Xiaomi Poco F1 phone requires this quirk.
Suggested-by: Bjorn Andersson <andersson@kernel.org>
Signed-off-by: Amit Pundir <amit.pundir@linaro.org>
Tested-by: Paul Sajna <sajattack@postmarketos.org>
Reviewed-by: Baochen Qiang <baochen.qiang@oss.qualcomm.com>
Reviewed-by: Vasanthakumar Thiagarajan <vasanthakumar.thiagarajan@oss.qualcomm.com>
Acked-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
Signed-off-by: David Heidelberg <david@ixit.cz>
---
drivers/net/wireless/ath/ath10k/qmi.c | 13 ++++++++++---
drivers/net/wireless/ath/ath10k/snoc.c | 3 +++
drivers/net/wireless/ath/ath10k/snoc.h | 1 +
3 files changed, 14 insertions(+), 3 deletions(-)
diff --git a/drivers/net/wireless/ath/ath10k/qmi.c b/drivers/net/wireless/ath/ath10k/qmi.c
index eebd78e7ff6bc..e7f90fd9e9b83 100644
--- a/drivers/net/wireless/ath/ath10k/qmi.c
+++ b/drivers/net/wireless/ath/ath10k/qmi.c
@@ -808,6 +808,7 @@ ath10k_qmi_ind_register_send_sync_msg(struct ath10k_qmi *qmi)
static void ath10k_qmi_event_server_arrive(struct ath10k_qmi *qmi)
{
struct ath10k *ar = qmi->ar;
+ struct ath10k_snoc *ar_snoc = ath10k_snoc_priv(ar);
int ret;
ret = ath10k_qmi_ind_register_send_sync_msg(qmi);
@@ -819,9 +820,15 @@ static void ath10k_qmi_event_server_arrive(struct ath10k_qmi *qmi)
return;
}
- ret = ath10k_qmi_host_cap_send_sync(qmi);
- if (ret)
- return;
+ /*
+ * Skip the host capability request for the firmware versions which
+ * do not support this feature.
+ */
+ if (!test_bit(ATH10K_SNOC_FLAG_SKIP_HOST_CAP_QUIRK, &ar_snoc->flags)) {
+ ret = ath10k_qmi_host_cap_send_sync(qmi);
+ if (ret)
+ return;
+ }
ret = ath10k_qmi_msa_mem_info_send_sync_msg(qmi);
if (ret)
diff --git a/drivers/net/wireless/ath/ath10k/snoc.c b/drivers/net/wireless/ath/ath10k/snoc.c
index f72f236fb9eb3..3106502275781 100644
--- a/drivers/net/wireless/ath/ath10k/snoc.c
+++ b/drivers/net/wireless/ath/ath10k/snoc.c
@@ -1362,6 +1362,9 @@ static void ath10k_snoc_quirks_init(struct ath10k *ar)
if (of_property_read_bool(dev->of_node, "qcom,snoc-host-cap-8bit-quirk"))
set_bit(ATH10K_SNOC_FLAG_8BIT_HOST_CAP_QUIRK, &ar_snoc->flags);
+
+ if (of_property_read_bool(dev->of_node, "qcom,snoc-host-cap-skip-quirk"))
+ set_bit(ATH10K_SNOC_FLAG_SKIP_HOST_CAP_QUIRK, &ar_snoc->flags);
}
int ath10k_snoc_fw_indication(struct ath10k *ar, u64 type)
diff --git a/drivers/net/wireless/ath/ath10k/snoc.h b/drivers/net/wireless/ath/ath10k/snoc.h
index 1ecae34687c21..46574fd8f84ee 100644
--- a/drivers/net/wireless/ath/ath10k/snoc.h
+++ b/drivers/net/wireless/ath/ath10k/snoc.h
@@ -51,6 +51,7 @@ enum ath10k_snoc_flags {
ATH10K_SNOC_FLAG_MODEM_STOPPED,
ATH10K_SNOC_FLAG_RECOVERY,
ATH10K_SNOC_FLAG_8BIT_HOST_CAP_QUIRK,
+ ATH10K_SNOC_FLAG_SKIP_HOST_CAP_QUIRK,
};
struct clk_bulk_data;
--
2.53.0
^ permalink raw reply related
* Re: [PATCH wireless-next 0/6] Consolidate Michael MIC code into mac80211
From: Christoph Hellwig @ 2026-04-07 6:33 UTC (permalink / raw)
To: Johannes Berg
Cc: Christoph Hellwig, Eric Biggers, linux-wireless, linux-crypto,
linux-kernel, Herbert Xu
In-Reply-To: <b4a8265d1814eb63be9a64ab4581439829f22fb0.camel@sipsolutions.net>
On Tue, Apr 07, 2026 at 08:28:20AM +0200, Johannes Berg wrote:
> On Mon, 2026-04-06 at 23:24 -0700, Christoph Hellwig wrote:
> > On Mon, Apr 06, 2026 at 11:15:08PM -0700, Eric Biggers wrote:
> > > Just to clarify, mac80211 already contains the michael_mic() function.
> > > And every driver that needs Michael MIC already depends on mac80211
> > > except for ipw2x00. So bloat-wise I assumed it's probably better to
> > > make that one driver depend on mac80211, rather than make every driver
> > > pull in the Michael MIC code (by moving it from mac80211 to cfg80211).
> > > But if you prefer that the code be in cfg80211 we can do it that way.
> >
> > To me the most sensible thing would be to have a separate module for
> > the code. If you don't want to expose it too widely for understandable
> > reasons, keep the module in net/wireless/.
> >
> Maybe, but that'd probably be more overhead than anything else? The
> text+data is 725 bytes (on x86-64).
Yeah. Just thinking out loud, if the wireless maintainers are fine with
the cگg80211 version that's probably fine.
^ permalink raw reply
* [PATCH] wifi: mac80211: remove unused variables in minstrel_ht_alloc_sta
From: Jiajia Liu @ 2026-04-07 6:32 UTC (permalink / raw)
To: Johannes Berg, linux-wireless, linux-kernel; +Cc: Jiajia Liu
Remove the unused variable max_rates and related code. Also remove the
variable mi and pass type to kzalloc_obj instead.
Signed-off-by: Jiajia Liu <liujiajia@kylinos.cn>
---
net/mac80211/rc80211_minstrel_ht.c | 15 +--------------
1 file changed, 1 insertion(+), 14 deletions(-)
diff --git a/net/mac80211/rc80211_minstrel_ht.c b/net/mac80211/rc80211_minstrel_ht.c
index 62745ca00e06..b73ef3adfcc5 100644
--- a/net/mac80211/rc80211_minstrel_ht.c
+++ b/net/mac80211/rc80211_minstrel_ht.c
@@ -1849,20 +1849,7 @@ minstrel_ht_rate_update(void *priv, struct ieee80211_supported_band *sband,
static void *
minstrel_ht_alloc_sta(void *priv, struct ieee80211_sta *sta, gfp_t gfp)
{
- struct ieee80211_supported_band *sband;
- struct minstrel_ht_sta *mi;
- struct minstrel_priv *mp = priv;
- struct ieee80211_hw *hw = mp->hw;
- int max_rates = 0;
- int i;
-
- for (i = 0; i < NUM_NL80211_BANDS; i++) {
- sband = hw->wiphy->bands[i];
- if (sband && sband->n_bitrates > max_rates)
- max_rates = sband->n_bitrates;
- }
-
- return kzalloc_obj(*mi, gfp);
+ return kzalloc_obj(struct minstrel_ht_sta, gfp);
}
static void
--
2.25.1
^ permalink raw reply related
* Re: [PATCH wireless-next 0/6] Consolidate Michael MIC code into mac80211
From: Johannes Berg @ 2026-04-07 6:28 UTC (permalink / raw)
To: Christoph Hellwig, Eric Biggers
Cc: linux-wireless, linux-crypto, linux-kernel, Herbert Xu
In-Reply-To: <adSjHlfi15v_U62B@infradead.org>
On Mon, 2026-04-06 at 23:24 -0700, Christoph Hellwig wrote:
> On Mon, Apr 06, 2026 at 11:15:08PM -0700, Eric Biggers wrote:
> > Just to clarify, mac80211 already contains the michael_mic() function.
> > And every driver that needs Michael MIC already depends on mac80211
> > except for ipw2x00. So bloat-wise I assumed it's probably better to
> > make that one driver depend on mac80211, rather than make every driver
> > pull in the Michael MIC code (by moving it from mac80211 to cfg80211).
> > But if you prefer that the code be in cfg80211 we can do it that way.
>
> To me the most sensible thing would be to have a separate module for
> the code. If you don't want to expose it too widely for understandable
> reasons, keep the module in net/wireless/.
>
Maybe, but that'd probably be more overhead than anything else? The
text+data is 725 bytes (on x86-64).
johannes
^ permalink raw reply
* Re: [PATCH wireless-next 0/6] Consolidate Michael MIC code into mac80211
From: Johannes Berg @ 2026-04-07 6:00 UTC (permalink / raw)
To: Eric Biggers, linux-wireless; +Cc: linux-crypto, linux-kernel, Herbert Xu
In-Reply-To: <20260405052734.130368-1-ebiggers@kernel.org>
Hi,
On Sat, 2026-04-04 at 22:27 -0700, Eric Biggers wrote:
> Michael MIC is an inherently weak algorithm that is specific to WPA
> TKIP, which itself was an interim security solution to replace the
> broken WEP standard.
Heh, yeah, we keep thinking about whether or not we can remove either of
them completely, but ... backward compatibility is messy.
> Seeing as Michael MIC is specific to WPA TKIP and should never be used
> elsewhere, this series migrates those few drivers to the mac80211
> implementation of Michael MIC, then removes the crypto implementation of
> Michael MIC. This consolidates duplicate code and prevents other kernel
> subsystems from accidentally using this insecure algorithm.
Makes sense, mostly.
The one thing that feels odd to me in this is moving it to *mac80211*
specifically, and then using that in the ancient drivers. Not only is
that a big module those don't (otherwise) need, but also it makes it
look like you need the softmac stack for those drivers, but they're
really hardmac so that's a bit confusing.
I wouldn't want to have a separate module just for this, but I think
since it's going to be exported anyway, we could move the whole
michael.c file to net/wireless/ and make it part of cfg80211. All
wireless drivers ought to depend on that anyway.
johannes
^ permalink raw reply
* Re: [PATCH wireless-next 0/6] Consolidate Michael MIC code into mac80211
From: Christoph Hellwig @ 2026-04-07 6:24 UTC (permalink / raw)
To: Eric Biggers
Cc: Johannes Berg, linux-wireless, linux-crypto, linux-kernel,
Herbert Xu
In-Reply-To: <20260407061508.GA7934@sol>
On Mon, Apr 06, 2026 at 11:15:08PM -0700, Eric Biggers wrote:
> Just to clarify, mac80211 already contains the michael_mic() function.
> And every driver that needs Michael MIC already depends on mac80211
> except for ipw2x00. So bloat-wise I assumed it's probably better to
> make that one driver depend on mac80211, rather than make every driver
> pull in the Michael MIC code (by moving it from mac80211 to cfg80211).
> But if you prefer that the code be in cfg80211 we can do it that way.
To me the most sensible thing would be to have a separate module for
the code. If you don't want to expose it too widely for understandable
reasons, keep the module in net/wireless/.
^ permalink raw reply
* Re: [PATCH wireless-next 0/6] Consolidate Michael MIC code into mac80211
From: Johannes Berg @ 2026-04-07 6:22 UTC (permalink / raw)
To: Eric Biggers; +Cc: linux-wireless, linux-crypto, linux-kernel, Herbert Xu
In-Reply-To: <20260407061508.GA7934@sol>
On Mon, 2026-04-06 at 23:15 -0700, Eric Biggers wrote:
> On Tue, Apr 07, 2026 at 08:00:53AM +0200, Johannes Berg wrote:
> > The one thing that feels odd to me in this is moving it to *mac80211*
> > specifically, and then using that in the ancient drivers. Not only is
> > that a big module those don't (otherwise) need, but also it makes it
> > look like you need the softmac stack for those drivers, but they're
> > really hardmac so that's a bit confusing.
> >
> > I wouldn't want to have a separate module just for this, but I think
> > since it's going to be exported anyway, we could move the whole
> > michael.c file to net/wireless/ and make it part of cfg80211. All
> > wireless drivers ought to depend on that anyway.
>
> Just to clarify, mac80211 already contains the michael_mic() function.
Yes, for the SW crypto there, which apparently was never ported to the
crypto functions.
> And every driver that needs Michael MIC already depends on mac80211
> except for ipw2x00.
I'm actually surprised it's any of those (actually) mac80211 based
drivers at all, since they ought to just be able to hand the skb to
mac80211 for checking. Not sure what's going on there, but I haven't
looked carefully either.
> So bloat-wise I assumed it's probably better to
> make that one driver depend on mac80211, rather than make every driver
> pull in the Michael MIC code (by moving it from mac80211 to cfg80211).
> But if you prefer that the code be in cfg80211 we can do it that way.
I think you're probably right, but it's a pretty small function and
architecturally, having those drivers depend on mac80211 but not
actually use any of its "real" functionality is IMHO somewhat confusing,
maybe especially from a Kconfig POV.
Also most drivers already use mac80211 so for those it makes no
difference, but of course there are a number of non-mac80211 drivers
that would get this function.
So I think overall, it still makes more sense in cfg80211 - we've been
treating that as not just nl80211 but also "useful things for drivers"
(for obvious reasons, every WiFi driver must use it), so that'd be the
better place I think.
johannes
^ permalink raw reply
* Re: [PATCH wireless-next 0/6] Consolidate Michael MIC code into mac80211
From: Eric Biggers @ 2026-04-07 6:15 UTC (permalink / raw)
To: Johannes Berg; +Cc: linux-wireless, linux-crypto, linux-kernel, Herbert Xu
In-Reply-To: <7f69d6e6c2057858eda5c65ec77be44d72c6ac78.camel@sipsolutions.net>
On Tue, Apr 07, 2026 at 08:00:53AM +0200, Johannes Berg wrote:
> The one thing that feels odd to me in this is moving it to *mac80211*
> specifically, and then using that in the ancient drivers. Not only is
> that a big module those don't (otherwise) need, but also it makes it
> look like you need the softmac stack for those drivers, but they're
> really hardmac so that's a bit confusing.
>
> I wouldn't want to have a separate module just for this, but I think
> since it's going to be exported anyway, we could move the whole
> michael.c file to net/wireless/ and make it part of cfg80211. All
> wireless drivers ought to depend on that anyway.
Just to clarify, mac80211 already contains the michael_mic() function.
And every driver that needs Michael MIC already depends on mac80211
except for ipw2x00. So bloat-wise I assumed it's probably better to
make that one driver depend on mac80211, rather than make every driver
pull in the Michael MIC code (by moving it from mac80211 to cfg80211).
But if you prefer that the code be in cfg80211 we can do it that way.
- Eric
^ permalink raw reply
* Re: [PATCH v4 1/3] dt-bindings: wireless: ath10k: Add quirk to skip host cap QMI requests
From: Krzysztof Kozlowski @ 2026-04-07 5:44 UTC (permalink / raw)
To: david, Johannes Berg, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Jeff Johnson, Bjorn Andersson, Konrad Dybcio,
Paul Sajna
Cc: Amit Pundir, linux-wireless, devicetree, ath10k, linux-kernel,
linux-arm-msm, phone-devel
In-Reply-To: <20260325-skip-host-cam-qmi-req-v4-1-bc08538487aa@ixit.cz>
On 25/03/2026 18:57, David Heidelberg via B4 Relay wrote:
> From: Amit Pundir <amit.pundir@linaro.org>
>
> Some firmware versions do not support the host-capability QMI request.
> Since this request occurs before firmware and board files are loaded,
> the quirk cannot be expressed in the firmware itself and must be described
> in the device tree.
>
> Signed-off-by: Amit Pundir <amit.pundir@linaro.org>
> Signed-off-by: David Heidelberg <david@ixit.cz>
> ---
> Documentation/devicetree/bindings/net/wireless/qcom,ath10k.yaml | 6 ++++++
> 1 file changed, 6 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/net/wireless/qcom,ath10k.yaml b/Documentation/devicetree/bindings/net/wireless/qcom,ath10k.yaml
> index f2440d39b7ebc..5120b3589ab57 100644
> --- a/Documentation/devicetree/bindings/net/wireless/qcom,ath10k.yaml
> +++ b/Documentation/devicetree/bindings/net/wireless/qcom,ath10k.yaml
> @@ -171,6 +171,12 @@ properties:
> Quirk specifying that the firmware expects the 8bit version
> of the host capability QMI request
>
> + qcom,snoc-host-cap-skip-quirk:
> + type: boolean
> + description:
> + Quirk specifying that the firmware wants to skip the host
> + capability QMI request
> +
Property is fine, looks conflicting with qcom,snoc-host-cap-8bit-quirk
though. Either you skip QMI or use specific format of that request.
You need
https://lore.kernel.org/all/20230118163208.GA117919-robh@kernel.org/
allOf:
- not:
required:
Best regards,
Krzysztof
^ permalink raw reply
* [PATCH wireless v3 3/3] wifi: mt76: mt7996: clear cipher state on key removal for WED offload
From: Joshua Klinesmith @ 2026-04-07 5:39 UTC (permalink / raw)
To: linux-wireless
Cc: nbd, lorenzo, ryder.lee, shayne.chen, sean.wang,
Joshua Klinesmith, stable
In-Reply-To: <20260407053917.75898-1-joshuaklinesmith@gmail.com>
Same issue as mt7915: link->mt76.cipher is set on key installation
but never cleared on removal. The WA firmware retains the stale
cipher in BSS_INFO, sets the protection bit on WED-offloaded
frames, and drops all plaintext traffic when encryption is
switched to open/none.
Reset link->mt76.cipher to zero and call mt7996_mcu_add_bss_info()
when the last group key is removed. The clearing is guarded by
checking that both hw_key_idx and hw_key_idx2 are unset (-1) so
that GTK rotation and BIGTK removal while another group key is
active do not trigger a premature zero-cipher BSS update.
Fixes: 98686cd21624 ("wifi: mt76: mt7996: add driver for MediaTek Wi-Fi 7 (802.11be) devices")
Cc: stable@vger.kernel.org
Signed-off-by: Joshua Klinesmith <joshuaklinesmith@gmail.com>
---
.../net/wireless/mediatek/mt76/mt7996/main.c | 23 ++++++++++++++++---
1 file changed, 20 insertions(+), 3 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c
index f16135f0b7f9..8b1bc3237527 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c
@@ -244,10 +244,27 @@ mt7996_set_hw_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
&link->mt76, msta_link, true);
}
- if (cmd == SET_KEY)
+ if (cmd == SET_KEY) {
*wcid_keyidx = idx;
- else if (idx == *wcid_keyidx)
- *wcid_keyidx = -1;
+ } else {
+ if (idx == *wcid_keyidx)
+ *wcid_keyidx = -1;
+
+ /* Clear BSS cipher only when the last group key is removed;
+ * during GTK rotation the new key is installed before the old
+ * one is removed, so hw_key_idx still points at the new key
+ * and this condition stays false.
+ */
+ if (!sta && link->mt76.cipher &&
+ msta_link->wcid.hw_key_idx == (u8)-1 &&
+ msta_link->wcid.hw_key_idx2 == (u8)-1) {
+ link->mt76.cipher = 0;
+ if (link->phy)
+ mt7996_mcu_add_bss_info(link->phy, vif,
+ link_conf, &link->mt76,
+ msta_link, true);
+ }
+ }
/* only do remove key for BIGTK */
if (cmd != SET_KEY && !is_bigtk)
--
2.43.0
^ permalink raw reply related
* [PATCH wireless v3 2/3] wifi: mt76: mt7915: clear cipher state on key removal for WED offload
From: Joshua Klinesmith @ 2026-04-07 5:39 UTC (permalink / raw)
To: linux-wireless
Cc: nbd, lorenzo, ryder.lee, shayne.chen, sean.wang,
Joshua Klinesmith, stable
In-Reply-To: <20260407053917.75898-1-joshuaklinesmith@gmail.com>
When switching from WPA-PSK/SAE to open/no encryption, the
DISABLE_KEY path never resets mvif->mt76.cipher back to zero.
The stale cipher value is sent to the WA firmware via BSS_INFO
updates, causing the firmware to keep the protection bit set on
WED-offloaded packets. The hardware then drops all plaintext
frames, resulting in zero throughput.
Reset mvif->mt76.cipher to zero and notify the firmware via
mt7915_mcu_add_bss_info() when the last group key is removed.
The clearing is guarded by checking that both hw_key_idx and
hw_key_idx2 are unset (-1) so that GTK rotation (where the new
key is installed before the old one is removed) and BIGTK
removal while another group key is active do not trigger a
premature zero-cipher BSS update.
Fixes: 3fd2dbd6a1d3 ("mt76: mt7915: update bss_info with cipher after setting the group key")
Cc: stable@vger.kernel.org
Signed-off-by: Joshua Klinesmith <joshuaklinesmith@gmail.com>
---
drivers/net/wireless/mediatek/mt76/mt7915/main.c | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/main.c b/drivers/net/wireless/mediatek/mt76/mt7915/main.c
index 116dff49c104..2365d1ccf23d 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7915/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7915/main.c
@@ -414,6 +414,19 @@ static int mt7915_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
} else {
if (idx == *wcid_keyidx)
*wcid_keyidx = -1;
+
+ /* Clear BSS cipher only when the last group key is removed;
+ * during GTK rotation the new key is installed before the old
+ * one is removed, so hw_key_idx still points at the new key
+ * and this condition stays false.
+ */
+ if (!sta && mvif->mt76.cipher &&
+ wcid->hw_key_idx == (u8)-1 &&
+ wcid->hw_key_idx2 == (u8)-1) {
+ mvif->mt76.cipher = 0;
+ mt7915_mcu_add_bss_info(phy, vif, true);
+ }
+
goto out;
}
--
2.43.0
^ permalink raw reply related
* [PATCH wireless v3 1/3] wifi: mt76: initialize hw_key_idx2 in mt76_wcid_init
From: Joshua Klinesmith @ 2026-04-07 5:39 UTC (permalink / raw)
To: linux-wireless
Cc: nbd, lorenzo, ryder.lee, shayne.chen, sean.wang,
Joshua Klinesmith
In-Reply-To: <20260407053917.75898-1-joshuaklinesmith@gmail.com>
hw_key_idx is initialized to -1 (0xFF, meaning "no key") in
mt76_wcid_init(), but hw_key_idx2 (used for AES-CMAC/BIGTK key
tracking) is left at the kzalloc default of 0. This makes the
two key index slots inconsistent: code that checks whether all
group keys have been removed cannot use a uniform "== (u8)-1"
test on both slots.
Initialize hw_key_idx2 to -1 alongside hw_key_idx so both use
the same "no key installed" sentinel. This does not change
runtime behavior since all existing consumers compare
hw_key_idx2 against explicit key indices (6 or 7) which are
distinct from both 0 and 0xFF.
Fixes: 730d6d0da8d8 ("mt76: mt7615: fix key set/delete issues")
Signed-off-by: Joshua Klinesmith <joshuaklinesmith@gmail.com>
---
drivers/net/wireless/mediatek/mt76/mac80211.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c
index 75772979f438..5eea3b4f27dc 100644
--- a/drivers/net/wireless/mediatek/mt76/mac80211.c
+++ b/drivers/net/wireless/mediatek/mt76/mac80211.c
@@ -1690,6 +1690,7 @@ EXPORT_SYMBOL_GPL(mt76_sta_pre_rcu_remove);
void mt76_wcid_init(struct mt76_wcid *wcid, u8 band_idx)
{
wcid->hw_key_idx = -1;
+ wcid->hw_key_idx2 = -1;
wcid->phy_idx = band_idx;
INIT_LIST_HEAD(&wcid->tx_list);
--
2.43.0
^ permalink raw reply related
* [PATCH wireless v3 0/3] wifi: mt76: clear cipher state on key removal for WED offload
From: Joshua Klinesmith @ 2026-04-07 5:39 UTC (permalink / raw)
To: linux-wireless
Cc: nbd, lorenzo, ryder.lee, shayne.chen, sean.wang,
Joshua Klinesmith
Clear stale BSS cipher on group key removal so WED-offloaded
plaintext traffic is not dropped after switching from encrypted
to open/no-encryption mode.
Changes since v2:
- New patch 1/3: initialize hw_key_idx2 to -1 in mt76_wcid_init()
for consistent "no key" sentinel on both key index slots.
- Guard cipher clearing on both hw_key_idx == (u8)-1 AND
hw_key_idx2 == (u8)-1, so that GTK rotation (new key installed
before old removed) and BIGTK removal while another group key
is active do not trigger a premature zero-cipher BSS update.
Changes since v1:
- Rebased on current wireless tree.
Joshua Klinesmith (3):
wifi: mt76: initialize hw_key_idx2 in mt76_wcid_init
wifi: mt76: mt7915: clear cipher state on key removal for WED offload
wifi: mt76: mt7996: clear cipher state on key removal for WED offload
drivers/net/wireless/mediatek/mt76/mac80211.c | 1 +
.../net/wireless/mediatek/mt76/mt7915/main.c | 13 +++++++++++
.../net/wireless/mediatek/mt76/mt7996/main.c | 23 ++++++++++++++++---
3 files changed, 34 insertions(+), 3 deletions(-)
--
2.43.0
^ permalink raw reply
* [PATCH wireless v2 2/2] wifi: mt76: mt7996: validate WCID index before WTBL lookup
From: Joshua Klinesmith @ 2026-04-07 5:39 UTC (permalink / raw)
To: linux-wireless
Cc: nbd, lorenzo, ryder.lee, shayne.chen, sean.wang,
Joshua Klinesmith, stable
In-Reply-To: <20260407053903.75861-1-joshuaklinesmith@gmail.com>
Same class of bug as mt7915: the mt7996 driver does not validate
WCID indices from TX free events or TX status reports before
WTBL lookups. An out-of-range WCID causes invalid MMIO accesses
leading to a kernel data abort.
Add bounds checks in mt7996_mac_tx_free() and
mt7996_mac_add_txs() to match the pattern used by mt7615,
mt7921, and mt7925 drivers.
Additionally, clear the carried wcid and link_sta state when
a WCID pair lookup fails (either out of range or not a station),
so that subsequent header and MSDU entries in the same TX free
event do not attribute statistics or free tokens against a stale
WCID from a previous pair.
Fixes: 98686cd21624 ("wifi: mt76: mt7996: add driver for MediaTek Wi-Fi 7 (802.11be) devices")
Cc: stable@vger.kernel.org
Signed-off-by: Joshua Klinesmith <joshuaklinesmith@gmail.com>
---
drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
index 3d9648fb6773..f962ad398e04 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
@@ -1248,9 +1248,16 @@ mt7996_mac_tx_free(struct mt7996_dev *dev, void *data, int len)
u16 idx;
idx = FIELD_GET(MT_TXFREE_INFO_WLAN_ID, info);
+ if (idx >= mt7996_wtbl_size(dev)) {
+ wcid = NULL;
+ link_sta = NULL;
+ goto next;
+ }
+
wcid = mt76_wcid_ptr(dev, idx);
sta = wcid_to_sta(wcid);
if (!sta) {
+ wcid = NULL;
link_sta = NULL;
goto next;
}
@@ -1482,6 +1489,9 @@ static void mt7996_mac_add_txs(struct mt7996_dev *dev, void *data)
u8 pid;
wcidx = le32_get_bits(txs_data[2], MT_TXS2_WCID);
+ if (wcidx >= mt7996_wtbl_size(dev))
+ return;
+
pid = le32_get_bits(txs_data[3], MT_TXS3_PID);
if (pid < MT_PACKET_ID_NO_SKB)
--
2.43.0
^ permalink raw reply related
* [PATCH wireless v2 1/2] wifi: mt76: mt7915: validate WCID index before WTBL lookup
From: Joshua Klinesmith @ 2026-04-07 5:39 UTC (permalink / raw)
To: linux-wireless
Cc: nbd, lorenzo, ryder.lee, shayne.chen, sean.wang,
Joshua Klinesmith, stable
In-Reply-To: <20260407053903.75861-1-joshuaklinesmith@gmail.com>
The mt7915 driver does not validate WCID indices extracted from
hardware TX free events and TX status reports before using them
for WTBL MMIO register accesses. The hardware WCID field is 10
bits wide (max 1023) but actual WTBL capacity is only 288
(MT7915) or 544 (MT7916). An out-of-range index causes
mt7915_mac_wtbl_lmac_addr() to compute an invalid MMIO address,
leading to a kernel data abort:
Unable to handle kernel paging request at virtual address
ffffff88d5ab0010
The mt7615, mt7921, and mt7925 drivers already validate WCID
indices against their WTBL size before use. Add the same bounds
checks in mt7915_mac_tx_free() and mt7915_mac_add_txs().
Additionally, when a WCID pair lookup in the TX free path
resolves to a valid WCID that is not a station (wcid_to_sta()
returns NULL), or the WCID index is out of range, clear both
wcid and sta so that subsequent non-pair MSDU entries do not
attribute TX statistics or pass a stale station pointer to
mt76_connac2_txwi_free().
Fixes: c17780e7b21e ("mt76: mt7915: add txfree event v3")
Cc: stable@vger.kernel.org
Signed-off-by: Joshua Klinesmith <joshuaklinesmith@gmail.com>
---
drivers/net/wireless/mediatek/mt76/mt7915/mac.c | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c
index 2f307c4caff1..19435f3c6fa5 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c
@@ -913,10 +913,19 @@ mt7915_mac_tx_free(struct mt7915_dev *dev, void *data, int len)
u16 idx;
idx = FIELD_GET(MT_TX_FREE_WLAN_ID, info);
+ if (idx >= mt7915_wtbl_size(dev)) {
+ wcid = NULL;
+ sta = NULL;
+ continue;
+ }
+
wcid = mt76_wcid_ptr(dev, idx);
sta = wcid_to_sta(wcid);
- if (!sta)
+ if (!sta) {
+ wcid = NULL;
+ sta = NULL;
continue;
+ }
msta = container_of(wcid, struct mt7915_sta, wcid);
mt76_wcid_add_poll(&dev->mt76, &msta->wcid);
@@ -1004,6 +1013,9 @@ static void mt7915_mac_add_txs(struct mt7915_dev *dev, void *data)
u8 pid;
wcidx = le32_get_bits(txs_data[2], MT_TXS2_WCID);
+ if (wcidx >= mt7915_wtbl_size(dev))
+ return;
+
pid = le32_get_bits(txs_data[3], MT_TXS3_PID);
if (pid < MT_PACKET_ID_WED)
--
2.43.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