* [PATCH mt76-next 09/29] wifi: mt76: mt7996: don't leak MLD group index on remap alloc failure
From: Felix Fietkau @ 2026-07-24 12:47 UTC (permalink / raw)
To: linux-wireless
In-Reply-To: <20260724124813.3961474-1-nbd@nbd.name>
mt7996_change_vif_links() sets the mld_idx_mask group bit before
allocating the remap index. If the remap allocation fails it jumped to
the exit without clearing that bit, permanently consuming one of the 16
MLD group slots. Release the group bit on the error path.
Fixes: 4fb3b4e7d1ca ("wifi: mt76: mt7996: fix MLD group index assignment")
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
drivers/net/wireless/mediatek/mt76/mt7996/main.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c
index 2487b00c3790..57afe4e81666 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c
@@ -2458,6 +2458,7 @@ mt7996_change_vif_links(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
idx = get_free_idx(dev->mld_remap_idx_mask, 0, 15) - 1;
if (idx < 0) {
+ dev->mld_idx_mask &= ~BIT_ULL(mvif->mld_group_idx);
ret = -ENOSPC;
goto out;
}
--
2.53.0
^ permalink raw reply related
* [PATCH mt76-next 08/29] wifi: mt76: mt7996: reserve space for the CSA-abort countdown TLV
From: Felix Fietkau @ 2026-07-24 12:47 UTC (permalink / raw)
To: linux-wireless
In-Reply-To: <20260724124813.3961474-1-nbd@nbd.name>
When a CSA countdown is active, mt7996_mcu_beacon_cntdwn() emits two
bss_bcn_cntdwn_tlv entries (the CSA countdown and the CCA-abort BCC), but
MT7996_BEACON_UPDATE_SIZE only reserved one. With MBSSID enabled and a
near-maximum beacon template the extra 8 bytes could push the offload
command past MT7996_MAX_BSS_OFFLOAD_SIZE and trigger skb_over_panic().
Reserve room for both countdown TLVs.
Fixes: 98686cd21624 ("wifi: mt76: mt7996: add driver for MediaTek Wi-Fi 7 (802.11be) devices")
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
drivers/net/wireless/mediatek/mt76/mt7996/mcu.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h
index 8902e16508b7..c673e986ecb5 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h
@@ -917,7 +917,7 @@ enum {
#define MT7996_BEACON_UPDATE_SIZE (sizeof(struct bss_req_hdr) + \
sizeof(struct bss_bcn_content_tlv) + \
4 + MT_TXD_SIZE + \
- sizeof(struct bss_bcn_cntdwn_tlv) + \
+ sizeof(struct bss_bcn_cntdwn_tlv) * 2 + \
sizeof(struct bss_bcn_mbss_tlv))
#define MT7996_MAX_BSS_OFFLOAD_SIZE 2048
#define MT7996_MAX_BEACON_SIZE (MT7996_MAX_BSS_OFFLOAD_SIZE - \
--
2.53.0
^ permalink raw reply related
* [PATCH mt76-next 07/29] wifi: mt76: fix ER-SU 106-tone RU check in RX rate decode
From: Felix Fietkau @ 2026-07-24 12:47 UTC (permalink / raw)
To: linux-wireless
In-Reply-To: <20260724124813.3961474-1-nbd@nbd.name>
MT_PHY_TYPE_HE_EXT_SU is an enum value (9), not a bit flag, so the
bitwise test "*mode & MT_PHY_TYPE_HE_EXT_SU" also matches OFDM, HT-GF and
several HE/EHT modes. Only genuine ER-SU should be classified as a
106-tone RU at 40 MHz; use an equality comparison.
Fixes: 98686cd21624 ("wifi: mt76: mt7996: add driver for MediaTek Wi-Fi 7 (802.11be) devices")
Fixes: d832f5e73815 ("mt76: connac: move mt76_connac2_mac_fill_rx_rate in connac module")
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c | 2 +-
drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c b/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c
index 0ac3ab42ea40..cbf6a987cf90 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c
@@ -1114,7 +1114,7 @@ int mt76_connac2_mac_fill_rx_rate(struct mt76_dev *dev,
case IEEE80211_STA_RX_BW_20:
break;
case IEEE80211_STA_RX_BW_40:
- if (*mode & MT_PHY_TYPE_HE_EXT_SU &&
+ if (*mode == MT_PHY_TYPE_HE_EXT_SU &&
(idx & MT_PRXV_TX_ER_SU_106T)) {
status->bw = RATE_INFO_BW_HE_RU;
status->he_ru =
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
index e0a1076ac706..866752f2fa24 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
@@ -349,7 +349,7 @@ mt7996_mac_fill_rx_rate(struct mt7996_dev *dev,
case IEEE80211_STA_RX_BW_20:
break;
case IEEE80211_STA_RX_BW_40:
- if (*mode & MT_PHY_TYPE_HE_EXT_SU &&
+ if (*mode == MT_PHY_TYPE_HE_EXT_SU &&
(idx & MT_PRXV_TX_ER_SU_106T)) {
status->bw = RATE_INFO_BW_HE_RU;
status->he_ru =
--
2.53.0
^ permalink raw reply related
* [PATCH mt76-next 06/29] wifi: mt76: decode the full VHT Rx STBC capability field
From: Felix Fietkau @ 2026-07-24 12:47 UTC (permalink / raw)
To: linux-wireless
In-Reply-To: <20260724124813.3961474-1-nbd@nbd.name>
The Rx STBC subfield of the VHT capabilities is a 3-bit cumulative value,
but the driver only tested the RXSTBC_1 bit when advertising the peer's
Rx STBC support to firmware. A peer reporting Rx STBC of 2, 3 or 4 has
that bit clear, so STBC was never used towards it. Test the full
IEEE80211_VHT_CAP_RXSTBC_MASK, matching the HT path.
Fixes: 046d2e7c50e3 ("mac80211: prepare sta handling for MLO support")
Fixes: 2660fde82f65 ("wifi: mt76: mt7996: Update mt7996_mcu_add_rate_ctrl to MLO")
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
drivers/net/wireless/mediatek/mt76/mt7915/mcu.c | 2 +-
drivers/net/wireless/mediatek/mt76/mt7996/mcu.c | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c
index 0ee21fcc83c1..459dbcc9d90e 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c
@@ -1617,7 +1617,7 @@ mt7915_mcu_sta_rate_ctrl_tlv(struct sk_buff *skb, struct mt7915_dev *dev,
cap |= STA_CAP_VHT_SGI_160;
if (sta->deflink.vht_cap.cap & IEEE80211_VHT_CAP_TXSTBC)
cap |= STA_CAP_VHT_TX_STBC;
- if (sta->deflink.vht_cap.cap & IEEE80211_VHT_CAP_RXSTBC_1)
+ if (sta->deflink.vht_cap.cap & IEEE80211_VHT_CAP_RXSTBC_MASK)
cap |= STA_CAP_VHT_RX_STBC;
if (mvif->cap.vht_ldpc &&
(sta->deflink.vht_cap.cap & IEEE80211_VHT_CAP_RXLDPC))
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
index 73ca92081ca0..1b334cbf6d72 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
@@ -2476,7 +2476,7 @@ mt7996_mcu_sta_rate_ctrl_tlv(struct sk_buff *skb, struct mt7996_dev *dev,
cap |= STA_CAP_VHT_SGI_160;
if (link_sta->vht_cap.cap & IEEE80211_VHT_CAP_TXSTBC)
cap |= STA_CAP_VHT_TX_STBC;
- if (link_sta->vht_cap.cap & IEEE80211_VHT_CAP_RXSTBC_1)
+ if (link_sta->vht_cap.cap & IEEE80211_VHT_CAP_RXSTBC_MASK)
cap |= STA_CAP_VHT_RX_STBC;
if ((vif->type != NL80211_IFTYPE_AP || link_conf->vht_ldpc) &&
(link_sta->vht_cap.cap & IEEE80211_VHT_CAP_RXLDPC))
--
2.53.0
^ permalink raw reply related
* [PATCH mt76-next 05/29] wifi: mt76: mt7996: hold dev->mt76.mutex while disabling tx worker in SER
From: Felix Fietkau @ 2026-07-24 12:47 UTC (permalink / raw)
To: linux-wireless
In-Reply-To: <20260724124813.3961474-1-nbd@nbd.name>
mt7996_mac_reset_work() parked the tx worker and disabled the RX/TX NAPIs
before taking dev->mt76.mutex. mt76_worker_disable()/_enable() are plain
kthread park/unpark, not refcounted, and __mt76_set_channel() toggles the
same worker and the MT76_RESET bit under the mutex. An L1 SER racing a
channel switch could therefore have the worker unparked and MT76_RESET
cleared while the reset path resets the DMA rings, corrupting descriptors
or tokens. Take the mutex before disabling the worker, as mt7915 does.
Fixes: 27015b6fbcca ("wifi: mt76: mt7996: enable full system reset support")
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
index c204327eaa37..e0a1076ac706 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
@@ -2573,6 +2573,8 @@ void mt7996_mac_reset_work(struct work_struct *work)
cancel_delayed_work_sync(&phy->mt76->mac_work);
}
+ mutex_lock(&dev->mt76.mutex);
+
mt76_worker_disable(&dev->mt76.tx_worker);
mt76_for_each_q_rx(&dev->mt76, i) {
if (mtk_wed_device_active(&dev->mt76.mmio.wed) &&
@@ -2590,8 +2592,6 @@ void mt7996_mac_reset_work(struct work_struct *work)
}
napi_disable(&dev->mt76.tx_napi);
- mutex_lock(&dev->mt76.mutex);
-
mt76_wr(dev, MT_MCU_INT_EVENT, MT_MCU_INT_EVENT_DMA_STOPPED);
if (mt7996_wait_reset_state(dev, MT_MCU_CMD_RESET_DONE)) {
--
2.53.0
^ permalink raw reply related
* [PATCH mt76-next 03/29] wifi: mt76: fix out-of-bounds access in mmio copy helpers
From: Felix Fietkau @ 2026-07-24 12:47 UTC (permalink / raw)
To: linux-wireless
In-Reply-To: <20260724124813.3961474-1-nbd@nbd.name>
mt76_mmio_write_copy() and mt76_mmio_read_copy() iterate up to
ALIGN(len, 4), so a length that is not a multiple of four reads past the
source buffer (write_copy) or writes past the destination (read_copy).
Copy the aligned body in the loop and handle the remaining tail through a
4-byte bounce buffer, keeping the register access width unchanged.
Fixes: 2df00805f7db ("wifi: mt76: mmio_*_copy fix byte order and alignment")
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
drivers/net/wireless/mediatek/mt76/mmio.c | 18 ++++++++++++++++--
1 file changed, 16 insertions(+), 2 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mmio.c b/drivers/net/wireless/mediatek/mt76/mmio.c
index 05d74cd7248e..73d47608bf42 100644
--- a/drivers/net/wireless/mediatek/mt76/mmio.c
+++ b/drivers/net/wireless/mediatek/mt76/mmio.c
@@ -35,9 +35,16 @@ static void mt76_mmio_write_copy(struct mt76_dev *dev, u32 offset,
{
int i;
- for (i = 0; i < ALIGN(len, 4); i += 4)
+ for (i = 0; i + 4 <= len; i += 4)
writel(get_unaligned_le32(data + i),
dev->mmio.regs + offset + i);
+
+ if (i < len) {
+ u8 tmp[4] = {};
+
+ memcpy(tmp, data + i, len - i);
+ writel(get_unaligned_le32(tmp), dev->mmio.regs + offset + i);
+ }
}
static void mt76_mmio_read_copy(struct mt76_dev *dev, u32 offset,
@@ -45,9 +52,16 @@ static void mt76_mmio_read_copy(struct mt76_dev *dev, u32 offset,
{
int i;
- for (i = 0; i < ALIGN(len, 4); i += 4)
+ for (i = 0; i + 4 <= len; i += 4)
put_unaligned_le32(readl(dev->mmio.regs + offset + i),
data + i);
+
+ if (i < len) {
+ u8 tmp[4];
+
+ put_unaligned_le32(readl(dev->mmio.regs + offset + i), tmp);
+ memcpy(data + i, tmp, len - i);
+ }
}
static int mt76_mmio_wr_rp(struct mt76_dev *dev, u32 base,
--
2.53.0
^ permalink raw reply related
* [PATCH mt76-next 02/29] wifi: mt76: mt7996: bound TLV walk in mt7996_mcu_get_chip_config
From: Felix Fietkau @ 2026-07-24 12:47 UTC (permalink / raw)
To: linux-wireless
In-Reply-To: <20260724124813.3961474-1-nbd@nbd.name>
The response TLV loop advanced by tlv->len without a minimum, so a
theoretical firmware response containing a zero-length TLV could spin
forever, hanging the CPU during device probe.
The u32 payload was also read without bounds checking.
Reject a short fixed field, stop on a TLV whose length underruns the
header or overruns the skb.
Fixes: 5d33053be609 ("wifi: mt76: mt7996: add variants support")
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
drivers/net/wireless/mediatek/mt76/mt7996/mcu.c | 16 +++++++++++++---
1 file changed, 13 insertions(+), 3 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
index 01c9adbca68b..73ca92081ca0 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
@@ -4438,21 +4438,31 @@ int mt7996_mcu_get_chip_config(struct mt7996_dev *dev, u32 *cap)
return ret;
/* fixed field */
+ if (skb->len < 4) {
+ dev_kfree_skb(skb);
+ return -EINVAL;
+ }
skb_pull(skb, 4);
buf = skb->data;
- while (buf - skb->data < skb->len) {
+ while (buf - skb->data + sizeof(struct tlv) <= skb->len) {
struct tlv *tlv = (struct tlv *)buf;
+ u16 tlv_len = le16_to_cpu(tlv->len);
+
+ if (tlv_len < sizeof(*tlv) ||
+ tlv_len > skb->len - (buf - skb->data))
+ break;
switch (le16_to_cpu(tlv->tag)) {
case UNI_EVENT_CHIP_CONFIG_EFUSE_VERSION:
- *cap = le32_to_cpu(*(__le32 *)(buf + sizeof(*tlv)));
+ if (tlv_len >= sizeof(*tlv) + sizeof(__le32))
+ *cap = le32_to_cpu(*(__le32 *)(buf + sizeof(*tlv)));
break;
default:
break;
}
- buf += le16_to_cpu(tlv->len);
+ buf += tlv_len;
}
dev_kfree_skb(skb);
--
2.53.0
^ permalink raw reply related
* [PATCH mt76-next 04/29] wifi: mt76: mt7915: unwind state on add_interface failure
From: Felix Fietkau @ 2026-07-24 12:47 UTC (permalink / raw)
To: linux-wireless
In-Reply-To: <20260724124813.3961474-1-nbd@nbd.name>
When mt76_wcid_alloc() fails, mt7915_add_interface() returned without
clearing the vif_mask/omac_mask bits it had already set, without removing
the firmware dev info added earlier, and without clearing a monitor_vif
pointer to the vif mac80211 is about to free. mac80211 does not call
remove_interface() for a failed add, so the indices and firmware dev
entry leaked permanently and testmode could dereference the stale
monitor_vif. Add a proper error unwind.
Fixes: b619e01380ee ("mt76: fix MBSS index condition in DBDC mode")
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
drivers/net/wireless/mediatek/mt76/mt7915/main.c | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/main.c b/drivers/net/wireless/mediatek/mt76/mt7915/main.c
index 4ed3d808654f..d2130226de64 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7915/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7915/main.c
@@ -249,7 +249,7 @@ static int mt7915_add_interface(struct ieee80211_hw *hw,
idx = mt76_wcid_alloc(dev->mt76.wcid_mask, mt7915_wtbl_size(dev));
if (idx < 0) {
ret = -ENOSPC;
- goto out;
+ goto err;
}
INIT_LIST_HEAD(&mvif->sta.rc_list);
@@ -277,7 +277,17 @@ static int mt7915_add_interface(struct ieee80211_hw *hw,
mt7915_mcu_add_sta(dev, vif, NULL, CONN_STATE_PORT_SECURE, true);
rcu_assign_pointer(dev->mt76.wcid[idx], &mvif->sta.wcid);
+ mutex_unlock(&dev->mt76.mutex);
+
+ return 0;
+
+err:
+ dev->mt76.vif_mask &= ~BIT_ULL(mvif->mt76.idx);
+ phy->omac_mask &= ~BIT_ULL(mvif->mt76.omac_idx);
+ mt7915_mcu_add_dev_info(phy, vif, false);
out:
+ if (phy->monitor_vif == vif)
+ phy->monitor_vif = NULL;
mutex_unlock(&dev->mt76.mutex);
return ret;
--
2.53.0
^ permalink raw reply related
* [PATCH mt76-next 01/29] wifi: mt76: fix HE DCM max-RU capability encoding
From: Felix Fietkau @ 2026-07-24 12:47 UTC (permalink / raw)
To: linux-wireless
sta_rec_he.dcm_rx_max_nss was assigned twice: the second assignment,
sourced from HE PHY capability byte 8 (DCM max RU), overwrote the RX-NSS
value and left dcm_max_ru at zero. Every associated HE station advertising
DCM support was configured in firmware with a wrong dcm_rx_max_nss and a
zero dcm_max_ru. Store the DCM max-RU value in dcm_max_ru as intended.
The same copy-paste error existed in both the shared connac2 path and the
mt7915 path.
Fixes: c336318f57a9 ("mt76: mt7915: add HE capabilities support for peers")
Fixes: 67aa27431c7f ("mt76: mt7921: rely on mt76_connac_mcu common library")
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c | 2 +-
drivers/net/wireless/mediatek/mt76/mt7915/mcu.c | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c
index 562d4b936d6c..ed6a0c92e8e8 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c
@@ -800,7 +800,7 @@ mt76_connac_mcu_sta_he_tlv(struct sk_buff *skb, struct ieee80211_sta *sta)
HE_PHY(CAP3_DCM_MAX_CONST_RX_MASK, elem->phy_cap_info[3]);
he->dcm_rx_max_nss =
HE_PHY(CAP3_DCM_MAX_RX_NSS_2, elem->phy_cap_info[3]);
- he->dcm_rx_max_nss =
+ he->dcm_max_ru =
HE_PHY(CAP8_DCM_MAX_RU_MASK, elem->phy_cap_info[8]);
he->pkt_ext = 2;
diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c
index 4409962b20e2..0ee21fcc83c1 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c
@@ -900,7 +900,7 @@ mt7915_mcu_sta_he_tlv(struct sk_buff *skb, struct ieee80211_sta *sta,
HE_PHY(CAP3_DCM_MAX_CONST_RX_MASK, elem->phy_cap_info[3]);
he->dcm_rx_max_nss =
HE_PHY(CAP3_DCM_MAX_RX_NSS_2, elem->phy_cap_info[3]);
- he->dcm_rx_max_nss =
+ he->dcm_max_ru =
HE_PHY(CAP8_DCM_MAX_RU_MASK, elem->phy_cap_info[8]);
he->pkt_ext = 2;
--
2.53.0
^ permalink raw reply related
* Re: [PATCH] wifi: mac80211: reject station association if AP is not started
From: Slawomir Stepien @ 2026-07-24 12:02 UTC (permalink / raw)
To: Johannes Berg
Cc: syzbot, syzkaller-bugs, linux-wireless, linux-kernel, syzbot
In-Reply-To: <86efc46da2c0c5cf73ca262f8be8c5086d626ee7.camel@sipsolutions.net>
On lip 24, 2026 09:15, Johannes Berg wrote:
> On Fri, 2026-07-24 at 08:50 +0200, Slawomir Stepien wrote:
> > On lip 23, 2026 11:14, Johannes Berg wrote:
> > > On Thu, 2026-07-23 at 08:11 +0000, syzbot wrote:
> > > > From: Slawomir Stepien <sst@poczta.fm>
> > >
> > > I'm a bit confused? Did syzbot just pick up another patch?
> >
> > Hi Johannes!
> >
> > No, this issue has been found by syzbot while doing fuzzing testing.
>
> Yeah, but the email? It's "From: syzbot <...>" and actually was
> delivered to me by sea.source.kernel.org, whereas your other mail was
> delivered to me by smtpo49.interia.pl, so I'm confused as to who sent
> the mail and how :)
The initial e-mail with patch was sent by the syzbot on my request, that's why I'm in the added From
and Signed-off-by (bot can't be the Author of commit and I'm responsible for handling this patch on
mailing list).
You can read more about this here:
https://github.com/google/syzkaller/blob/master/docs/syzbot_ai_patches.md.
--
Slawomir Stepien
^ permalink raw reply
* [PATCH wireless-next v5 2/4] wifi: mac80211: estimate expected throughput if not provided by driver/rc
From: Felix Fietkau @ 2026-07-24 11:54 UTC (permalink / raw)
To: linux-wireless; +Cc: johannes
In-Reply-To: <20260724115429.3921457-1-nbd@nbd.name>
Estimate the tx throughput based on the expected per-packet tx time.
This is useful for mesh implementations that rely on expected throughput,
e.g. 802.11s or batman-adv.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
v3: avoid band=0 default, return 0 if no chanctx
v4: rename lsi to link_sinfo
net/mac80211/sta_info.c | 49 ++++++++++++++++++++++++++++++++++++++---
1 file changed, 46 insertions(+), 3 deletions(-)
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index 9c69c5ba0d3f..877117929f5b 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -2795,6 +2795,28 @@ void sta_set_accumulated_removed_links_sinfo(struct sta_info *sta,
}
}
+static u32 sta_estimate_expected_throughput(struct sta_info *sta,
+ struct rate_info *ri,
+ struct ieee80211_bss_conf *bss_conf)
+{
+ struct ieee80211_hw *hw = &sta->sdata->local->hw;
+ struct ieee80211_chanctx_conf *conf;
+ u32 duration;
+ u8 band;
+
+ conf = rcu_dereference(bss_conf->chanctx_conf);
+ if (!conf)
+ return 0;
+ band = conf->def.chan->band;
+
+ duration = ieee80211_rate_expected_tx_airtime(hw, NULL, ri, band, true, 1024);
+ duration += duration >> 4; /* add assumed packet error rate of ~6% */
+ if (!duration)
+ return 0;
+
+ return ((1024 * USEC_PER_SEC) / duration) * 8;
+}
+
static void sta_set_link_sinfo(struct sta_info *sta,
struct link_station_info *link_sinfo,
struct ieee80211_link_data *link,
@@ -3009,6 +3031,10 @@ static void sta_set_link_sinfo(struct sta_info *sta,
link_sinfo->bss_param.beacon_interval = link->conf->beacon_int;
thr = sta_get_expected_throughput(sta);
+ if (!thr && (link_sinfo->filled & BIT_ULL(NL80211_STA_INFO_TX_BITRATE)))
+ thr = sta_estimate_expected_throughput(sta,
+ &link_sinfo->txrate,
+ link->conf);
if (thr != 0) {
link_sinfo->filled |=
@@ -3262,6 +3288,14 @@ void sta_set_sinfo(struct sta_info *sta, struct station_info *sinfo,
if (thr != 0) {
sinfo->filled |= BIT_ULL(NL80211_STA_INFO_EXPECTED_THROUGHPUT);
sinfo->expected_throughput = thr;
+ } else if (!sta->sta.valid_links &&
+ (sinfo->filled & BIT_ULL(NL80211_STA_INFO_TX_BITRATE))) {
+ thr = sta_estimate_expected_throughput(sta, &sinfo->txrate,
+ &sdata->vif.bss_conf);
+ if (thr) {
+ sinfo->filled |= BIT_ULL(NL80211_STA_INFO_EXPECTED_THROUGHPUT);
+ sinfo->expected_throughput = thr;
+ }
}
if (!(sinfo->filled & BIT_ULL(NL80211_STA_INFO_ACK_SIGNAL)) &&
@@ -3282,6 +3316,7 @@ void sta_set_sinfo(struct sta_info *sta, struct station_info *sinfo,
if (sta->sta.valid_links) {
struct ieee80211_link_data *link;
struct link_sta_info *link_sta;
+ u32 est_thr = 0;
int link_id;
sinfo->mlo_params_valid = true;
@@ -3293,17 +3328,25 @@ void sta_set_sinfo(struct sta_info *sta, struct station_info *sinfo,
sinfo->valid_links = sta->sta.valid_links;
for_each_valid_link(sinfo, link_id) {
+ struct link_station_info *link_sinfo = sinfo->links[link_id];
+
link_sta = wiphy_dereference(sta->local->hw.wiphy,
sta->link[link_id]);
link = wiphy_dereference(sdata->local->hw.wiphy,
sdata->link[link_id]);
- if (!link_sta || !sinfo->links[link_id] || !link) {
+ if (!link_sta || !link_sinfo || !link) {
sinfo->valid_links &= ~BIT(link_id);
continue;
}
- sta_set_link_sinfo(sta, sinfo->links[link_id],
- link, tidstats);
+ sta_set_link_sinfo(sta, link_sinfo, link, tidstats);
+ if (!thr &&
+ (link_sinfo->filled & BIT_ULL(NL80211_STA_INFO_EXPECTED_THROUGHPUT)))
+ est_thr += link_sinfo->expected_throughput;
+ }
+ if (est_thr) {
+ sinfo->filled |= BIT_ULL(NL80211_STA_INFO_EXPECTED_THROUGHPUT);
+ sinfo->expected_throughput = est_thr;
}
}
}
--
2.53.0
^ permalink raw reply related
* [PATCH wireless-next v5 3/4] wifi: mac80211: add AQL support for multicast packets
From: Felix Fietkau @ 2026-07-24 11:54 UTC (permalink / raw)
To: linux-wireless; +Cc: johannes
In-Reply-To: <20260724115429.3921457-1-nbd@nbd.name>
Excessive multicast traffic with little competing unicast traffic can easily
flood hardware queues, leading to throughput issues. Additionally, filling
the hardware queues with too many packets breaks FQ for multicast data.
Fix this by enabling AQL for multicast packets.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
v2: fix broadcast/multicast inconsistency
v5: add missing kdoc
include/net/cfg80211.h | 1 +
include/net/mac80211.h | 3 ++-
net/mac80211/debugfs.c | 13 ++++++++--
net/mac80211/ieee80211_i.h | 2 ++
net/mac80211/main.c | 1 +
net/mac80211/sta_info.c | 17 ++++++++++++-
net/mac80211/sta_info.h | 3 ++-
net/mac80211/status.c | 5 ++--
net/mac80211/tx.c | 52 ++++++++++++++++++++------------------
9 files changed, 66 insertions(+), 31 deletions(-)
diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index 15c08b24502f..47adbb439a28 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -3742,6 +3742,7 @@ enum wiphy_params_flags {
/* The per TXQ device queue limit in airtime */
#define IEEE80211_DEFAULT_AQL_TXQ_LIMIT_L 5000
#define IEEE80211_DEFAULT_AQL_TXQ_LIMIT_H 12000
+#define IEEE80211_DEFAULT_AQL_TXQ_LIMIT_MC 50000
/* The per interface airtime threshold to switch to lower queue limit */
#define IEEE80211_AQL_THRESHOLD 24000
diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 5ffa3d8b6142..2fde6601beec 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -1323,6 +1323,7 @@ ieee80211_rate_get_vht_nss(const struct ieee80211_tx_rate *rate)
* @status_data: internal data for TX status handling, assigned privately,
* see also &enum ieee80211_status_data for the internal documentation
* @status_data_idr: indicates status data is IDR allocated ID for ack frame
+ * @tx_time_mc: TX time estimate is for a multicast frame, used internally
* @tx_time_est: TX time estimate in units of 4us, used internally
* @control: union part for control data
* @control.rates: TX rates array to try
@@ -1365,8 +1366,8 @@ struct ieee80211_tx_info {
status_data_idr:1,
status_data:13,
hw_queue:4,
+ tx_time_mc:1,
tx_time_est:10;
- /* 1 free bit */
union {
struct {
diff --git a/net/mac80211/debugfs.c b/net/mac80211/debugfs.c
index a4d5461f6480..8ebf5bcf3c0e 100644
--- a/net/mac80211/debugfs.c
+++ b/net/mac80211/debugfs.c
@@ -210,11 +210,13 @@ static ssize_t aql_pending_read(struct file *file,
"VI %u us\n"
"BE %u us\n"
"BK %u us\n"
+ "MC %u us\n"
"total %u us\n",
atomic_read(&local->aql_ac_pending_airtime[IEEE80211_AC_VO]),
atomic_read(&local->aql_ac_pending_airtime[IEEE80211_AC_VI]),
atomic_read(&local->aql_ac_pending_airtime[IEEE80211_AC_BE]),
atomic_read(&local->aql_ac_pending_airtime[IEEE80211_AC_BK]),
+ atomic_read(&local->aql_mc_pending_airtime),
atomic_read(&local->aql_total_pending_airtime));
return simple_read_from_buffer(user_buf, count, ppos,
buf, len);
@@ -239,7 +241,8 @@ static ssize_t aql_txq_limit_read(struct file *file,
"VO %u %u\n"
"VI %u %u\n"
"BE %u %u\n"
- "BK %u %u\n",
+ "BK %u %u\n"
+ "MC %u\n",
local->aql_txq_limit_low[IEEE80211_AC_VO],
local->aql_txq_limit_high[IEEE80211_AC_VO],
local->aql_txq_limit_low[IEEE80211_AC_VI],
@@ -247,7 +250,8 @@ static ssize_t aql_txq_limit_read(struct file *file,
local->aql_txq_limit_low[IEEE80211_AC_BE],
local->aql_txq_limit_high[IEEE80211_AC_BE],
local->aql_txq_limit_low[IEEE80211_AC_BK],
- local->aql_txq_limit_high[IEEE80211_AC_BK]);
+ local->aql_txq_limit_high[IEEE80211_AC_BK],
+ local->aql_txq_limit_mc);
return simple_read_from_buffer(user_buf, count, ppos,
buf, len);
}
@@ -273,6 +277,11 @@ static ssize_t aql_txq_limit_write(struct file *file,
else
buf[count] = '\0';
+ if (sscanf(buf, "mcast %u", &q_limit_low) == 1) {
+ local->aql_txq_limit_mc = q_limit_low;
+ return count;
+ }
+
if (sscanf(buf, "%u %u %u", &ac, &q_limit_low, &q_limit_high) != 3)
return -EINVAL;
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 4034b71a31cf..a1ef88fe846d 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -1464,10 +1464,12 @@ struct ieee80211_local {
spinlock_t handle_wake_tx_queue_lock;
u16 airtime_flags;
+ u32 aql_txq_limit_mc;
u32 aql_txq_limit_low[IEEE80211_NUM_ACS];
u32 aql_txq_limit_high[IEEE80211_NUM_ACS];
u32 aql_threshold;
atomic_t aql_total_pending_airtime;
+ atomic_t aql_mc_pending_airtime;
atomic_t aql_ac_pending_airtime[IEEE80211_NUM_ACS];
const struct ieee80211_ops *ops;
diff --git a/net/mac80211/main.c b/net/mac80211/main.c
index 90d295cc364f..3adb941f407f 100644
--- a/net/mac80211/main.c
+++ b/net/mac80211/main.c
@@ -988,6 +988,7 @@ struct ieee80211_hw *ieee80211_alloc_hw_nm(size_t priv_data_len,
spin_lock_init(&local->rx_path_lock);
spin_lock_init(&local->queue_stop_reason_lock);
+ local->aql_txq_limit_mc = IEEE80211_DEFAULT_AQL_TXQ_LIMIT_MC;
for (i = 0; i < IEEE80211_NUM_ACS; i++) {
INIT_LIST_HEAD(&local->active_txqs[i]);
spin_lock_init(&local->active_txq_lock[i]);
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index 877117929f5b..0a8ca32fe38c 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -2489,13 +2489,28 @@ EXPORT_SYMBOL(ieee80211_sta_recalc_aggregates);
void ieee80211_sta_update_pending_airtime(struct ieee80211_local *local,
struct sta_info *sta, u8 ac,
- u16 tx_airtime, bool tx_completed)
+ u16 tx_airtime, bool tx_completed,
+ bool mcast)
{
int tx_pending;
if (!wiphy_ext_feature_isset(local->hw.wiphy, NL80211_EXT_FEATURE_AQL))
return;
+ if (mcast) {
+ if (!tx_completed) {
+ atomic_add(tx_airtime, &local->aql_mc_pending_airtime);
+ return;
+ }
+
+ tx_pending = atomic_sub_return(tx_airtime,
+ &local->aql_mc_pending_airtime);
+ if (tx_pending < 0)
+ atomic_cmpxchg(&local->aql_mc_pending_airtime,
+ tx_pending, 0);
+ return;
+ }
+
if (!tx_completed) {
if (sta)
atomic_add(tx_airtime,
diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h
index 5da3142d8516..ee0d32877c5b 100644
--- a/net/mac80211/sta_info.h
+++ b/net/mac80211/sta_info.h
@@ -147,7 +147,8 @@ struct airtime_info {
void ieee80211_sta_update_pending_airtime(struct ieee80211_local *local,
struct sta_info *sta, u8 ac,
- u16 tx_airtime, bool tx_completed);
+ u16 tx_airtime, bool tx_completed,
+ bool mcast);
struct sta_info;
diff --git a/net/mac80211/status.c b/net/mac80211/status.c
index d635490f59d3..3d811f652603 100644
--- a/net/mac80211/status.c
+++ b/net/mac80211/status.c
@@ -777,7 +777,7 @@ static void ieee80211_report_used_skb(struct ieee80211_local *local,
ieee80211_sta_update_pending_airtime(local, sta,
skb_get_queue_mapping(skb),
tx_time_est,
- true);
+ true, info->tx_time_mc);
rcu_read_unlock();
}
@@ -1189,10 +1189,11 @@ void ieee80211_tx_status_ext(struct ieee80211_hw *hw,
/* Do this here to avoid the expensive lookup of the sta
* in ieee80211_report_used_skb().
*/
+ bool mcast = IEEE80211_SKB_CB(skb)->tx_time_mc;
ieee80211_sta_update_pending_airtime(local, sta,
skb_get_queue_mapping(skb),
tx_time_est,
- true);
+ true, mcast);
ieee80211_info_set_tx_time_est(IEEE80211_SKB_CB(skb), 0);
}
diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c
index eb36f1b9771b..c6ebc0231090 100644
--- a/net/mac80211/tx.c
+++ b/net/mac80211/tx.c
@@ -4049,20 +4049,20 @@ struct sk_buff *ieee80211_tx_dequeue(struct ieee80211_hw *hw,
encap_out:
info->control.vif = vif;
- if (tx.sta &&
- wiphy_ext_feature_isset(local->hw.wiphy, NL80211_EXT_FEATURE_AQL)) {
- bool ampdu = txq->ac != IEEE80211_AC_VO;
+ if (wiphy_ext_feature_isset(local->hw.wiphy, NL80211_EXT_FEATURE_AQL)) {
+ bool ampdu = txq->sta && txq->ac != IEEE80211_AC_VO;
u32 airtime;
airtime = ieee80211_calc_expected_tx_airtime(hw, vif, txq->sta,
skb->len, ampdu);
- if (airtime) {
- airtime = ieee80211_info_set_tx_time_est(info, airtime);
- ieee80211_sta_update_pending_airtime(local, tx.sta,
- txq->ac,
- airtime,
- false);
- }
+ if (!airtime)
+ return skb;
+
+ airtime = ieee80211_info_set_tx_time_est(info, airtime);
+ info->tx_time_mc = !tx.sta;
+ ieee80211_sta_update_pending_airtime(local, tx.sta, txq->ac,
+ airtime, false,
+ info->tx_time_mc);
}
return skb;
@@ -4114,6 +4114,7 @@ struct ieee80211_txq *ieee80211_next_txq(struct ieee80211_hw *hw, u8 ac)
struct ieee80211_txq *ret = NULL;
struct txq_info *txqi = NULL, *head = NULL;
bool found_eligible_txq = false;
+ bool aql_check;
spin_lock_bh(&local->active_txq_lock[ac]);
@@ -4137,26 +4138,28 @@ struct ieee80211_txq *ieee80211_next_txq(struct ieee80211_hw *hw, u8 ac)
if (!head)
head = txqi;
+ aql_check = ieee80211_txq_airtime_check(hw, &txqi->txq);
+ if (aql_check)
+ found_eligible_txq = true;
+
if (txqi->txq.sta) {
struct sta_info *sta = container_of(txqi->txq.sta,
struct sta_info, sta);
- bool aql_check = ieee80211_txq_airtime_check(hw, &txqi->txq);
- s32 deficit = ieee80211_sta_deficit(sta, txqi->txq.ac);
- if (aql_check)
- found_eligible_txq = true;
-
- if (deficit < 0)
+ if (ieee80211_sta_deficit(sta, txqi->txq.ac) < 0) {
sta->airtime[txqi->txq.ac].deficit +=
sta->airtime_weight;
- if (deficit < 0 || !aql_check) {
- list_move_tail(&txqi->schedule_order,
- &local->active_txqs[txqi->txq.ac]);
- goto begin;
+ aql_check = false;
}
}
+ if (!aql_check) {
+ list_move_tail(&txqi->schedule_order,
+ &local->active_txqs[txqi->txq.ac]);
+ goto begin;
+ }
+
if (txqi->schedule_round == local->schedule_round[ac])
goto out;
@@ -4223,7 +4226,8 @@ bool ieee80211_txq_airtime_check(struct ieee80211_hw *hw,
return true;
if (!txq->sta)
- return true;
+ return atomic_read(&local->aql_mc_pending_airtime) <
+ local->aql_txq_limit_mc;
if (unlikely(txq->tid == IEEE80211_NUM_TIDS))
return true;
@@ -4272,15 +4276,15 @@ bool ieee80211_txq_may_transmit(struct ieee80211_hw *hw,
spin_lock_bh(&local->active_txq_lock[ac]);
- if (!txqi->txq.sta)
- goto out;
-
if (list_empty(&txqi->schedule_order))
goto out;
if (!ieee80211_txq_schedule_airtime_check(local, ac))
goto out;
+ if (!txqi->txq.sta)
+ goto out;
+
list_for_each_entry_safe(iter, tmp, &local->active_txqs[ac],
schedule_order) {
if (iter == txqi)
--
2.53.0
^ permalink raw reply related
* [PATCH wireless-next v5 1/4] wifi: mac80211: factor out part of ieee80211_calc_expected_tx_airtime
From: Felix Fietkau @ 2026-07-24 11:54 UTC (permalink / raw)
To: linux-wireless; +Cc: johannes
Create ieee80211_rate_expected_tx_airtime helper function, which returns
the expected tx airtime for a given rate and packet length in units of
1/1024 usec, for more accuracy.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
net/mac80211/airtime.c | 87 ++++++++++++++++++++++----------------
net/mac80211/ieee80211_i.h | 5 +++
2 files changed, 56 insertions(+), 36 deletions(-)
diff --git a/net/mac80211/airtime.c b/net/mac80211/airtime.c
index c61df637232a..0c54cdbd753c 100644
--- a/net/mac80211/airtime.c
+++ b/net/mac80211/airtime.c
@@ -685,7 +685,7 @@ static int ieee80211_fill_rx_status(struct ieee80211_rx_status *stat,
if (ieee80211_fill_rate_info(hw, stat, band, ri))
return 0;
- if (!ieee80211_rate_valid(rate))
+ if (!rate || !ieee80211_rate_valid(rate))
return -1;
if (rate->flags & IEEE80211_TX_RC_160_MHZ_WIDTH)
@@ -753,6 +753,53 @@ u32 ieee80211_calc_tx_airtime(struct ieee80211_hw *hw,
}
EXPORT_SYMBOL_GPL(ieee80211_calc_tx_airtime);
+u32 ieee80211_rate_expected_tx_airtime(struct ieee80211_hw *hw,
+ struct ieee80211_tx_rate *tx_rate,
+ struct rate_info *ri,
+ enum nl80211_band band,
+ bool ampdu, int len)
+{
+ struct ieee80211_rx_status stat;
+ u32 duration, overhead;
+ u8 agg_shift;
+
+ if (ieee80211_fill_rx_status(&stat, hw, tx_rate, ri, band, len))
+ return 0;
+
+ if (stat.encoding == RX_ENC_LEGACY || !ampdu)
+ return ieee80211_calc_rx_airtime(hw, &stat, len) * 1024;
+
+ duration = ieee80211_get_rate_duration(hw, &stat, &overhead);
+
+ /*
+ * Assume that HT/VHT transmission on any AC except VO will
+ * use aggregation. Since we don't have reliable reporting
+ * of aggregation length, assume an average size based on the
+ * tx rate.
+ * This will not be very accurate, but much better than simply
+ * assuming un-aggregated tx in all cases.
+ */
+ if (duration > 400 * 1024) /* <= VHT20 MCS2 1S */
+ agg_shift = 1;
+ else if (duration > 250 * 1024) /* <= VHT20 MCS3 1S or MCS1 2S */
+ agg_shift = 2;
+ else if (duration > 150 * 1024) /* <= VHT20 MCS5 1S or MCS2 2S */
+ agg_shift = 3;
+ else if (duration > 70 * 1024) /* <= VHT20 MCS5 2S */
+ agg_shift = 4;
+ else if (stat.encoding != RX_ENC_HE ||
+ duration > 20 * 1024) /* <= HE40 MCS6 2S */
+ agg_shift = 5;
+ else
+ agg_shift = 6;
+
+ duration *= len;
+ duration /= AVG_PKT_SIZE;
+ duration += (overhead * 1024 >> agg_shift);
+
+ return duration;
+}
+
u32 ieee80211_calc_expected_tx_airtime(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
struct ieee80211_sta *pubsta,
@@ -775,45 +822,13 @@ u32 ieee80211_calc_expected_tx_airtime(struct ieee80211_hw *hw,
if (pubsta) {
struct sta_info *sta = container_of(pubsta, struct sta_info,
sta);
- struct ieee80211_rx_status stat;
struct ieee80211_tx_rate *tx_rate = &sta->deflink.tx_stats.last_rate;
struct rate_info *ri = &sta->deflink.tx_stats.last_rate_info;
- u32 duration, overhead;
- u8 agg_shift;
+ u32 duration;
- if (ieee80211_fill_rx_status(&stat, hw, tx_rate, ri, band, len))
- return 0;
-
- if (stat.encoding == RX_ENC_LEGACY || !ampdu)
- return ieee80211_calc_rx_airtime(hw, &stat, len);
-
- duration = ieee80211_get_rate_duration(hw, &stat, &overhead);
- /*
- * Assume that HT/VHT transmission on any AC except VO will
- * use aggregation. Since we don't have reliable reporting
- * of aggregation length, assume an average size based on the
- * tx rate.
- * This will not be very accurate, but much better than simply
- * assuming un-aggregated tx in all cases.
- */
- if (duration > 400 * 1024) /* <= VHT20 MCS2 1S */
- agg_shift = 1;
- else if (duration > 250 * 1024) /* <= VHT20 MCS3 1S or MCS1 2S */
- agg_shift = 2;
- else if (duration > 150 * 1024) /* <= VHT20 MCS5 1S or MCS2 2S */
- agg_shift = 3;
- else if (duration > 70 * 1024) /* <= VHT20 MCS5 2S */
- agg_shift = 4;
- else if (stat.encoding != RX_ENC_HE ||
- duration > 20 * 1024) /* <= HE40 MCS6 2S */
- agg_shift = 5;
- else
- agg_shift = 6;
-
- duration *= len;
- duration /= AVG_PKT_SIZE;
+ duration = ieee80211_rate_expected_tx_airtime(hw, tx_rate, ri,
+ band, true, len);
duration /= 1024;
- duration += (overhead >> agg_shift);
return max_t(u32, duration, 4);
}
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 11f449e8ff00..4034b71a31cf 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -2937,6 +2937,11 @@ u8 *ieee80211_get_bssid(struct ieee80211_hdr *hdr, size_t len,
extern const struct ethtool_ops ieee80211_ethtool_ops;
+u32 ieee80211_rate_expected_tx_airtime(struct ieee80211_hw *hw,
+ struct ieee80211_tx_rate *tx_rate,
+ struct rate_info *ri,
+ enum nl80211_band band,
+ bool ampdu, int len);
u32 ieee80211_calc_expected_tx_airtime(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
struct ieee80211_sta *pubsta,
--
2.53.0
^ permalink raw reply related
* [PATCH wireless-next v5 4/4] wifi: mac80211: add ieee80211_txq_aql_pending()
From: Felix Fietkau @ 2026-07-24 11:54 UTC (permalink / raw)
To: linux-wireless; +Cc: johannes
In-Reply-To: <20260724115429.3921457-1-nbd@nbd.name>
Add a function to allow drivers to query the pending AQL airtime
for a given txq, for both unicast and broadcast.
This will be used for mt76 to limit buffering in AP mode for power-save
stations.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
v2: reorder txq->tid check
v3: fix compile error - adjust for changes in previous patch
include/net/mac80211.h | 11 +++++++++++
net/mac80211/tx.c | 18 ++++++++++++++++++
2 files changed, 29 insertions(+)
diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 2fde6601beec..5edaefcc57f0 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -6893,6 +6893,17 @@ void ieee80211_sta_register_airtime(struct ieee80211_sta *pubsta, u8 tid,
bool
ieee80211_txq_airtime_check(struct ieee80211_hw *hw, struct ieee80211_txq *txq);
+/**
+ * ieee80211_txq_aql_pending - get pending AQL airtime for a txq
+ *
+ * @hw: pointer obtained from ieee80211_alloc_hw()
+ * @txq: pointer obtained from station or virtual interface
+ *
+ * Return: pending airtime (in usec) for the given txq.
+ */
+u32 ieee80211_txq_aql_pending(struct ieee80211_hw *hw,
+ struct ieee80211_txq *txq);
+
/**
* ieee80211_iter_keys - iterate keys programmed into the device
* @hw: pointer obtained from ieee80211_alloc_hw()
diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c
index c6ebc0231090..fd8c02d235e1 100644
--- a/net/mac80211/tx.c
+++ b/net/mac80211/tx.c
@@ -4247,6 +4247,24 @@ bool ieee80211_txq_airtime_check(struct ieee80211_hw *hw,
}
EXPORT_SYMBOL(ieee80211_txq_airtime_check);
+u32 ieee80211_txq_aql_pending(struct ieee80211_hw *hw,
+ struct ieee80211_txq *txq)
+{
+ struct ieee80211_local *local = hw_to_local(hw);
+ struct sta_info *sta;
+
+ if (unlikely(txq->tid == IEEE80211_NUM_TIDS))
+ return 0;
+
+ if (!txq->sta)
+ return atomic_read(&local->aql_mc_pending_airtime);
+
+ sta = container_of(txq->sta, struct sta_info, sta);
+
+ return atomic_read(&sta->airtime[txq->ac].aql_tx_pending);
+}
+EXPORT_SYMBOL(ieee80211_txq_aql_pending);
+
static bool
ieee80211_txq_schedule_airtime_check(struct ieee80211_local *local, u8 ac)
{
--
2.53.0
^ permalink raw reply related
* [PATCH wireless-next] wifi: nxpwifi: reject zero-length extension elements in beacon IEs
From: Linmao Li @ 2026-07-24 10:36 UTC (permalink / raw)
To: Jeff Chen
Cc: Francesco Dolcini, Johannes Berg, linux-wireless, linux-kernel,
Linmao Li
nxpwifi_update_bss_desc_with_ie() dispatches on elem->data[0] for
WLAN_EID_EXTENSION without checking that the element has a payload.
A well-formed extension element carries at least the element ID
extension byte, but nothing enforces that in the IE stream, and the
loop accepts a zero-length element because its header alone fits.
elem->data[0] then reads the byte after the element, which is past the
kmemdup()ed IE buffer when that element ends the stream.
Fixes: 73b01e57ed3e ("wifi: nxp: add nxpwifi driver for IW61x")
Signed-off-by: Linmao Li <lilinmao@kylinos.cn>
---
drivers/net/wireless/nxp/nxpwifi/scan.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/drivers/net/wireless/nxp/nxpwifi/scan.c b/drivers/net/wireless/nxp/nxpwifi/scan.c
index bb3ce2b6f4b9..b77056983e83 100644
--- a/drivers/net/wireless/nxp/nxpwifi/scan.c
+++ b/drivers/net/wireless/nxp/nxpwifi/scan.c
@@ -1255,6 +1255,9 @@ int nxpwifi_update_bss_desc_with_ie(struct nxpwifi_adapter *adapter,
(u16)(current_ptr - bss_entry->beacon_buf);
break;
case WLAN_EID_EXTENSION:
+ if (!element_len)
+ return -EINVAL;
+
elem = (struct element *)current_ptr;
switch (elem->data[0]) {
--
2.25.1
^ permalink raw reply related
* [PATCH] wifi: iwlegacy: replace BUG_ON() with WARN_ON() on num_stations check
From: stf_xl @ 2026-07-24 9:55 UTC (permalink / raw)
To: linux-wireless; +Cc: martin-eric.racine
From: Stanislaw Gruszka <stf_xl@wp.pl>
BUG_ON() for il->num_stations < 0 can happen in real word, see
https://bugzilla.kernel.org/show_bug.cgi?id=221733
Replace BUG_ON() with WARN_ON() (and reset the counter to 0) to
do not put whole system to inconsistent state on the condition.
Also allocate debugfs buffer for all stations (32 or 25)
to do not use num_stations since it can not be valid number.
Signed-off-by: Stanislaw Gruszka <stf_xl@wp.pl>
---
drivers/net/wireless/intel/iwlegacy/common.c | 7 ++++---
drivers/net/wireless/intel/iwlegacy/debug.c | 2 +-
2 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/drivers/net/wireless/intel/iwlegacy/common.c b/drivers/net/wireless/intel/iwlegacy/common.c
index 8d0ff339ad08..0bb807ff8edf 100644
--- a/drivers/net/wireless/intel/iwlegacy/common.c
+++ b/drivers/net/wireless/intel/iwlegacy/common.c
@@ -2179,8 +2179,8 @@ il_remove_station(struct il_priv *il, const u8 sta_id, const u8 * addr)
il->stations[sta_id].used &= ~IL_STA_DRIVER_ACTIVE;
il->num_stations--;
-
- BUG_ON(il->num_stations < 0);
+ if (WARN_ON(il->num_stations < 0))
+ il->num_stations = 0;
spin_unlock_irqrestore(&il->sta_lock, flags);
@@ -2328,7 +2328,8 @@ il_dealloc_bcast_stations(struct il_priv *il)
il->stations[i].used &= ~IL_STA_UCODE_ACTIVE;
il->num_stations--;
- BUG_ON(il->num_stations < 0);
+ if (WARN_ON(il->num_stations < 0))
+ il->num_stations = 0;
kfree(il->stations[i].lq);
il->stations[i].lq = NULL;
}
diff --git a/drivers/net/wireless/intel/iwlegacy/debug.c b/drivers/net/wireless/intel/iwlegacy/debug.c
index d998a3f1b056..8a9f79ff1c6e 100644
--- a/drivers/net/wireless/intel/iwlegacy/debug.c
+++ b/drivers/net/wireless/intel/iwlegacy/debug.c
@@ -396,7 +396,7 @@ il_dbgfs_stations_read(struct file *file, char __user *user_buf, size_t count,
int i, j, pos = 0;
ssize_t ret;
/* Add 30 for initial string */
- const size_t bufsz = 30 + sizeof(char) * 500 * (il->num_stations);
+ const size_t bufsz = 30 + sizeof(char) * 500 * max_sta;
buf = kmalloc(bufsz, GFP_KERNEL);
if (!buf)
--
2.50.1
^ permalink raw reply related
* Re: [PATCH v4 1/4] wifi: ath11k: use queue mapping for WCN6750 ring selection
From: Jose Ignacio Tornos Martinez @ 2026-07-24 9:42 UTC (permalink / raw)
To: tamizh.raja
Cc: ath11k, ath12k, jjohnson, jtornosm, linux-kernel, linux-wireless
In-Reply-To: <CABkEBKaZOEh4OrZTYfFOj8QZHY8Ruxn2rcXL-axWHwK4o6MECA@mail.gmail.com>
Hello Tamizh,
This is the same mapping already used by WCN7850 via skb_get_queue_mapping().
The patch only aligns WCN6750 with that existing approach.
Thanks
Best regards
José Ignacio
^ permalink raw reply
* RE: [PATCH v2 1/4] wifi: rtw88: 8723bs: add the core handling required by RTL8723BS
From: Ping-Ke Shih @ 2026-07-24 9:29 UTC (permalink / raw)
To: luka.gejak@linux.dev
Cc: linux-wireless@vger.kernel.org, linux-kernel@vger.kernel.org,
Michael Straube, Peter Robinson, Bitterblue Smith
In-Reply-To: <20260722200337.135150-2-luka.gejak@linux.dev>
luka.gejak@linux.dev <luka.gejak@linux.dev> wrote:
> From: Luka Gejak <luka.gejak@linux.dev>
>
> Several of those need explaining, because the reason is not visible in
> the code:
>
It seems like you misunderstood what I meant. You should add each necessary
functions by individual patch. Don't mix them.
(Sorry, I'd reply the cover letter you wrote next week.)
If you consider these all are necessary, send preparations as first patchset,
and start to add RTL8723BS by second patchset. With a large patchset, I can't
respond quickly.
Ping-Ke
^ permalink raw reply
* [PATCH] wifi: brcmfmac: Set DMA direction for msgbuf packet IDs
From: Can Peng @ 2026-07-24 9:25 UTC (permalink / raw)
To: arend.vanspriel
Cc: linux-wireless, brcm80211, brcm80211-dev-list.pdl, linux-kernel,
Can Peng, stable
brcmf_msgbuf_init_pktids() takes the DMA direction from its callers,
but never stores it in the packet ID state. Since the state is zeroed,
pktids->direction remains DMA_BIDIRECTIONAL for both the TX and RX
packet ID pools.
All msgbuf packet ID map and unmap paths use pktids->direction. As a
result, TX buffers requested with DMA_TO_DEVICE and RX buffers requested
with DMA_FROM_DEVICE are mapped and unmapped as DMA_BIDIRECTIONAL
instead.
Store the caller-provided direction when initializing the packet ID
state.
Fixes: 9a1bb60250d2 ("brcmfmac: Adding msgbuf protocol.")
Cc: stable@vger.kernel.org
Signed-off-by: Can Peng <pengcan@kylinos.cn>
---
drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c
index ba1ce1552e0f..c1feb164d845 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c
@@ -310,6 +310,7 @@ brcmf_msgbuf_init_pktids(u32 nr_array_entries,
}
pktids->array = array;
pktids->array_size = nr_array_entries;
+ pktids->direction = direction;
return pktids;
}
--
2.53.0
^ permalink raw reply related
* Re: [PATCH v8 5/9] block: implement NVMEM provider
From: Loic Poulain @ 2026-07-24 9:10 UTC (permalink / raw)
To: Christoph Hellwig
Cc: Ulf Hansson, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Bjorn Andersson, Konrad Dybcio, Jens Axboe, Johannes Berg,
Jeff Johnson, Bartosz Golaszewski, Marcel Holtmann,
Luiz Augusto von Dentz, Balakrishna Godavarthi, Rocky Liao,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Srinivas Kandagatla, Andrew Lunn, Heiner Kallweit,
Russell King, Saravana Kannan, Christian Marangi, linux-mmc,
devicetree, linux-kernel, linux-arm-msm, linux-block,
linux-wireless, ath10k, linux-bluetooth, netdev, daniel,
Bartosz Golaszewski
In-Reply-To: <amLwz6WuHYUwKKJo@infradead.org>
Hi Christoph,
On Fri, Jul 24, 2026 at 6:57 AM Christoph Hellwig <hch@infradead.org> wrote:
>
> On Fri, Jul 03, 2026 at 03:45:18PM +0200, Loic Poulain wrote:
> > + Allow block devices (or partitions) to act as NVMEM providers,
>
> Based on the code below I don't think you actually support partitions.
Right, I was planning to add partition support in a follow-up series.
But since this is really supposed to be a block-agnostic feature,
whether it's a disk or a partition, I'll include partition in v8.
>
> > +static int blk_nvmem_reg_read(void *priv, unsigned int from, void *val, size_t bytes)
>
> Overly long line.
>
> > +{
> > + dev_t devt = (dev_t)(uintptr_t)priv;
> > + size_t bytes_left = bytes;
> > + loff_t pos = from;
> > + int ret = 0;
> > +
> > + struct file *bdev_file __free(fput) =
> > + bdev_file_open_by_dev(devt, BLK_OPEN_READ, NULL, NULL);
>
> Please try to avoid all that magic autfree stuff that makes the code
> impossible to read about.
Ack, I've received mixed feedback on this. However, given that there
aren't any particularly complex or numerous exit paths, I guess the
explicit release will make the code clearer.
> > + if (IS_ERR(bdev_file))
> > + return PTR_ERR(bdev_file);
> > +
> > + while (bytes_left) {
>
> .. and just use __kernel_read instead of badly reimplementing it.
Ack.
>
> > + config.name = dev_name(dev);
> > + config.owner = THIS_MODULE;
> > + config.priv = (void *)(uintptr_t)dev->devt;
>
> Why is this storing the dev_t and not the block device itself?
It was originally intended to avoid races with device removal, but now
that we're synchronized with the block subsystem, that's no longer
strictly necessary.
>
> > +
> > + blk_nvmem_add(disk->part0);
>
> Hmm, and you're always doing it for the whole device. So the intefaces
> really should be based on a gendisk, not a block_device.
I will add it for partitions as well.
>
> Also once you register the whole devices as nvmem provider, we really
> should keep it from being used a block device, shouldn't we?
I'm not sure, the whole concept is block backed NVMEM, so we still
need to add that block as base layer, though we could discuss
suppressing uevent for it. Preventing block usage would make the
feature much more intrusive than other NVMEM providers like MTD, where
the underlying device stays accessible. And we may genuinely still
want to write the disk (e.g. for factory provisioning), updating the
very data the cells point to, etc. There's also the layering aspect,
if a disk (e.g. mmcblk0) has logical partitions on top (mmcblk0pX), we
still register/expose the whole disk mmcblk0 as a block device
regardless. So if a disk contains NVMEM cells, I think it should still
be exposed as a block device as well.
Regards,
Loic
^ permalink raw reply
* RE: [PATCH v2] wifi: rtlwifi: pci: fix error path in rtl_pci_probe()
From: Ping-Ke Shih @ 2026-07-24 9:07 UTC (permalink / raw)
To: Abdun Nihaal; +Cc: linux-wireless@vger.kernel.org, linux-kernel@vger.kernel.org
In-Reply-To: <20260723120118.145383-1-nihaal@cse.iitm.ac.in>
Abdun Nihaal <nihaal@cse.iitm.ac.in> wrote:
> In the last error path in rtl_pci_probe(), the cleanup functions are
> skipped due to a wrong goto label. Moreover, the successful call to
> rtl_init_rfkill(), ieee80211_register_hw(), rtl_debug_add_one() have to
> be reverted. Fix this issue by updating the labels and adding the
> relevant cleanup functions to the last error path.
>
> Fixes: 0c8173385e54 ("rtl8192ce: Add new driver")
> Signed-off-by: Abdun Nihaal <nihaal@cse.iitm.ac.in>
Acked-by: Ping-Ke Shih <pkshih@realtek.com>
^ permalink raw reply
* RE: [PATCH v2] wifi: rtlwifi: rtl8192du: Fix possible memory leak in rtl92du_init_sw_vars()
From: Ping-Ke Shih @ 2026-07-24 9:06 UTC (permalink / raw)
To: Abdun Nihaal
Cc: linux-wireless@vger.kernel.org, linux-kernel@vger.kernel.org,
rtl8821cerfe2@gmail.com, stable@vger.kernel.org
In-Reply-To: <20260723114539.136986-1-nihaal@cse.iitm.ac.in>
Abdun Nihaal <nihaal@cse.iitm.ac.in> wrote:
> The memory allocated inside rtl92du_init_shared_data() is not freed in
> any of the subsequent error paths in rtl92du_init_sw_vars().
> Fix that by adding a call to rtl92du_deinit_shared_data() in the error
> path.
>
> Fixes: b5dc8873b6ff ("wifi: rtlwifi: Add rtl8192du/sw.c")
> Cc: stable@vger.kernel.org
> Signed-off-by: Abdun Nihaal <nihaal@cse.iitm.ac.in>
Acked-by: Ping-Ke Shih <pkshih@realtek.com>
^ permalink raw reply
* Re: [PATCH v4 1/4] wifi: ath11k: use queue mapping for WCN6750 ring selection
From: Tamizh Raja @ 2026-07-24 9:04 UTC (permalink / raw)
To: Jose Ignacio Tornos Martinez
Cc: jjohnson, ath11k, ath12k, linux-wireless, linux-kernel
In-Reply-To: <20260724053121.15759-2-jtornosm@redhat.com>
On Fri, Jul 24, 2026 at 11:01 AM Jose Ignacio Tornos Martinez
<jtornosm@redhat.com> wrote:
>
> WCN6750 selects the TCL ring using skb_get_hash() to distribute
> flows across its 3 TX rings, as introduced in commit 7636c9a6e7d7
> ("wifi: ath11k: Add multi TX ring support for WCN6750"). The goal
> was to prevent out-of-order packet delivery that could occur with
> smp_processor_id()-based selection, where packets of the same flow
> could end up on different rings depending on CPU scheduling.
>
> Switch to skb_get_queue_mapping() instead, which returns the AC
> (access category) assigned by mac80211 in ieee80211_select_queue().
> This provides the same ordering guarantee: packets of the same TID
> always map to the same AC and therefore always land on the same
> ring, preventing reordering.
>
> Using queue mapping for ring selection also provides QoS-aware
> distribution, where each traffic class gets a deterministic ring
> assignment, rather than the random distribution that flow hashing
> produces. With 3 rings and 4 ACs (VO=0, VI=1, BE=2, BK=3), the
> mapping becomes: VO and BK share ring 0, VI uses ring 1, and BE
> uses ring 2.
Combining VO and BK is fine? since VO is a high priority. @baochen any
thought on this?
>
> This matches the approach already used by ath12k for WCN7850, which
> has the same ring count and uses skb_get_queue_mapping() for its
> ring selector.
>
> This change also removes the dependency on skb_get_hash(), which
> is relevant for a subsequent patch that removes the skb parameter
> from the get_ring_selector hw_ops callback entirely, enabling
> TX flow control in wake_tx_queue without requiring a complex peek
> to inspect frames before dequeue.
>
> Signed-off-by: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>
> ---
> v4: new patch
>
> drivers/net/wireless/ath/ath11k/hw.c | 8 +-------
> 1 file changed, 1 insertion(+), 7 deletions(-)
>
> diff --git a/drivers/net/wireless/ath/ath11k/hw.c b/drivers/net/wireless/ath/ath11k/hw.c
> index 93f9a03c48dc..d679e39dce03 100644
> --- a/drivers/net/wireless/ath/ath11k/hw.c
> +++ b/drivers/net/wireless/ath/ath11k/hw.c
> @@ -891,13 +891,7 @@ static u32 ath11k_hw_ipq8074_get_tcl_ring_selector(struct sk_buff *skb)
>
> static u32 ath11k_hw_wcn6750_get_tcl_ring_selector(struct sk_buff *skb)
> {
> - /* Select the TCL ring based on the flow hash of the SKB instead
> - * of CPU ID. Since applications pumping the traffic can be scheduled
> - * on multiple CPUs, there is a chance that packets of the same flow
> - * could end on different TCL rings, this could sometimes results in
> - * an out of order arrival of the packets at the receiver.
> - */
> - return skb_get_hash(skb);
> + return skb_get_queue_mapping(skb);
> }
>
> const struct ath11k_hw_ops ipq8074_ops = {
> --
> 2.54.0
>
>
--
- Tamizh.
^ permalink raw reply
* Re: [PATCH ath-next] wifi: ath12k: skip MLO multicast links during crash recovery in Tx path
From: Rameshkumar Sundaram @ 2026-07-24 8:58 UTC (permalink / raw)
To: Pavankumar Nandeshwar, ath12k; +Cc: linux-wireless
In-Reply-To: <20260723054653.2794550-1-pavankumar.nandeshwar@oss.qualcomm.com>
On 7/23/2026 11:16 AM, Pavankumar Nandeshwar wrote:
> In ath12k_wifi7_mac_op_tx(), the MLO multicast broadcast path iterates
> over all active links and copies the original skb for transmission on
> each link. When firmware crash recovery is underway (ATH12K_FLAG_CRASH_FLUSH
> set), the per-link copy is allocated and partially processed before
> ath12k_wifi7_dp_tx() eventually rejects it with -ESHUTDOWN.
>
> This wastes GFP_ATOMIC memory and produces spurious "failed to transmit
> frame" warnings for every active MLO link during the recovery window.
> The unicast and non-MLO paths are unaffected: they call ath12k_wifi7_dp_tx()
> directly, which already guards against the flag at its entry.
>
> Skip any link whose associated ath12k_base has ATH12K_FLAG_CRASH_FLUSH set
> before performing the skb_copy(), matching the behaviour of
> ath12k_wifi7_dp_tx() but avoiding the unnecessary allocation entirely.
>
> 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
>
> Signed-off-by: Pavankumar Nandeshwar <pavankumar.nandeshwar@oss.qualcomm.com>
Reviewed-by: Rameshkumar Sundaram <rameshkumar.sundaram@oss.qualcomm.com>
^ permalink raw reply
* [PATCH] wifi: mt76: mt7925: add MT7928 debugfs txpower_sku with power table selection
From: JB Tsai @ 2026-07-24 8:52 UTC (permalink / raw)
To: nbd, lorenzo
Cc: linux-wireless, linux-mediatek, Sean.Wang, Quan.Zhou, Ryder.Lee,
litien.chang, Charlie-cy.Wu, jb.tsai
From: Charlie-cy Wu <Charlie-cy.Wu@mediatek.com>
Add MT7928-specific debugfs interface for txpower_sku that supports
multiple power table selection. MT7928 extends the power structure with
additional OFDM bandwidth variants (OFDM20/40/80/160) compared to MT7925.
Key changes:
- Add struct mt7928_txpwr with OFDM20/40/80/160 support
- Implement mt7928_txpwr() handler for MT7928-specific power display
- Add txpower_tbl debugfs control for selecting power tables:
* 0: STA_SISO
* 1: STA_CDD
* 2: STA_MIMO
* 3: P2P_SISO
* 4: P2P_CDD
* 5: P2P_MIMO
- Extend mt7925_txpwr_req with power_tbl field for FW communication
- Add mt7928_txpwr_event structure for handling FW responses
- Store txpwr_tbl in mt792x_dev for per-device power table tracking
Usage:
echo 0 > /sys/kernel/debug/ieee80211/phy0/mt76/txpower_tbl
cat /sys/kernel/debug/ieee80211/phy0/mt76/txpower_sku
Signed-off-by: Charlie-cy Wu <Charlie-cy.Wu@mediatek.com>
---
.../wireless/mediatek/mt76/mt7925/debugfs.c | 151 +++++++++++++++++-
.../net/wireless/mediatek/mt76/mt7925/mcu.c | 27 ++++
.../net/wireless/mediatek/mt76/mt7925/mcu.h | 20 ++-
.../wireless/mediatek/mt76/mt7925/mt7925.h | 39 +++++
drivers/net/wireless/mediatek/mt76/mt792x.h | 1 +
5 files changed, 235 insertions(+), 3 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c b/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c
index e2498659c884..3db564d63c15 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c
@@ -178,6 +178,147 @@ mt7925_txpwr(struct seq_file *s, void *data)
return ret;
}
+static inline void
+mt7928_eht_txpwr(struct seq_file *s, struct mt7928_txpwr *txpwr, u8 band_idx)
+{
+ seq_printf(s, "%-22s %6s %6s %6s %6s %6s %6s %6s %6s %6s %6s %6s %6s %6s %6s %6s %6s\n",
+ " ", "mcs0", "mcs1", "mcs2", "mcs3", "mcs4", "mcs5",
+ "mcs6", "mcs7", "mcs8", "mcs9", "mcs10", "mcs11",
+ "mcs12", "mcs13", "mcs14", "mcs15");
+ mt7925_print_txpwr_entry(EHT26, eht26, band_idx);
+ mt7925_print_txpwr_entry(EHT52, eht52, band_idx);
+ mt7925_print_txpwr_entry(EHT106, eht106, band_idx);
+ mt7925_print_txpwr_entry(EHT242, eht242, band_idx);
+ mt7925_print_txpwr_entry(EHT484, eht484, band_idx);
+
+ mt7925_print_txpwr_entry(EHT996, eht996, band_idx);
+ mt7925_print_txpwr_entry(EHT996x2, eht996x2, band_idx);
+ mt7925_print_txpwr_entry(EHT996x4, eht996x4, band_idx);
+ mt7925_print_txpwr_entry(EHT26_52, eht26_52, band_idx);
+ mt7925_print_txpwr_entry(EHT26_106, eht26_106, band_idx);
+ mt7925_print_txpwr_entry(EHT484_242, eht484_242, band_idx);
+ mt7925_print_txpwr_entry(EHT996_484, eht996_484, band_idx);
+ mt7925_print_txpwr_entry(EHT996_484_242, eht996_484_242, band_idx);
+ mt7925_print_txpwr_entry(EHT996x2_484, eht996x2_484, band_idx);
+ mt7925_print_txpwr_entry(EHT996x3, eht996x3, band_idx);
+ mt7925_print_txpwr_entry(EHT996x3_484, eht996x3_484, band_idx);
+}
+
+static int
+mt7928_txpwr(struct seq_file *s, void *data)
+{
+ struct mt792x_dev *dev = dev_get_drvdata(s->private);
+ struct mt7928_txpwr *txpwr = NULL;
+ u8 band_idx = dev->mphy.band_idx;
+ int ret = 0;
+
+ txpwr = devm_kmalloc(dev->mt76.dev, sizeof(*txpwr), GFP_KERNEL);
+
+ if (!txpwr)
+ return -ENOMEM;
+
+ mt792x_mutex_acquire(dev);
+ ret = mt7928_get_txpwr_info(dev, band_idx, dev->txpwr_tbl, txpwr);
+ mt792x_mutex_release(dev);
+
+ if (ret)
+ goto out;
+
+ /* Display power table name based on the index */
+ switch (dev->txpwr_tbl) {
+ case 0:
+ seq_printf(s, "Power Table: %d (STA_SISO)\n", dev->txpwr_tbl);
+ break;
+ case 1:
+ seq_printf(s, "Power Table: %d (STA_CDD)\n", dev->txpwr_tbl);
+ break;
+ case 2:
+ seq_printf(s, "Power Table: %d (STA_MIMO)\n", dev->txpwr_tbl);
+ break;
+ case 3:
+ seq_printf(s, "Power Table: %d (P2P_SISO)\n", dev->txpwr_tbl);
+ break;
+ case 4:
+ seq_printf(s, "Power Table: %d (P2P_CDD)\n", dev->txpwr_tbl);
+ break;
+ case 5:
+ seq_printf(s, "Power Table: %d (P2P_MIMO)\n", dev->txpwr_tbl);
+ break;
+ default:
+ seq_printf(s, "Power Table: %d (Unknown)\n", dev->txpwr_tbl);
+ break;
+ }
+
+ seq_printf(s, "%-22s %6s %6s %6s %6s\n",
+ " ", "1m", "2m", "5m", "11m");
+ mt7925_print_txpwr_entry(CCK, cck, band_idx);
+
+ seq_printf(s, "%-22s %6s %6s %6s %6s %6s %6s %6s %6s\n",
+ " ", "6m", "9m", "12m", "18m", "24m", "36m",
+ "48m", "54m");
+ mt7925_print_txpwr_entry(OFDM20, ofdm20, band_idx);
+ mt7925_print_txpwr_entry(OFDM40, ofdm40, band_idx);
+ mt7925_print_txpwr_entry(OFDM80, ofdm80, band_idx);
+ mt7925_print_txpwr_entry(OFDM160, ofdm160, band_idx);
+
+ seq_printf(s, "%-22s %6s %6s %6s %6s %6s %6s %6s %6s\n",
+ " ", "mcs0", "mcs1", "mcs2", "mcs3", "mcs4", "mcs5",
+ "mcs6", "mcs7");
+ mt7925_print_txpwr_entry(HT20, ht20, band_idx);
+
+ seq_printf(s, "%-22s %6s %6s %6s %6s %6s %6s %6s %6s %6s\n",
+ " ", "mcs0", "mcs1", "mcs2", "mcs3", "mcs4", "mcs5",
+ "mcs6", "mcs7", "mcs32");
+ mt7925_print_txpwr_entry(HT40, ht40, band_idx);
+
+ seq_printf(s, "%-22s %6s %6s %6s %6s %6s %6s %6s %6s %6s %6s %6s %6s\n",
+ " ", "mcs0", "mcs1", "mcs2", "mcs3", "mcs4", "mcs5",
+ "mcs6", "mcs7", "mcs8", "mcs9", "mcs10", "mcs11");
+ mt7925_print_txpwr_entry(VHT20, vht20, band_idx);
+ mt7925_print_txpwr_entry(VHT40, vht40, band_idx);
+
+ mt7925_print_txpwr_entry(VHT80, vht80, band_idx);
+ mt7925_print_txpwr_entry(VHT160, vht160, band_idx);
+
+ mt7925_print_txpwr_entry(HE26, he26, band_idx);
+ mt7925_print_txpwr_entry(HE52, he52, band_idx);
+ mt7925_print_txpwr_entry(HE106, he106, band_idx);
+ mt7925_print_txpwr_entry(HE242, he242, band_idx);
+ mt7925_print_txpwr_entry(HE484, he484, band_idx);
+
+ mt7925_print_txpwr_entry(HE996, he996, band_idx);
+ mt7925_print_txpwr_entry(HE996x2, he996x2, band_idx);
+
+ mt7928_eht_txpwr(s, txpwr, band_idx);
+
+out:
+ devm_kfree(dev->mt76.dev, txpwr);
+ return ret;
+}
+
+static int
+mt7928_txpwr_tbl_set(void *data, u64 val)
+{
+ struct mt792x_dev *dev = data;
+
+ dev->txpwr_tbl = (u8)val;
+
+ return 0;
+}
+
+static int
+mt7928_txpwr_tbl_get(void *data, u64 *val)
+{
+ struct mt792x_dev *dev = data;
+
+ *val = dev->txpwr_tbl;
+
+ return 0;
+}
+
+DEFINE_DEBUGFS_ATTRIBUTE(fops_txpwr_tbl, mt7928_txpwr_tbl_get,
+ mt7928_txpwr_tbl_set, "%lld\n");
+
static int
mt7925_pm_set(void *data, u64 val)
{
@@ -303,8 +444,14 @@ int mt7925_init_debugfs(struct mt792x_dev *dev)
debugfs_create_devm_seqfile(dev->mt76.dev, "acq", dir,
mt792x_queues_acq);
- debugfs_create_devm_seqfile(dev->mt76.dev, "txpower_sku", dir,
- mt7925_txpwr);
+ if (is_mt7928(&dev->mt76)) {
+ debugfs_create_devm_seqfile(dev->mt76.dev, "txpower_sku", dir,
+ mt7928_txpwr);
+ debugfs_create_file("txpower_tbl", 0600, dir, dev, &fops_txpwr_tbl);
+ } else {
+ debugfs_create_devm_seqfile(dev->mt76.dev, "txpower_sku", dir,
+ mt7925_txpwr);
+ }
debugfs_create_file("tx_stats", 0400, dir, dev, &mt792x_tx_stats_fops);
debugfs_create_file("fw_debug", 0600, dir, dev, &fops_fw_debug);
debugfs_create_file("runtime-pm", 0600, dir, dev, &fops_pm);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
index 37502d8a0e7d..7162eca8e23e 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
@@ -2211,6 +2211,33 @@ int mt7925_get_txpwr_info(struct mt792x_dev *dev, u8 band_idx, struct mt7925_txp
return 0;
}
+int mt7928_get_txpwr_info(struct mt792x_dev *dev, u8 band_idx, u8 power_tbl,
+ struct mt7928_txpwr *txpwr)
+{
+ struct mt7928_txpwr_event *event;
+ struct mt7925_txpwr_req req = {
+ .tag = cpu_to_le16(TX_POWER_SHOW_INFO),
+ .len = cpu_to_le16(sizeof(req) - 4),
+ .catg = TXPOWER_ALL_RATE_POWER_INFO,
+ .band_idx = band_idx,
+ .power_tbl = power_tbl,
+ };
+ struct sk_buff *skb;
+ int ret;
+
+ ret = mt76_mcu_send_and_get_msg(&dev->mt76, MCU_UNI_CMD(TXPOWER),
+ &req, sizeof(req), true, &skb);
+ if (ret)
+ return ret;
+
+ event = (struct mt7928_txpwr_event *)skb->data;
+ memcpy(txpwr, &event->txpwr, sizeof(*txpwr));
+
+ dev_kfree_skb(skb);
+
+ return 0;
+}
+
int mt7925_mcu_set_sniffer(struct mt792x_dev *dev, struct ieee80211_vif *vif,
bool enable)
{
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h
index 1613c4765186..754db84200d8 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h
@@ -69,7 +69,7 @@ struct mt7925_txpwr_req {
u8 format_id;
u8 catg;
u8 band_idx;
- u8 _rsv1;
+ u8 power_tbl;
} __packed;
struct mt7925_txpwr_event {
@@ -90,6 +90,24 @@ struct mt7925_txpwr_event {
u8 rsv1;
} __packed;
+struct mt7928_txpwr_event {
+ u8 rsv[4];
+ __le16 tag;
+ __le16 len;
+
+ u8 catg;
+ u8 band_idx;
+ u8 ch_band;
+ u8 format; /* 0:Legacy, 1:HE */
+
+ /* Rate power info */
+ struct mt7928_txpwr txpwr;
+
+ s8 pwr_max;
+ s8 pwr_min;
+ u8 rsv1;
+} __packed;
+
enum {
TM_SWITCH_MODE,
TM_SET_AT_CMD,
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h b/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h
index e3fef41cbb33..b06f17b2fd8c 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h
@@ -260,6 +260,43 @@ struct mt7925_txpwr {
s8 eht996x3_484[16][2];
};
+struct mt7928_txpwr {
+ s8 cck[4][2];
+ s8 ofdm20[8][2];
+ s8 ofdm40[8][2];
+ s8 ofdm80[8][2];
+ s8 ofdm160[8][2];
+ s8 ht20[8][2];
+ s8 ht40[9][2];
+ s8 vht20[12][2];
+ s8 vht40[12][2];
+ s8 vht80[12][2];
+ s8 vht160[12][2];
+ s8 he26[12][2];
+ s8 he52[12][2];
+ s8 he106[12][2];
+ s8 he242[12][2];
+ s8 he484[12][2];
+ s8 he996[12][2];
+ s8 he996x2[12][2];
+ s8 eht26[16][2];
+ s8 eht52[16][2];
+ s8 eht106[16][2];
+ s8 eht242[16][2];
+ s8 eht484[16][2];
+ s8 eht996[16][2];
+ s8 eht996x2[16][2];
+ s8 eht996x4[16][2];
+ s8 eht26_52[16][2];
+ s8 eht26_106[16][2];
+ s8 eht484_242[16][2];
+ s8 eht996_484[16][2];
+ s8 eht996_484_242[16][2];
+ s8 eht996x2_484[16][2];
+ s8 eht996x3[16][2];
+ s8 eht996x3_484[16][2];
+};
+
static inline u8 mt7927_band_idx(enum nl80211_band band)
{
switch (band) {
@@ -339,6 +376,8 @@ int mt7925_mcu_uni_bss_ps(struct mt792x_dev *dev,
void mt7925_coredump_work(struct work_struct *work);
int mt7925_get_txpwr_info(struct mt792x_dev *dev, u8 band_idx,
struct mt7925_txpwr *txpwr);
+int mt7928_get_txpwr_info(struct mt792x_dev *dev, u8 band_idx, u8 power_tbl,
+ struct mt7928_txpwr *txpwr);
void mt7925_mac_set_fixed_rate_table(struct mt792x_dev *dev,
u8 tbl_idx, u16 rate_idx);
void mt7925_mac_write_txwi(struct mt76_dev *dev, __le32 *txwi,
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h
index 5a1ba1cfc5a3..fbe1793e5598 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x.h
+++ b/drivers/net/wireless/mediatek/mt76/mt792x.h
@@ -281,6 +281,7 @@ struct mt792x_dev {
u8 fw_debug;
u8 fw_features;
+ u8 txpwr_tbl;
struct mt76_connac_pm pm;
struct mt76_connac_coredump coredump;
--
2.18.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