* [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
* [PATCH] wifi: mt76: connac3: use __le32 for mt7925_regd_rule fields
From: JB Tsai @ 2026-07-24 8:51 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>
The mt7925_regd_rule structure is parsed directly from the firmware
regulatory event, whose fields are little-endian. Declaring start_freq,
end_freq, max_bw, eirp and flags as u32 misrepresents this wire format
and triggers sparse endianness warnings on big-endian aware builds.
Change these fields to __le32 to reflect the firmware byte order.
Fixes: 4ce639ea518f ("wifi: mt76: mt7925: add regulatory wiphy self manager support")
Signed-off-by: Charlie-cy Wu <Charlie-cy.Wu@mediatek.com>
---
drivers/net/wireless/mediatek/mt76/mt7925/regd.h | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/regd.h b/drivers/net/wireless/mediatek/mt76/mt7925/regd.h
index 2feacf42dc22..373992558974 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/regd.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/regd.h
@@ -13,11 +13,11 @@ struct mt7925_regd_rule_header {
};
struct mt7925_regd_rule {
- u32 start_freq;
- u32 end_freq;
- u32 max_bw;
- u32 eirp;
- u32 flags;
+ __le32 start_freq;
+ __le32 end_freq;
+ __le32 max_bw;
+ __le32 eirp;
+ __le32 flags;
u8 rsv[12];
};
--
2.18.0
^ permalink raw reply related
* [PATCH] wifi: mt76: connac3: refactor CLC header type validation logic
From: JB Tsai @ 2026-07-24 8:51 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>
Separate the hardware encapsulation type validation into two distinct
checks based on CLC index type. For BE_CTRL and REGD indices, validate
against the t2 header type, while other indices should validate against
the t0 header type. This ensures proper CLC filtering for different
calibration data structures.
Fixes: 4ce639ea518f ("wifi: mt76: mt7925: add regulatory wiphy self manager support")
Signed-off-by: Charlie-cy Wu <Charlie-cy.Wu@mediatek.com>
---
drivers/net/wireless/mediatek/mt76/mt7925/mcu.c | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
index a12b939b6123..37502d8a0e7d 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
@@ -899,8 +899,11 @@ static int mt7925_load_clc(struct mt792x_dev *dev, const char *fw_name)
continue;
/* header content sanity */
- if ((clc->idx == MT792x_CLC_BE_CTRL &&
- u8_get_bits(clc->t2.type, MT_EE_HW_TYPE_ENCAP) != hw_encap) ||
+ if ((clc->idx == MT792x_CLC_BE_CTRL || clc->idx == MT792x_CLC_REGD) &&
+ u8_get_bits(clc->t2.type, MT_EE_HW_TYPE_ENCAP) != hw_encap)
+ continue;
+
+ if (clc->idx != MT792x_CLC_BE_CTRL && clc->idx != MT792x_CLC_REGD &&
u8_get_bits(clc->t0.type, MT_EE_HW_TYPE_ENCAP) != hw_encap)
continue;
--
2.18.0
^ permalink raw reply related
* [PATCH] wifi: mt76: connac3: implement 6GHz power type detection and dynamic CLC filtering
From: JB Tsai @ 2026-07-24 8:51 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>
This patch enhances 6GHz regulatory handling by implementing proper
power type detection and selective CLC (Country Location Configuration)
table filtering.
Changes in main.c:
1. Add mt7925_regd_set_6ghz_power_type() with multi-source channel
detection fallback mechanism:
- Primary: link_conf->chanreq.oper.chan (per-link BSS config)
- Secondary: vif->bss_conf.chanreq.oper.chan (default link)
- Tertiary: phy->mt76->chandef.chan (PHY layer)
2. Move mt7925_calc_vif_num() and mt7925_regd_set_6ghz_power_type()
before mt7925_mac_link_sta_add() to avoid implicit declaration
3. Call mt7925_regd_set_6ghz_power_type() in station add/remove paths
Changes in mcu.c:
1. Add power_type_change parameter to __mt7925_mcu_set_clc() and
related functions to distinguish between regulatory updates and
power type changes
2. Implement CLC table filtering logic:
- When power_type_change=false (from mt7925_mcu_regd_update):
Send 2/5G ('-'), 6G LPI ('0'), and 6G VLP ('2') tables
- When power_type_change=true (from mt7925_regd_set_6ghz_power_type):
Send only the specific 6G power table matching current power_type
3. Map power_type to CLC type codes:
MT_AP_LPI -> '0', MT_AP_SP -> '1', MT_AP_VLP -> '2'
4. Add env_6g field to CLC request structure to pass power type to FW
This fixes the issue where vif->bss_conf.chanreq.oper.chan was NULL
during early connection stages, and enables proper 6GHz power type
switching based on regulatory requirements.
Signed-off-by: Charlie-cy Wu <Charlie-cy.Wu@mediatek.com>
---
.../net/wireless/mediatek/mt76/mt7925/main.c | 84 +++++++++++++++++++
.../net/wireless/mediatek/mt76/mt7925/mcu.c | 46 ++++++++--
.../wireless/mediatek/mt76/mt7925/mt7925.h | 4 +-
.../net/wireless/mediatek/mt76/mt7925/regd.c | 10 +--
4 files changed, 133 insertions(+), 11 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
index a9059866b701..5f6ad84da70a 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
@@ -884,6 +884,86 @@ mt7925_get_rates_table(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
return mvif->basic_rates_idx;
}
+static void
+mt7925_calc_vif_num(void *priv, u8 *mac, struct ieee80211_vif *vif)
+{
+ u32 *num = priv;
+
+ if (!priv)
+ return;
+
+ switch (vif->type) {
+ case NL80211_IFTYPE_STATION:
+ case NL80211_IFTYPE_P2P_CLIENT:
+ case NL80211_IFTYPE_AP:
+ case NL80211_IFTYPE_P2P_GO:
+ *num += 1;
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+mt7925_regd_set_6ghz_power_type(struct ieee80211_vif *vif,
+ struct ieee80211_bss_conf *link_conf,
+ bool is_add)
+{
+ struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv;
+ struct mt792x_phy *phy = mvif->phy;
+ struct mt792x_dev *dev = phy->dev;
+ struct ieee80211_channel *chan = NULL;
+ u32 valid_vif_num = 0;
+ enum mt792x_reg_power_type old_power_type;
+ bool power_type_changed = false;
+
+ old_power_type = phy->power_type;
+
+ /* Try to get channel from link_conf first, then vif->bss_conf, then phy */
+ if (link_conf && link_conf->chanreq.oper.chan)
+ chan = link_conf->chanreq.oper.chan;
+ else if (vif->bss_conf.chanreq.oper.chan)
+ chan = vif->bss_conf.chanreq.oper.chan;
+ else if (phy->mt76->chandef.chan)
+ chan = phy->mt76->chandef.chan;
+
+ ieee80211_iterate_active_interfaces(mt76_hw(dev),
+ IEEE80211_IFACE_ITER_RESUME_ALL,
+ mt7925_calc_vif_num, &valid_vif_num);
+
+ if (valid_vif_num > 1) {
+ phy->power_type = MT_AP_DEFAULT;
+ goto out;
+ }
+
+ if (!is_add)
+ vif->bss_conf.power_type = IEEE80211_REG_UNSET_AP;
+
+ switch (vif->bss_conf.power_type) {
+ case IEEE80211_REG_SP_AP:
+ phy->power_type = MT_AP_SP;
+ break;
+ case IEEE80211_REG_VLP_AP:
+ phy->power_type = MT_AP_VLP;
+ break;
+ case IEEE80211_REG_LPI_AP:
+ phy->power_type = MT_AP_LPI;
+ break;
+ case IEEE80211_REG_UNSET_AP:
+ phy->power_type = MT_AP_UNSET;
+ break;
+ default:
+ phy->power_type = MT_AP_DEFAULT;
+ break;
+ }
+
+out:
+ power_type_changed = (old_power_type != phy->power_type);
+
+ if (power_type_changed && chan && chan->band == NL80211_BAND_6GHZ)
+ mt7925_mcu_apply_regd(dev, dev->mt76.alpha2, dev->country_ie_env, true);
+}
+
static int mt7925_mac_link_sta_add(struct mt76_dev *mdev,
struct ieee80211_vif *vif,
struct ieee80211_link_sta *link_sta,
@@ -1006,6 +1086,8 @@ static int mt7925_mac_link_sta_add(struct mt76_dev *mdev,
goto out_pm;
}
+ mt7925_regd_set_6ghz_power_type(vif, link_conf, true);
+
mt76_connac_power_save_sched(&dev->mphy, &dev->pm);
return 0;
@@ -1279,6 +1361,8 @@ static void mt7925_mac_link_sta_remove(struct mt76_dev *mdev,
mt76_wcid_cleanup(mdev, wcid);
mt76_wcid_mask_clear(mdev->wcid_mask, idx);
+ mt7925_regd_set_6ghz_power_type(vif, link_conf, false);
+
mt76_connac_power_save_sched(&dev->mphy, &dev->pm);
}
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
index 9678ab50dbe6..a12b939b6123 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
@@ -3496,7 +3496,7 @@ EXPORT_SYMBOL_GPL(mt7925_mcu_set_channel_domain);
static int
__mt7925_mcu_set_clc(struct mt792x_dev *dev, u8 *alpha2,
enum environment_cap env_cap,
- struct mt7925_clc *clc, u8 idx)
+ struct mt7925_clc *clc, u8 idx, bool power_type_change)
{
struct mt7925_clc_segment *seg;
struct sk_buff *skb;
@@ -3514,19 +3514,38 @@ __mt7925_mcu_set_clc(struct mt792x_dev *dev, u8 *alpha2,
u8 pad1;
u8 alpha2[2];
u8 type[2];
- u8 rsvd[64];
+ u8 env_6g;
+ u8 rsvd[63];
} __packed req = {
.tag = cpu_to_le16(0x3),
.idx = idx,
.env = env_cap,
+ .env_6g = dev->phy.power_type,
};
int ret, valid_cnt = 0;
u8 *pos, *last_pos;
+ u8 target_6g_type;
if (!clc)
return 0;
+ /* Determine target 6G power type based on phy->power_type */
+ switch (dev->phy.power_type) {
+ case MT_AP_LPI:
+ target_6g_type = '0';
+ break;
+ case MT_AP_SP:
+ target_6g_type = '1';
+ break;
+ case MT_AP_VLP:
+ target_6g_type = '2';
+ break;
+ default:
+ target_6g_type = '0'; /* default to LPI */
+ break;
+ }
+
req.ver = clc->ver;
pos = clc->data + sizeof(*seg) * clc->t0.nr_seg;
last_pos = clc->data + le32_to_cpu(*(__le32 *)(clc->data + 4));
@@ -3538,6 +3557,23 @@ __mt7925_mcu_set_clc(struct mt792x_dev *dev, u8 *alpha2,
rule->alpha2[1] != alpha2[1])
continue;
+ /* Filter power tables based on call source:
+ * power_type_change == false: from mt7925_mcu_regd_update()
+ * - Send 2/5G ('-') and 6G LPI ('0') and 6G VLP ('2')
+ * power_type_change == true: from mt7925_regd_set_6ghz_power_type()
+ * - Send only the specific 6G power table based on power_type
+ */
+ if (power_type_change) {
+ /* Only send the specific 6G power table */
+ if (rule->type[0] != target_6g_type)
+ continue;
+ } else {
+ /* Send 2/5G and 6G LPI and VLP only */
+ if (rule->type[0] != '-' && rule->type[0] != '0' &&
+ rule->type[0] != '2')
+ continue;
+ }
+
seg = (struct mt7925_clc_segment *)clc->data
+ rule->seg_idx - 1;
@@ -3571,7 +3607,7 @@ __mt7925_mcu_set_clc(struct mt792x_dev *dev, u8 *alpha2,
}
int mt7925_mcu_set_clc(struct mt792x_dev *dev, u8 *alpha2,
- enum environment_cap env_cap)
+ enum environment_cap env_cap, bool power_type_change)
{
struct mt792x_phy *phy = (struct mt792x_phy *)&dev->phy;
int i, ret;
@@ -3586,13 +3622,13 @@ int mt7925_mcu_set_clc(struct mt792x_dev *dev, u8 *alpha2,
continue;
ret = __mt7925_mcu_set_clc(dev, alpha2, env_cap,
- phy->clc[i], i);
+ phy->clc[i], i, power_type_change);
/* If no country found, set "00" as default */
if (ret == -ENOENT)
ret = __mt7925_mcu_set_clc(dev, "00",
ENVIRON_INDOOR,
- phy->clc[i], i);
+ phy->clc[i], i, power_type_change);
if (ret < 0)
return ret;
}
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h b/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h
index 321e732347f2..e3fef41cbb33 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h
@@ -385,7 +385,9 @@ int mt7925_set_tx_sar_pwr(struct ieee80211_hw *hw,
int mt7925_mcu_regval(struct mt792x_dev *dev, u32 regidx, u32 *val, bool set);
int mt7925_mcu_set_clc(struct mt792x_dev *dev, u8 *alpha2,
- enum environment_cap env_cap);
+ enum environment_cap env_cap, bool power_type_change);
+int mt7925_mcu_apply_regd(struct mt792x_dev *dev, u8 *alpha2,
+ enum environment_cap env, bool power_type_change);
int mt7925_mcu_set_mlo_roc(struct mt792x_phy *phy, struct mt792x_bss_conf *mconf,
u16 sel_links, int duration, u8 token_id);
int mt7925_mcu_set_roc(struct mt792x_phy *phy, struct mt792x_bss_conf *mconf,
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/regd.c b/drivers/net/wireless/mediatek/mt76/mt7925/regd.c
index 3eb3f172dba4..a039ec3ee47b 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/regd.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/regd.c
@@ -137,14 +137,14 @@ mt7925_regd_channel_update(struct wiphy *wiphy, struct mt792x_dev *dev)
}
}
-static int mt7925_mcu_apply_regd(struct mt792x_dev *dev, u8 *alpha2,
- enum environment_cap env)
+int mt7925_mcu_apply_regd(struct mt792x_dev *dev, u8 *alpha2,
+ enum environment_cap env, bool power_type_change)
{
struct ieee80211_hw *hw = mt76_hw(dev);
struct wiphy *wiphy = hw->wiphy;
int ret;
- ret = mt7925_mcu_set_clc(dev, alpha2, env);
+ ret = mt7925_mcu_set_clc(dev, alpha2, env, power_type_change);
if (ret < 0)
return ret;
@@ -167,7 +167,7 @@ int mt7925_mcu_regd_update(struct mt792x_dev *dev, u8 *alpha2,
mt792x_mutex_acquire(dev);
if (dev->regd_change)
- ret = mt7925_mcu_apply_regd(dev, alpha2, country_ie_env);
+ ret = mt7925_mcu_apply_regd(dev, alpha2, country_ie_env, false);
mt792x_mutex_release(dev);
dev->regd_change = false;
dev->regd_in_progress = false;
@@ -407,7 +407,7 @@ int mt7925_regd_change(struct mt792x_phy *phy, char *alpha2)
} else if (phy->chip_cap & MT792x_CHIP_CAP_11D_EN) {
return regulatory_hint(wiphy, alpha2);
} else {
- return mt7925_mcu_set_clc(dev, alpha2, ENVIRON_INDOOR);
+ return mt7925_mcu_set_clc(dev, alpha2, ENVIRON_INDOOR, false);
}
}
EXPORT_SYMBOL_GPL(mt7925_regd_change);
--
2.18.0
^ permalink raw reply related
* [PATCH] wifi: mt76: connac3: add fixedrate and autorate debugfs for rate control
From: JB Tsai @ 2026-07-24 8:50 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 two debugfs interfaces to manually control the per-STA rate
adaptation via the UNI_CMD_RA firmware command:
- fixedrate: lock a STA to a fixed TX rate (SET_FIXED_RATE tag). The
value is a packed hex field describing the full PHY rate:
0x[WCID(2)][Mode][BW][MCS][Nss][SGI][Preamble]
[STBC][LDPC][SPE_EN][HeLtf]
- autorate: re-enable/disable firmware rate adaptation for a STA
(SET_AUTO_RATE tag):
0x[WCID(2)][En][Mode]
Key changes:
- Add mt7925_mcu_set_fixed_rate() sending the SET_FIXED_RATE(0x0F) TLV
- Add mt7925_mcu_set_auto_rate() sending the SET_AUTO_RATE(0x11) TLV
- Add RA command TLV structures (mt7925_ra_hdr,
mt7925_ra_fixed_rate_tlv/_v1, mt7925_ra_auto_rate_tlv) and the
UNI_CMD_RA tag enum
- Register fixedrate/autorate debugfs files and store the last value
in mt792x_dev for read back
- Translate the LDPC input to the FW's "on" value (7)
Note: EHT modes require HeLtf=1, otherwise the firmware drops all TX.
Usage:
echo 0x1D372000101 > /sys/kernel/debug/ieee80211/phyX/mt76/fixedrate
echo 0x110 > /sys/kernel/debug/ieee80211/phyX/mt76/autorate
Signed-off-by: Charlie-cy Wu <Charlie-cy.Wu@mediatek.com>
---
.../wireless/mediatek/mt76/mt7925/debugfs.c | 97 +++++++++++++++++++
.../net/wireless/mediatek/mt76/mt7925/mcu.c | 40 ++++++++
.../net/wireless/mediatek/mt76/mt7925/mcu.h | 42 ++++++++
drivers/net/wireless/mediatek/mt76/mt792x.h | 3 +
4 files changed, 182 insertions(+)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c b/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c
index 3db564d63c15..4a043855a049 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c
@@ -427,6 +427,101 @@ static int mt7925_chip_reset(void *data, u64 val)
DEFINE_DEBUGFS_ATTRIBUTE(fops_reset, NULL, mt7925_chip_reset, "%lld\n");
+/* fixedrate input format (12 hex digits):
+ * 0x[WCID(2)][Mode][BW][MCS][Nss][SGI][Preamble][STBC][LDPC][SPE_EN][HeLtf]
+ * The RA tag is fixed to SET_FIXED_RATE internally.
+ * WCID is 2 nibbles, all others are 1 nibble.
+ *
+ * [WCID] Wireless Client ID
+ * [Mode] CCK=0, OFDM=1, HT=2, GF=3, VHT=4, PLR=5, HE_SU=8, HE_ER_SU=9,
+ * HE_TRIG=10, HE_MU=11, EHT_ER=13, EHT_TB=14, EHT_SU and EHT_MU=15
+ * [BW] BW20=0, BW40=1, BW80=2, BW160=3, BW320=4
+ * [MCS] CCK=0~3, OFDM=0~7, HT=0~32, VHT=0~9, HE=0~11, EHT=0~13, EHT_ER=14~15
+ * [Nss] 1~8
+ * [GI] HT/VHT: 0=Long, 1=Short; HE/EHT: 0=0.8us, 1=1.6us, 2=3.2us
+ * [Preamble] Long=0, Short=other
+ * [STBC] Enable=1, Disable=0
+ * [LDPC] BCC=0, LDPC=1 (driver sends the FW's "on" value 7)
+ * [SPE_EN] spatial extension index
+ * [HeLtf] 1X=0, 2X=1, 4X=2 (NOTE: EHT modes require HeLtf=1)
+ */
+static int
+mt7925_fixedrate_set(void *data, u64 val)
+{
+ struct mt792x_dev *dev = data;
+ struct mt7925_ra_fixed_rate_v1 rate = {};
+
+ dev->fixed_rate = val;
+
+ rate.wlan_idx = cpu_to_le16((val >> 40) & 0xff);
+ rate.phy_mode = (val >> 36) & 0xf;
+ rate.bw = (val >> 32) & 0xf;
+ rate.mcs = (val >> 28) & 0xf;
+ rate.nss = (val >> 24) & 0xf;
+ rate.short_gi = cpu_to_le16((val >> 20) & 0xf);
+ rate.short_preamble = (val >> 16) & 0xf;
+ rate.stbc = (val >> 12) & 0xf;
+ rate.ecc = ((val >> 8) & 0xf) ? 7 : 0;
+ rate.spe = (val >> 4) & 0xf;
+ rate.he_ltf = cpu_to_le16((val >> 0) & 0xf);
+
+ mt792x_mutex_acquire(dev);
+ mt7925_mcu_set_fixed_rate(dev, &rate);
+ mt792x_mutex_release(dev);
+
+ return 0;
+}
+
+static int
+mt7925_fixedrate_get(void *data, u64 *val)
+{
+ struct mt792x_dev *dev = data;
+
+ *val = dev->fixed_rate;
+
+ return 0;
+}
+DEFINE_DEBUGFS_ATTRIBUTE(fops_fixedrate, mt7925_fixedrate_get,
+ mt7925_fixedrate_set, "0x%llx\n");
+
+/* autorate input format (6 hex digits):
+ * 0x[Tag(2)][WCID(2)][En][Mode]
+ * The RA tag is fixed to SET_AUTO_RATE internally.
+ * WCID is 2 nibbles, En and Mode are 1 nibble each.
+ *
+ * [WCID] Wireless Client ID
+ * [En] Auto rate: Enable=1, Disable=0
+ * [Mode] don't care (RA picks the rate)
+ */
+static int
+mt7925_autorate_set(void *data, u64 val)
+{
+ struct mt792x_dev *dev = data;
+ u16 wlan_idx = (val >> 8) & 0xff;
+ bool enable = (val >> 4) & 0xf;
+ u8 mode = val & 0xf;
+
+ dev->auto_rate = val;
+
+ mt792x_mutex_acquire(dev);
+ mt7925_mcu_set_auto_rate(dev, wlan_idx, enable, mode);
+ mt792x_mutex_release(dev);
+
+ return 0;
+}
+
+static int
+mt7925_autorate_get(void *data, u64 *val)
+{
+ struct mt792x_dev *dev = data;
+
+ *val = dev->auto_rate;
+
+ return 0;
+}
+DEFINE_DEBUGFS_ATTRIBUTE(fops_autorate, mt7925_autorate_get,
+ mt7925_autorate_set, "0x%llx\n");
+
int mt7925_init_debugfs(struct mt792x_dev *dev)
{
struct dentry *dir;
@@ -461,6 +556,8 @@ int mt7925_init_debugfs(struct mt792x_dev *dev)
debugfs_create_devm_seqfile(dev->mt76.dev, "runtime_pm_stats", dir,
mt792x_pm_stats);
debugfs_create_file("deep-sleep", 0600, dir, dev, &fops_ds);
+ debugfs_create_file("fixedrate", 0600, dir, dev, &fops_fixedrate);
+ debugfs_create_file("autorate", 0600, dir, dev, &fops_autorate);
return 0;
}
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
index 7162eca8e23e..b8062eb91ba5 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
@@ -4113,3 +4113,43 @@ int mt7925_mcu_set_rssimonitor(struct mt792x_dev *dev, struct ieee80211_vif *vif
return mt76_mcu_send_msg(&dev->mt76, MCU_UNI_CMD(RSSI_MONITOR), &req,
sizeof(req), false);
}
+
+int mt7925_mcu_set_fixed_rate(struct mt792x_dev *dev,
+ struct mt7925_ra_fixed_rate_v1 *rate)
+{
+ struct {
+ struct mt7925_ra_hdr hdr;
+ struct mt7925_ra_fixed_rate_tlv tlv;
+ struct mt7925_ra_fixed_rate_v1 v1;
+ } __packed req = {
+ .tlv = {
+ .tag = cpu_to_le16(UNI_CMD_RA_TAG_SET_FIXED_RATE),
+ .len = cpu_to_le16(sizeof(req.tlv) + sizeof(req.v1)),
+ .version = cpu_to_le16(0),
+ },
+ .v1 = *rate,
+ };
+
+ return mt76_mcu_send_msg(&dev->mt76, MCU_UNI_CMD(RA), &req,
+ sizeof(req), true);
+}
+
+int mt7925_mcu_set_auto_rate(struct mt792x_dev *dev, u16 wlan_idx,
+ bool enable, u8 mode)
+{
+ struct {
+ struct mt7925_ra_hdr hdr;
+ struct mt7925_ra_auto_rate_tlv tlv;
+ } __packed req = {
+ .tlv = {
+ .tag = cpu_to_le16(UNI_CMD_RA_TAG_SET_AUTO_RATE),
+ .len = cpu_to_le16(sizeof(req.tlv)),
+ .wlan_idx = cpu_to_le16(wlan_idx),
+ .auto_rate_en = enable,
+ .mode = mode,
+ },
+ };
+
+ return mt76_mcu_send_msg(&dev->mt76, MCU_UNI_CMD(RA), &req,
+ sizeof(req), true);
+}
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h
index 754db84200d8..855613f0b29d 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h
@@ -61,6 +61,44 @@ struct mt7925_mcu_ant_id_config {
u8 ant_id[4];
} __packed;
+enum {
+ UNI_CMD_RA_TAG_SET_FIXED_RATE = 0x0F,
+ UNI_CMD_RA_TAG_SET_AUTO_RATE = 0x11,
+};
+
+struct mt7925_ra_hdr {
+ u8 _rsv[4];
+} __packed;
+
+struct mt7925_ra_fixed_rate_tlv {
+ __le16 tag;
+ __le16 len;
+ __le16 version;
+} __packed;
+
+struct mt7925_ra_fixed_rate_v1 {
+ __le16 wlan_idx;
+ u8 phy_mode;
+ u8 stbc;
+ __le16 short_gi;
+ u8 bw;
+ u8 ecc;
+ u8 mcs;
+ u8 nss;
+ __le16 he_ltf;
+ u8 spe;
+ u8 short_preamble;
+ __le16 _rsv;
+} __packed;
+
+struct mt7925_ra_auto_rate_tlv {
+ __le16 tag;
+ __le16 len;
+ __le16 wlan_idx;
+ u8 auto_rate_en;
+ u8 mode;
+} __packed;
+
struct mt7925_txpwr_req {
u8 _rsv[4];
__le16 tag;
@@ -758,4 +796,8 @@ int mt7925_mcu_update_arp_filter(struct mt76_dev *dev,
int
mt7925_mcu_uni_bss_bcnft(struct mt792x_dev *dev,
struct ieee80211_bss_conf *link_conf, bool enable);
+int mt7925_mcu_set_fixed_rate(struct mt792x_dev *dev,
+ struct mt7925_ra_fixed_rate_v1 *rate);
+int mt7925_mcu_set_auto_rate(struct mt792x_dev *dev, u16 wlan_idx,
+ bool enable, u8 mode);
#endif
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h
index fbe1793e5598..eecd0b1a5d0e 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x.h
+++ b/drivers/net/wireless/mediatek/mt76/mt792x.h
@@ -283,6 +283,9 @@ struct mt792x_dev {
u8 fw_features;
u8 txpwr_tbl;
+ u64 fixed_rate;
+ u64 auto_rate;
+
struct mt76_connac_pm pm;
struct mt76_connac_coredump coredump;
const struct mt792x_hif_ops *hif_ops;
--
2.18.0
^ permalink raw reply related
* [PATCH] wifi: mt76: mt792x: skip MTCL country list lookup for default BE mode
From: JB Tsai @ 2026-07-24 8:48 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>
In the v3 MTCL country list parsing, a mode_be value of 0x02 indicates
that 11BE enablement should follow the driver default instead of being
gated by the per-country BE bitmap.
Handle this case explicitly: when cl->mode_be == 0x02, skip the BE/EU
country list matching and return the driver default configuration
directly, avoiding an unnecessary lookup.
Fixes: 7cebc2300de2 ("wifi: mt76: mt792x: extend MTCL of APCI to version3 for EHT control")
Signed-off-by: Charlie-cy Wu <Charlie-cy.Wu@mediatek.com>
---
drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c | 11 +++++++----
drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.h | 7 +++++++
2 files changed, 14 insertions(+), 4 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c b/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c
index 946dd7956e4a..868feadbff61 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c
+++ b/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c
@@ -388,7 +388,7 @@ mt792x_acpi_get_mtcl_map_v3(int row, int column, struct mt792x_asar_cl_v3 *cl)
u32 config = 0;
u8 mode_be = 0;
- mode_be = (cl->mode_be > 0x02) ? 0 : cl->mode_be;
+ mode_be = (cl->mode_be > MT792X_ACPI_MTCL_MODE_DEFAULT) ? 0 : cl->mode_be;
if (cl->version > 2 && cl->clbe[row] & BIT(column))
config |= (mode_be & 0x3) << 4;
@@ -402,10 +402,10 @@ mt792x_acpi_get_mtcl_map(int row, int column, struct mt792x_asar_cl *cl)
u32 config = 0;
u8 mode_6g, mode_5g9;
- mode_6g = (cl->mode_6g > 0x02) ? 0 : cl->mode_6g;
- mode_5g9 = (cl->mode_5g9 > 0x01) ? 0 : cl->mode_5g9;
+ mode_6g = (cl->mode_6g > MT792X_ACPI_MTCL_MODE_DEFAULT) ? 0 : cl->mode_6g;
+ mode_5g9 = (cl->mode_5g9 > MT792X_ACPI_MTCL_MODE_ENABLE) ? 0 : cl->mode_5g9;
- if ((cl->cl6g[row] & BIT(column)) || cl->mode_6g == 0x02)
+ if ((cl->cl6g[row] & BIT(column)) || cl->mode_6g == MT792X_ACPI_MTCL_MODE_DEFAULT)
config |= (mode_6g & 0x3) << 2;
if (cl->version > 1 && cl->cl5g9[row] & BIT(column))
config |= (mode_5g9 & 0x3);
@@ -426,6 +426,9 @@ mt792x_acpi_parse_mtcl_tbl_v3(struct mt792x_phy *phy, char *alpha2)
if (!cl)
return MT792X_ACPI_MTCL_INVALID;
+ if (cl->mode_be == MT792X_ACPI_MTCL_MODE_DEFAULT)
+ goto out;
+
for (i = 0; i < ARRAY_SIZE(cc_list_be); i++) {
col = 7 - i % 8;
row = i / 8;
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.h b/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.h
index 474033073831..110f68438d90 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.h
+++ b/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.h
@@ -17,6 +17,13 @@
#define MT792X_ACPI_MTCL_INVALID 0xffffffff
+/* MTCL per-band control mode (mode_6g/mode_5g9/mode_be) */
+enum {
+ MT792X_ACPI_MTCL_MODE_DISABLE = 0x00, /* force disable */
+ MT792X_ACPI_MTCL_MODE_ENABLE = 0x01, /* enable if driver & BIOS support */
+ MT792X_ACPI_MTCL_MODE_DEFAULT = 0x02, /* supporting depends on driver setting */
+};
+
struct mt792x_asar_dyn_limit {
u8 idx;
u8 frp[5];
--
2.18.0
^ permalink raw reply related
* [PATCH] wifi: mt76: connac2: add CLC regulatory domain support
From: JB Tsai @ 2026-07-24 8:47 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 MT7921_CLC_REGD index definition and corresponding hardware
encapsulation type check in CLC loading flow to support regulatory
domain configuration via CLC (Country/Local Configuration) data.
Fixes: 058f274fb55f ("wifi: mt76: mt7921: add regulatory wiphy self manager support")
Signed-off-by: Charlie-cy Wu <Charlie-cy.Wu@mediatek.com>
---
drivers/net/wireless/mediatek/mt76/mt7921/mcu.c | 4 ++++
drivers/net/wireless/mediatek/mt76/mt7921/mt7921.h | 1 +
2 files changed, 5 insertions(+)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c
index 2e0769d18f87..c6af7f00ca59 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c
@@ -476,6 +476,10 @@ static int mt7921_load_clc(struct mt792x_dev *dev, const char *fw_name)
u8_get_bits(clc->type, MT_EE_HW_TYPE_ENCAP) != hw_encap)
continue;
+ if (clc->idx == MT7921_CLC_REGD &&
+ u8_get_bits(clc->type, MT_EE_HW_TYPE_ENCAP) != hw_encap)
+ continue;
+
phy->clc[clc->idx] = devm_kmemdup(mdev->dev, clc,
le32_to_cpu(clc->len),
GFP_KERNEL);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mt7921.h b/drivers/net/wireless/mediatek/mt76/mt7921/mt7921.h
index fb8654b7c5e7..f5628fb6bfbf 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/mt7921.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/mt7921.h
@@ -136,6 +136,7 @@ struct mt7921_dma_layout {
enum {
MT7921_CLC_POWER,
MT7921_CLC_CHAN,
+ MT7921_CLC_REGD = 3,
MT7921_CLC_MAX_NUM,
};
--
2.18.0
^ permalink raw reply related
* Re: [PATCH] wifi: mac80211: reject station association if AP is not started
From: Johannes Berg @ 2026-07-24 7:15 UTC (permalink / raw)
To: Slawomir Stepien
Cc: syzbot, syzkaller-bugs, linux-wireless, linux-kernel, syzbot
In-Reply-To: <amMLPgO8Yv1wjMGa@nr200>
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 :)
johannes
^ permalink raw reply
* Re: [PATCH] wifi: mac80211: reject station association if AP is not started
From: Slawomir Stepien @ 2026-07-24 6:50 UTC (permalink / raw)
To: Johannes Berg
Cc: syzbot, syzkaller-bugs, linux-wireless, linux-kernel, syzbot
In-Reply-To: <c9fa1c083ef9878240c2ee2fb223413153d93a68.camel@sipsolutions.net>
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.
> > dereferences the NULL chanctx_conf, triggering a WARN_ON:
>
> That can't be right?
Yeah, right. I will rephrase it in v2.
> > Fix this by rejecting the addition or modification of a station to the
> > associated state if the AP has not been started (chanctx_conf is NULL).
>
> That's a proxy for "AP is started", but not a good one, I think we
> actually have a flag or so somewhere? Maybe even reject it in cfg80211?
I haven't seen a good place for rejection, but let me double check.
> > Exempt Multi-Link Operation (MLO) interfaces from this check, as they
> > handle chanctx_conf per-link rather than globally on the VIF.
>
> Feels like that's only necessary because the proxy is bad.
>
> Also maybe a similar issue arises if an MLO AP is only half-started, and
> stations are added to all links, or so?
Not sure about MLO, but your suggestion sounds logical and such case might be the same as this one.
I will take a look at this too.
--
Slawomir Stepien
^ permalink raw reply
* Re: [PATCH ath-next 0/2] wifi: ath: Fix buffer overreads in CSA switch count event processing
From: Rameshkumar Sundaram @ 2026-07-24 6:40 UTC (permalink / raw)
To: Jeff Johnson, Jeff Johnson; +Cc: linux-wireless, ath12k, linux-kernel, ath11k
In-Reply-To: <20260723-ath12k_wmi_process_csa_switch_count_event-cleanup-v1-0-c01ef10bc6a3@oss.qualcomm.com>
On 7/24/2026 7:01 AM, Jeff Johnson wrote:
> Both ath11k and ath12k fail to properly sanitize the CSA switch count
> event.
>
> ---
> Jeff Johnson (2):
> wifi: ath12k: fix overreads in ath12k_wmi_process_csa_switch_count_event()
> wifi: ath11k: fix overreads in ath11k_wmi_process_csa_switch_count_event()
>
> drivers/net/wireless/ath/ath11k/wmi.c | 21 +++++++++++++++++++--
> drivers/net/wireless/ath/ath12k/wmi.c | 16 ++++++++++++++++
> 2 files changed, 35 insertions(+), 2 deletions(-)
> ---
> base-commit: 0ceb105245b5e57f803bdc0e79dc077fc06ff384
> change-id: 20260716-ath12k_wmi_process_csa_switch_count_event-cleanup-7fb191b7d7d3
>
Reviewed-by: Rameshkumar Sundaram <rameshkumar.sundaram@oss.qualcomm.com>
^ permalink raw reply
* Re: [PATCH wireless-next] wifi: rt2x00: Use flexible array for hw_mode_spec channels
From: Stanislaw Gruszka @ 2026-07-24 6:29 UTC (permalink / raw)
To: Rosen Penev
Cc: linux-wireless, Kees Cook, Gustavo A. R. Silva, open list,
open list:KERNEL HARDENING (not covered by other areas):Keyword:b__counted_by(_le|_be|_ptr)?b
In-Reply-To: <CAKxU2N_LbndYqrGDQ__sAjj87mj-J8Hs-RWeD90eRcGK1dDAcw@mail.gmail.com>
On Thu, Jul 23, 2026 at 12:26:41PM -0700, Rosen Penev wrote:
> On Thu, Jul 23, 2026 at 12:53 AM Stanislaw Gruszka <stf_xl@wp.pl> wrote:
> >
> > Hi,
> >
> > On Sun, Jun 28, 2026 at 08:31:56PM -0700, Rosen Penev wrote:
> > > struct hw_mode_spec stores channel information that is allocated by the
> > > individual rt2x00 drivers during hardware probing. The channel info
> > > array has the same lifetime as the hardware mode specification, but it
> > > is currently allocated separately and then freed through a separate
> > > pointer.
> > >
> > > Make struct rt2x00_dev hold a pointer to the hardware mode
> > > specification, store the channel info as a flexible array member, and
> > > allocate both pieces together with kzalloc_flex().
> > >
> > > Assisted-by: Codex:GPT-5.5
> > > Signed-off-by: Rosen Penev <rosenp@gmail.com>
> > > ---
> > > .../net/wireless/ralink/rt2x00/rt2400pci.c | 24 +++---
> > > .../net/wireless/ralink/rt2x00/rt2500pci.c | 55 ++++++++------
> > > .../net/wireless/ralink/rt2x00/rt2500usb.c | 55 ++++++++------
> > > .../net/wireless/ralink/rt2x00/rt2800lib.c | 74 ++++++++++---------
> > > drivers/net/wireless/ralink/rt2x00/rt2x00.h | 4 +-
> > > .../net/wireless/ralink/rt2x00/rt2x00config.c | 12 +--
> > > .../net/wireless/ralink/rt2x00/rt2x00dev.c | 4 +-
> > > drivers/net/wireless/ralink/rt2x00/rt61pci.c | 42 ++++++-----
> > > drivers/net/wireless/ralink/rt2x00/rt73usb.c | 50 +++++++------
> > > 9 files changed, 177 insertions(+), 143 deletions(-)
> >
> > This result in extra code. I do not see benefit of avoiding
> > kzalloc calls at cost of adding more lines of code.
> On runtime there's extra UBSAN analysis. Reducing allocations is always good.
No, not always.
Sanitizes are for catching bugs, not for performance.
Adding complexity to the driver to run faster with them
is not good idea.
Also, this is on initialization path, which done usually
once per system boot, so visible gains will be something
between zero an non-existing.
Regards
Stanislaw
^ permalink raw reply
* RE: [PATCH] wifi: rtl818x: initialize eeprom_93cx6 struct to zero
From: Ping-Ke Shih @ 2026-07-24 5:34 UTC (permalink / raw)
To: stf_xl@wp.pl, linux-wireless@vger.kernel.org
In-Reply-To: <20260723110640.8588-1-stf_xl@wp.pl>
stf_xl@wp.pl <stf_xl@wp.pl> wrote:
> From: Stanislaw Gruszka <stf_xl@wp.pl>
>
> Commit 7738a7ab9d12 ("misc: eeprom: eeprom_93cx6: Add quirk for extra
> read clock cycle") added extra 'quirk' field to struct eeprom_93cx6.
>
> Many existing users of eeprom_93cx6, including rtl818x drivers, allocate
> the structure on the stack without initializing all fields. As a result,
> the added quirk field has an undefined value and can randomly cause
> reading wrong data from the EEPROM.
>
> Fix by initializing the structures with {}.
>
> Fixes: 7738a7ab9d12 ("misc: eeprom: eeprom_93cx6: Add quirk for extra read clock cycle")
> Cc: stable@kernel.org # v6.13+
> Signed-off-by: Stanislaw Gruszka <stf_xl@wp.pl>
Reviewed-by: Ping-Ke Shih <pkshih@realtek.com>
^ permalink raw reply
* [PATCH v4 4/4] wifi: ath12k: implement custom wake_tx_queue with flow control
From: Jose Ignacio Tornos Martinez @ 2026-07-24 5:31 UTC (permalink / raw)
To: jjohnson
Cc: ath11k, ath12k, linux-wireless, linux-kernel,
Jose Ignacio Tornos Martinez
In-Reply-To: <20260724053121.15759-1-jtornosm@redhat.com>
Under heavy traffic, ath12k can hang and experiences -ENOMEM errors
("failed to transmit frame -12") when the hardware TCL ring fills up.
This issue is more commonly observed in VMs with PCIe passthrough but
also occurs on bare metal systems.
Implement a custom wake_tx_queue operation that:
1. Checks hardware ring space before dequeuing packets from mac80211
2. Uses per-ring locking (wake_tx_lock with spin_lock_bh) to serialize
concurrent wake_tx_queue calls targeting the same ring and to ensure
bottom halves are disabled as required by ieee80211_tx_dequeue()
3. Syncs with hardware state to get accurate free slot count
4. Resolves the target TCL ring once using get_ring_selector(txq->ac),
which selects the ring based on the access category
5. Returns early during firmware crash in the same way as other
tx paths
This approach follows the pattern used in the iwlwifi driver, adapted
for ath12k's hardware ring architecture.
This prevents hangs, eliminates -ENOMEM errors, and improves throughput
by optimizing resource usage and preventing unnecessary packet drops.
Signed-off-by: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>
---
v4: Adapt to use get_ring_selector(txq->ac) instead of peek API
due to concerns raised by Johannes Berg.
v3: https://lore.kernel.org/all/20260720070852.206495-4-jtornosm@redhat.com/
drivers/net/wireless/ath/ath12k/dp.c | 1 +
drivers/net/wireless/ath/ath12k/dp.h | 2 +
drivers/net/wireless/ath/ath12k/hal.c | 1 +
drivers/net/wireless/ath/ath12k/wifi7/hw.c | 76 +++++++++++++++++++++-
4 files changed, 79 insertions(+), 1 deletion(-)
diff --git a/drivers/net/wireless/ath/ath12k/dp.c b/drivers/net/wireless/ath/ath12k/dp.c
index af5f11fc1d84..3d46cfbf0a1c 100644
--- a/drivers/net/wireless/ath/ath12k/dp.c
+++ b/drivers/net/wireless/ath/ath12k/dp.c
@@ -1539,6 +1539,7 @@ static int ath12k_dp_setup(struct ath12k_base *ab)
}
for (i = 0; i < ab->hw_params->max_tx_ring; i++) {
+ spin_lock_init(&dp->tx_ring[i].wake_tx_lock);
dp->tx_ring[i].tcl_data_ring_id = i;
dp->tx_ring[i].tx_status_head = 0;
diff --git a/drivers/net/wireless/ath/ath12k/dp.h b/drivers/net/wireless/ath/ath12k/dp.h
index f8cfc7bb29dd..68d2020be9b8 100644
--- a/drivers/net/wireless/ath/ath12k/dp.h
+++ b/drivers/net/wireless/ath/ath12k/dp.h
@@ -58,6 +58,8 @@ struct dp_tx_ring {
u8 tcl_data_ring_id;
struct dp_srng tcl_data_ring;
struct dp_srng tcl_comp_ring;
+ /* Serializes wake_tx_queue operations for this ring */
+ spinlock_t wake_tx_lock;
struct hal_wbm_completion_ring_tx *tx_status;
int tx_status_head;
int tx_status_tail;
diff --git a/drivers/net/wireless/ath/ath12k/hal.c b/drivers/net/wireless/ath/ath12k/hal.c
index a164563fff28..071cb5d30931 100644
--- a/drivers/net/wireless/ath/ath12k/hal.c
+++ b/drivers/net/wireless/ath/ath12k/hal.c
@@ -390,6 +390,7 @@ int ath12k_hal_srng_src_num_free(struct ath12k_base *ab, struct hal_srng *srng,
else
return ((srng->ring_size - hp + tp) / srng->entry_size) - 1;
}
+EXPORT_SYMBOL_GPL(ath12k_hal_srng_src_num_free);
void *ath12k_hal_srng_src_next_peek(struct ath12k_base *ab,
struct hal_srng *srng)
diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hw.c b/drivers/net/wireless/ath/ath12k/wifi7/hw.c
index 7436cf70925a..af0a3ba805dd 100644
--- a/drivers/net/wireless/ath/ath12k/wifi7/hw.c
+++ b/drivers/net/wireless/ath/ath12k/wifi7/hw.c
@@ -1100,9 +1100,83 @@ static void ath12k_wifi7_mac_op_tx(struct ieee80211_hw *hw,
}
}
+static void ath12k_wifi7_mac_op_wake_tx_queue(struct ieee80211_hw *hw,
+ struct ieee80211_txq *txq)
+{
+ struct ath12k_vif *ahvif = ath12k_vif_to_ahvif(txq->vif);
+ struct ath12k_hw *ah = ath12k_hw_to_ah(hw);
+ struct ieee80211_tx_control control = {
+ .sta = txq->sta,
+ };
+ struct ieee80211_vif *vif = txq->vif;
+ struct ath12k_link_vif *arvif;
+ struct dp_tx_ring *tx_ring;
+ struct hal_srng *tcl_ring;
+ struct ath12k_sta *ahsta;
+ struct ath12k_dp *dp;
+ struct sk_buff *skb;
+ struct ath12k *ar;
+ u8 ring_selector;
+ int num_free;
+ u8 ring_id;
+ u8 link_id;
+
+ if (ieee80211_vif_is_mld(vif) && txq->sta) {
+ ahsta = ath12k_sta_to_ahsta(txq->sta);
+ link_id = ahsta->assoc_link_id;
+ } else {
+ link_id = ahvif->deflink.link_id;
+ }
+
+ rcu_read_lock();
+
+ arvif = rcu_dereference(ahvif->link[link_id]);
+ if (!arvif || !arvif->ar) {
+ rcu_read_unlock();
+ return;
+ }
+
+ ar = arvif->ar;
+ dp = ar->ab->dp;
+
+ ring_selector = dp->hw_params->hw_ops->get_ring_selector(txq->ac);
+ ring_id = ring_selector % dp->hw_params->max_tx_ring;
+ tx_ring = &dp->tx_ring[ring_id];
+ tcl_ring = &dp->hal->srng_list[tx_ring->tcl_data_ring.ring_id];
+
+ while (1) {
+ if (unlikely(test_bit(ATH12K_FLAG_CRASH_FLUSH,
+ &ar->ab->dev_flags)))
+ break;
+
+ spin_lock_bh(&tx_ring->wake_tx_lock);
+
+ spin_lock(&tcl_ring->lock);
+ num_free = ath12k_hal_srng_src_num_free(ar->ab, tcl_ring, true);
+ spin_unlock(&tcl_ring->lock);
+
+ if (num_free == 0) {
+ spin_unlock_bh(&tx_ring->wake_tx_lock);
+ break;
+ }
+
+ skb = ieee80211_tx_dequeue(hw, txq);
+ if (!skb) {
+ spin_unlock_bh(&tx_ring->wake_tx_lock);
+ break;
+ }
+
+ ath12k_wifi7_mac_op_tx(hw, &control, skb);
+
+ spin_unlock_bh(&tx_ring->wake_tx_lock);
+ }
+
+ rcu_read_unlock();
+}
+
static const struct ieee80211_ops ath12k_ops_wifi7 = {
.tx = ath12k_wifi7_mac_op_tx,
- .wake_tx_queue = ieee80211_handle_wake_tx_queue,
+ .wake_tx_queue = ath12k_wifi7_mac_op_wake_tx_queue,
.start = ath12k_mac_op_start,
.stop = ath12k_mac_op_stop,
.reconfig_complete = ath12k_mac_op_reconfig_complete,
--
2.54.0
^ permalink raw reply related
* [PATCH v4 3/4] wifi: ath11k: implement custom wake_tx_queue with flow control
From: Jose Ignacio Tornos Martinez @ 2026-07-24 5:31 UTC (permalink / raw)
To: jjohnson
Cc: ath11k, ath12k, linux-wireless, linux-kernel,
Jose Ignacio Tornos Martinez
In-Reply-To: <20260724053121.15759-1-jtornosm@redhat.com>
Under heavy traffic, ath11k experiences frequent -ENOMEM errors
("failed to transmit frame -12") when the hardware TCL ring fills up.
This issue is more commonly observed in VMs with PCIe passthrough but
also occurs on bare metal systems. It is particularly problematic on
devices with a single shared TCL ring where all traffic classes
compete for the same 512 descriptor slots.
Implement a custom wake_tx_queue operation that:
1. Checks hardware ring space before dequeuing packets from mac80211
2. Uses per-ring locking (wake_tx_lock with spin_lock_bh) to serialize
concurrent wake_tx_queue calls targeting the same ring and to ensure
bottom halves are disabled as required by ieee80211_tx_dequeue()
3. Syncs with hardware state to get accurate free slot count
4. Resolves the target TCL ring using get_ring_selector(txq->ac),
which selects the ring based on the access category
5. Returns early during firmware crash in the same way as other
tx paths
This approach follows the pattern used in the iwlwifi driver, adapted
for ath11k's hardware ring architecture.
This eliminates -ENOMEM errors and improves throughput by optimizing
resource usage and preventing unnecessary packet drops.
Signed-off-by: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>
---
v4: Adapt to use get_ring_selector(txq->ac) instead of peek API
due to concerns raised by Johannes Berg.
v3: https://lore.kernel.org/all/20260720070852.206495-3-jtornosm@redhat.com/
drivers/net/wireless/ath/ath11k/dp.c | 1 +
drivers/net/wireless/ath/ath11k/dp.h | 2 ++
drivers/net/wireless/ath/ath11k/mac.c | 52 ++++++++++++++++++++++++++-
3 files changed, 54 insertions(+), 1 deletion(-)
diff --git a/drivers/net/wireless/ath/ath11k/dp.c b/drivers/net/wireless/ath/ath11k/dp.c
index f389b97acbdd..2e5978ec2b05 100644
--- a/drivers/net/wireless/ath/ath11k/dp.c
+++ b/drivers/net/wireless/ath/ath11k/dp.c
@@ -1087,6 +1087,7 @@ int ath11k_dp_alloc(struct ath11k_base *ab)
for (i = 0; i < ab->hw_params.hal_params->num_tx_rings; i++) {
idr_init(&dp->tx_ring[i].txbuf_idr);
spin_lock_init(&dp->tx_ring[i].tx_idr_lock);
+ spin_lock_init(&dp->tx_ring[i].wake_tx_lock);
dp->tx_ring[i].tcl_data_ring_id = i;
dp->tx_ring[i].tx_status_head = 0;
diff --git a/drivers/net/wireless/ath/ath11k/dp.h b/drivers/net/wireless/ath/ath11k/dp.h
index 84f66839f0c6..6d99501aa269 100644
--- a/drivers/net/wireless/ath/ath11k/dp.h
+++ b/drivers/net/wireless/ath/ath11k/dp.h
@@ -87,6 +87,8 @@ struct dp_tx_ring {
struct idr txbuf_idr;
/* Protects txbuf_idr and num_pending */
spinlock_t tx_idr_lock;
+ /* Serializes wake_tx_queue operations for this ring */
+ spinlock_t wake_tx_lock;
struct hal_wbm_release_ring *tx_status;
int tx_status_head;
int tx_status_tail;
diff --git a/drivers/net/wireless/ath/ath11k/mac.c b/drivers/net/wireless/ath/ath11k/mac.c
index 2d55cdc4d165..9813f7923c34 100644
--- a/drivers/net/wireless/ath/ath11k/mac.c
+++ b/drivers/net/wireless/ath/ath11k/mac.c
@@ -10065,9 +10065,59 @@ static int ath11k_mac_op_sta_state(struct ieee80211_hw *hw,
return ret;
}
+static void ath11k_mac_op_wake_tx_queue(struct ieee80211_hw *hw,
+ struct ieee80211_txq *txq)
+{
+ struct ieee80211_tx_control control = {
+ .sta = txq->sta,
+ };
+ struct ath11k *ar = hw->priv;
+ struct dp_tx_ring *tx_ring;
+ struct hal_srng *tcl_ring;
+ struct sk_buff *skb;
+ u32 ring_selector;
+ int num_free;
+ u8 ring_id;
+
+ if (!ar)
+ return;
+
+ ring_selector = ar->ab->hw_params.hw_ops->get_ring_selector(txq->ac);
+ ring_id = ring_selector % ar->ab->hw_params.hal_params->num_tx_rings;
+ tx_ring = &ar->ab->dp.tx_ring[ring_id];
+ tcl_ring = &ar->ab->hal.srng_list[tx_ring->tcl_data_ring.ring_id];
+
+ while (1) {
+ if (unlikely(test_bit(ATH11K_FLAG_CRASH_FLUSH,
+ &ar->ab->dev_flags)))
+ break;
+
+ spin_lock_bh(&tx_ring->wake_tx_lock);
+
+ spin_lock(&tcl_ring->lock);
+ num_free = ath11k_hal_srng_src_num_free(ar->ab, tcl_ring, true);
+ spin_unlock(&tcl_ring->lock);
+
+ if (num_free == 0) {
+ spin_unlock_bh(&tx_ring->wake_tx_lock);
+ break;
+ }
+
+ skb = ieee80211_tx_dequeue(hw, txq);
+ if (!skb) {
+ spin_unlock_bh(&tx_ring->wake_tx_lock);
+ break;
+ }
+
+ ath11k_mac_op_tx(hw, &control, skb);
+
+ spin_unlock_bh(&tx_ring->wake_tx_lock);
+ }
+}
+
static const struct ieee80211_ops ath11k_ops = {
.tx = ath11k_mac_op_tx,
- .wake_tx_queue = ieee80211_handle_wake_tx_queue,
+ .wake_tx_queue = ath11k_mac_op_wake_tx_queue,
.start = ath11k_mac_op_start,
.stop = ath11k_mac_op_stop,
.reconfig_complete = ath11k_mac_op_reconfig_complete,
--
2.54.0
^ permalink raw reply related
* [PATCH v4 2/4] wifi: ath11k/ath12k: remove skb parameter from get_ring_selector
From: Jose Ignacio Tornos Martinez @ 2026-07-24 5:31 UTC (permalink / raw)
To: jjohnson
Cc: ath11k, ath12k, linux-wireless, linux-kernel,
Jose Ignacio Tornos Martinez
In-Reply-To: <20260724053121.15759-1-jtornosm@redhat.com>
Change the get_ring_selector hw_ops callback signature from
get_ring_selector(struct sk_buff *skb) to get_ring_selector(u8 ac),
removing the dependency on the skb.
After the previous patch switched WCN6750 from skb_get_hash() to
skb_get_queue_mapping(), all get_ring_selector implementations fall
into two categories:
- smp_processor_id(): ignores the skb entirely
- skb_get_queue_mapping(): only reads the AC value from the skb
Since the AC is the only information extracted from the skb, pass it
directly as a u8 parameter instead. Callers in the dp_tx path now
pass skb_get_queue_mapping(skb), which preserves the exact same
behavior.
This change enables a subsequent patch to call get_ring_selector()
from wake_tx_queue using txq->ac, without needing access to the skb.
mac80211 guarantees that skb_get_queue_mapping(skb) == txq->ac
for any skb dequeued from a given txq, since both values are
derived from the same skb->priority through the same AC mapping.
No functional change.
Signed-off-by: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>
---
v4: new patch
drivers/net/wireless/ath/ath11k/dp_tx.c | 2 +-
drivers/net/wireless/ath/ath11k/hw.c | 16 ++++------------
drivers/net/wireless/ath/ath11k/hw.h | 2 +-
drivers/net/wireless/ath/ath12k/hw.h | 2 +-
drivers/net/wireless/ath/ath12k/wifi7/dp_tx.c | 2 +-
drivers/net/wireless/ath/ath12k/wifi7/hw.c | 6 +++---
6 files changed, 11 insertions(+), 19 deletions(-)
diff --git a/drivers/net/wireless/ath/ath11k/dp_tx.c b/drivers/net/wireless/ath/ath11k/dp_tx.c
index cac970c92806..083aca8ff355 100644
--- a/drivers/net/wireless/ath/ath11k/dp_tx.c
+++ b/drivers/net/wireless/ath/ath11k/dp_tx.c
@@ -108,7 +108,7 @@ int ath11k_dp_tx(struct ath11k *ar, struct ath11k_vif *arvif,
pool_id = skb_get_queue_mapping(skb) & (ATH11K_HW_MAX_QUEUES - 1);
- ring_selector = ab->hw_params.hw_ops->get_ring_selector(skb);
+ ring_selector = ab->hw_params.hw_ops->get_ring_selector(skb_get_queue_mapping(skb));
tcl_ring_sel:
tcl_ring_retry = false;
diff --git a/drivers/net/wireless/ath/ath11k/hw.c b/drivers/net/wireless/ath/ath11k/hw.c
index d679e39dce03..8a28c1d4c17b 100644
--- a/drivers/net/wireless/ath/ath11k/hw.c
+++ b/drivers/net/wireless/ath/ath11k/hw.c
@@ -876,22 +876,14 @@ static bool ath11k_hw_wcn6855_rx_desc_get_ldpc_support(struct hal_rx_desc *desc)
__le32_to_cpu(desc->u.wcn6855.msdu_start.info2));
}
-static u32 ath11k_hw_ipq8074_get_tcl_ring_selector(struct sk_buff *skb)
-{
- /* Let the default ring selection be based on current processor
- * number, where one of the 3 tcl rings are selected based on
- * the smp_processor_id(). In case that ring
- * is full/busy, we resort to other available rings.
- * If all rings are full, we drop the packet.
- *
- * TODO: Add throttling logic when all rings are full
- */
+static u32 ath11k_hw_ipq8074_get_tcl_ring_selector(u8 ac)
+{
return smp_processor_id();
}
-static u32 ath11k_hw_wcn6750_get_tcl_ring_selector(struct sk_buff *skb)
+static u32 ath11k_hw_wcn6750_get_tcl_ring_selector(u8 ac)
{
- return skb_get_queue_mapping(skb);
+ return ac;
}
const struct ath11k_hw_ops ipq8074_ops = {
diff --git a/drivers/net/wireless/ath/ath11k/hw.h b/drivers/net/wireless/ath/ath11k/hw.h
index 4996536fbd14..b6bc8b72d812 100644
--- a/drivers/net/wireless/ath/ath11k/hw.h
+++ b/drivers/net/wireless/ath/ath11k/hw.h
@@ -273,7 +273,7 @@ struct ath11k_hw_ops {
u16 (*mpdu_info_get_peerid)(struct hal_rx_mpdu_info *mpdu_info);
bool (*rx_desc_mac_addr2_valid)(struct hal_rx_desc *desc);
u8* (*rx_desc_mpdu_start_addr2)(struct hal_rx_desc *desc);
- u32 (*get_ring_selector)(struct sk_buff *skb);
+ u32 (*get_ring_selector)(u8 ac);
};
extern const struct ath11k_hw_ops ipq8074_ops;
diff --git a/drivers/net/wireless/ath/ath12k/hw.h b/drivers/net/wireless/ath/ath12k/hw.h
index 86fb8b719613..65caa7a2349f 100644
--- a/drivers/net/wireless/ath/ath12k/hw.h
+++ b/drivers/net/wireless/ath/ath12k/hw.h
@@ -243,7 +243,7 @@ struct ath12k_hw_ops {
int (*mac_id_to_pdev_id)(const struct ath12k_hw_params *hw, int mac_id);
int (*mac_id_to_srng_id)(const struct ath12k_hw_params *hw, int mac_id);
int (*rxdma_ring_sel_config)(struct ath12k_base *ab);
- u8 (*get_ring_selector)(struct sk_buff *skb);
+ u8 (*get_ring_selector)(u8 ac);
bool (*dp_srng_is_tx_comp_ring)(int ring_num);
bool (*is_frame_link_agnostic)(struct ath12k_link_vif *arvif,
struct ieee80211_mgmt *mgmt);
diff --git a/drivers/net/wireless/ath/ath12k/wifi7/dp_tx.c b/drivers/net/wireless/ath/ath12k/wifi7/dp_tx.c
index d2749de44553..74359e8eea47 100644
--- a/drivers/net/wireless/ath/ath12k/wifi7/dp_tx.c
+++ b/drivers/net/wireless/ath/ath12k/wifi7/dp_tx.c
@@ -111,7 +111,7 @@ int ath12k_wifi7_dp_tx(struct ath12k_pdev_dp *dp_pdev, struct ath12k_link_vif *a
* If all rings are full, we drop the packet.
* TODO: Add throttling logic when all rings are full
*/
- ring_selector = dp->hw_params->hw_ops->get_ring_selector(skb);
+ ring_selector = dp->hw_params->hw_ops->get_ring_selector(skb_get_queue_mapping(skb));
tcl_ring_sel:
tcl_ring_retry = false;
diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hw.c b/drivers/net/wireless/ath/ath12k/wifi7/hw.c
index d9fdd2fc8298..7436cf70925a 100644
--- a/drivers/net/wireless/ath/ath12k/wifi7/hw.c
+++ b/drivers/net/wireless/ath/ath12k/wifi7/hw.c
@@ -49,7 +49,7 @@ ath12k_wifi7_hw_mac_id_to_srng_id_qcn9274(const struct ath12k_hw_params *hw,
return 0;
}
-static u8 ath12k_wifi7_hw_get_ring_selector_qcn9274(struct sk_buff *skb)
+static u8 ath12k_wifi7_hw_get_ring_selector_qcn9274(u8 ac)
{
return smp_processor_id();
}
@@ -83,9 +83,9 @@ ath12k_wifi7_hw_mac_id_to_srng_id_wcn7850(const struct ath12k_hw_params *hw,
return mac_id;
}
-static u8 ath12k_wifi7_hw_get_ring_selector_wcn7850(struct sk_buff *skb)
+static u8 ath12k_wifi7_hw_get_ring_selector_wcn7850(u8 ac)
{
- return skb_get_queue_mapping(skb);
+ return ac;
}
static bool ath12k_wifi7_dp_srng_is_comp_ring_wcn7850(int ring_num)
--
2.54.0
^ permalink raw reply related
* [PATCH v4 1/4] wifi: ath11k: use queue mapping for WCN6750 ring selection
From: Jose Ignacio Tornos Martinez @ 2026-07-24 5:31 UTC (permalink / raw)
To: jjohnson
Cc: ath11k, ath12k, linux-wireless, linux-kernel,
Jose Ignacio Tornos Martinez
In-Reply-To: <20260724053121.15759-1-jtornosm@redhat.com>
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.
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
^ permalink raw reply related
* [PATCH v4 0/4] ath11k/ath12k: implement TX flow control
From: Jose Ignacio Tornos Martinez @ 2026-07-24 5:31 UTC (permalink / raw)
To: jjohnson
Cc: ath11k, ath12k, linux-wireless, linux-kernel,
Jose Ignacio Tornos Martinez
This series implements custom wake_tx_queue operations for ath11k and
ath12k drivers to prevent hardware ring overflow issues under heavy
traffic.
Without proper flow control, both drivers experience -ENOMEM errors
("failed to transmit frame -12") when the hardware TCL ring fills up.
Additionally, ath12k can hang under sustained high throughput. These
issues are more commonly observed in VMs with PCIe passthrough but
also occur on bare metal systems.
The implementation follows the pattern used in the iwlwifi driver,
adapted for ath11k/ath12k hardware ring architecture, checking
hardware ring space before dequeuing packets from mac80211.
Testing shows stable operation with eliminated -ENOMEM errors, no hangs,
and improved throughput under heavy traffic conditions.
Jose Ignacio Tornos Martinez (4):
wifi: ath11k: use queue mapping for WCN6750 ring selection
wifi: ath11k/ath12k: remove skb parameter from get_ring_selector
wifi: ath11k: implement custom wake_tx_queue with flow control
wifi: ath12k: implement custom wake_tx_queue with flow control
v4: Address feedback from Johannes Berg:
- Drop the ieee80211_tx_peek() mac80211 patch entirely due to the
commented concerns.
- Redesign as a consequence of dropping the peek API:
- Patch 1: switch WCN6750 from skb_get_hash() to skb_get_queue_mapping()
as a preparatory step, since the AC value is all that is needed.
This aligns with the approach already used by ath12k WCN7850
(which has the same ring count).
- Patch 2: Instead of peeking the skb to call get_ring_selector(skb),
pass txq->ac directly via get_ring_selector(u8 ac). This is safe
because mac80211 guarantees skb_get_queue_mapping(skb) == txq->ac for
any skb dequeued from a given txq.
- Patch 3 and 4: adapt tx flow control to the new approach.
v3: https://lore.kernel.org/all/20260720070852.206495-1-jtornosm@redhat.com/
--
2.49.0
^ permalink raw reply
* Re: [PATCH ath-next 0/2] wifi: ath: Fix buffer overreads in CSA switch count event processing
From: Baochen Qiang @ 2026-07-24 5:28 UTC (permalink / raw)
To: Jeff Johnson, Jeff Johnson; +Cc: linux-wireless, ath12k, linux-kernel, ath11k
In-Reply-To: <20260723-ath12k_wmi_process_csa_switch_count_event-cleanup-v1-0-c01ef10bc6a3@oss.qualcomm.com>
On 7/24/2026 9:31 AM, Jeff Johnson wrote:
> Both ath11k and ath12k fail to properly sanitize the CSA switch count
> event.
>
> ---
> Jeff Johnson (2):
> wifi: ath12k: fix overreads in ath12k_wmi_process_csa_switch_count_event()
> wifi: ath11k: fix overreads in ath11k_wmi_process_csa_switch_count_event()
>
> drivers/net/wireless/ath/ath11k/wmi.c | 21 +++++++++++++++++++--
> drivers/net/wireless/ath/ath12k/wmi.c | 16 ++++++++++++++++
> 2 files changed, 35 insertions(+), 2 deletions(-)
> ---
> base-commit: 0ceb105245b5e57f803bdc0e79dc077fc06ff384
> change-id: 20260716-ath12k_wmi_process_csa_switch_count_event-cleanup-7fb191b7d7d3
>
>
Reviewed-by: Baochen Qiang <baochen.qiang@oss.qualcomm.com>
^ permalink raw reply
* Re: [PATCH v8 5/9] block: implement NVMEM provider
From: Christoph Hellwig @ 2026-07-24 4:57 UTC (permalink / raw)
To: Loic Poulain
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: <20260703-block-as-nvmem-v8-5-98ae32bfc49a@oss.qualcomm.com>
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.
> +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.
> + if (IS_ERR(bdev_file))
> + return PTR_ERR(bdev_file);
> +
> + while (bytes_left) {
.. and just use __kernel_read instead of badly reimplementing it.
> + 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?
> +
> + 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.
Also once you register the whole devices as nvmem provider, we really
should keep it from being used a block device, shouldn't we?
^ permalink raw reply
* Re: [PATCH ath-current v2 0/8] wifi: ath12k: support firmware-allocated MLD peer ID
From: Rameshkumar Sundaram @ 2026-07-24 4:55 UTC (permalink / raw)
To: Baochen Qiang, Jeff Johnson; +Cc: linux-wireless, ath12k
In-Reply-To: <20260720-ath12k-fw-allocated-ml-peer-id-v2-0-630632758a80@oss.qualcomm.com>
On 7/20/2026 12:13 PM, Baochen Qiang wrote:
> ath12k currently assumes the host allocates the MLD peer ID and passes
> it down to firmware via WMI_PEER_ASSOC_CMDID. This works on QCN9274
> but breaks WCN7850/QCC2072, whose firmware always picks the ID itself
> and reports it back through HTT_T2H_MSG_TYPE_MLO_RX_PEER_MAP. As a
> result dp_hw->dp_peers[] is never populated for MLO peers and the data
> path lookup fails. On QCC2072 the firmware additionally crashes on MLO
> disconnect when ATH12K_WMI_FLAG_MLO_PEER_ID_VALID was set in the peer
> assoc command.
>
> Add a host_alloc_ml_id hw_param to branch behavior, defer the
> dp_peers[] publish to the HTT event for firmware-allocated chips, and
> propagate the firmware-assigned ID through the existing host
> bookkeeping when it arrives.
>
> Patch summary:
>
> 1: fix for an out-of-bounds clear_bit() in
> ath12k_mac_dp_peer_cleanup().
> 2: group peer assoc send-and-wait into a helper
> 3: refactor, keep ATH12K_PEER_ML_ID_VALID set in ahsta->ml_peer_id
> so later patches do not have to OR or mask it at every call site;
> 4: parse the HTT_T2H_MSG_TYPE_MLO_RX_PEER_MAP message;
> 5: introduce hw_param host_alloc_ml_id, set true on QCN9274 family
> and false on WCN7850/QCC2072;
> 6: on host_alloc_ml_id == false, leave peer_id_valid unset and send
> ml_peer_id == 0 in WMI_PEER_ASSOC_CMDID;
> 7: on host_alloc_ml_id == false, mark ahsta->ml_peer_id and
> dp_peer->peer_id as ATH12K_MLO_PEER_ID_PENDING and skip the
> dp_hw->dp_peers[] publish until the firmware reports the ID;
> 8: in the MLO_RX_PEER_MAP handler, propagate the firmware-assigned
> ID into dp_peer->peer_id, every dp_link_peer in
> dp_peer->link_peers[], and ahsta->ml_peer_id, all under
> dp_hw->peer_lock.
>
> ---
> Changes in v2:
> - patch 5/8: initialize ret before goto err handling in ath12k_mac_allocate()
> - Link to v1: https://lore.kernel.org/r/20260713-ath12k-fw-allocated-ml-peer-id-v1-0-d0a2a1a519eb@oss.qualcomm.com
>
> ---
> Baochen Qiang (8):
> wifi: ath12k: fix out-of-bounds clear_bit in ath12k_mac_dp_peer_cleanup()
> wifi: ath12k: factor out peer assoc send-and-wait into a helper
> wifi: ath12k: keep ATH12K_PEER_ML_ID_VALID set in ath12k_sta::ml_peer_id
> wifi: ath12k: add support for HTT_T2H_MSG_TYPE_MLO_RX_PEER_MAP
> wifi: ath12k: introduce host_alloc_ml_id hardware parameter
> wifi: ath12k: do not advertise MLD peer ID for firmware-allocate devices
> wifi: ath12k: defer dp_peer registration when firmware allocates MLD peer ID
> wifi: ath12k: resolve PENDING ML peer ID from MLO_PEER_MAP HTT event
>
> drivers/net/wireless/ath/ath12k/core.c | 2 +
> drivers/net/wireless/ath/ath12k/core.h | 3 +
> drivers/net/wireless/ath/ath12k/dp_htt.c | 49 +++++++++
> drivers/net/wireless/ath/ath12k/dp_htt.h | 12 +++
> drivers/net/wireless/ath/ath12k/dp_peer.c | 75 +++++++++++--
> drivers/net/wireless/ath/ath12k/dp_peer.h | 2 +
> drivers/net/wireless/ath/ath12k/hw.h | 2 +
> drivers/net/wireless/ath/ath12k/mac.c | 165 ++++++++++++++++++++---------
> drivers/net/wireless/ath/ath12k/peer.c | 31 +++++-
> drivers/net/wireless/ath/ath12k/peer.h | 1 +
> drivers/net/wireless/ath/ath12k/wifi7/hw.c | 12 +++
> 11 files changed, 293 insertions(+), 61 deletions(-)
> ---
> base-commit: 951dc0a744e4dc8490935316d3b76e23990bde3c
> change-id: 20260527-ath12k-fw-allocated-ml-peer-id-2b456891157f
Reviewed-by: Rameshkumar Sundaram <rameshkumar.sundaram@oss.qualcomm.com>
^ permalink raw reply
* Re: [PATCH] wifi: rtw88: disable ASPM and deep PS on ASUS TUF Gaming A15 FA506II
From: Ping-Ke Shih @ 2026-07-24 3:46 UTC (permalink / raw)
To: Mihail Dimoski, Ping-Ke Shih; +Cc: linux-wireless, Mihail Dimoski
In-Reply-To: <20260718124045.23493-1-mihaildimoski@gmail.com>
Mihail Dimoski <mihaildimoski@gmail.com> wrote:
> The RTL8822CE on the ASUS TUF Gaming A15 FA506II wedges during normal
> use. The driver watchdog toggles PCIe ASPM while leaving power save;
> the DBI read of the ASPM link-config register fails with -EIO, the PCIe
> link becomes unstable, and the device drops off the bus, taking Wi-Fi
> down until a cold power cycle:
>
> rtw88_8822ce 0000:03:00.0: failed to read ASPM, ret=-5
> rtw88_8822ce 0000:03:00.0: firmware failed to leave lps state
> rtw88_8822ce 0000:03:00.0: mac power on failed
>
> This is the same platform ASPM inter-operability problem already
> handled for other machines through rtw_pci_quirks[]. Disabling PCI
> ASPM and deep power save on this model stops the failure. Add a DMI
> quirk so the workaround is applied automatically.
>
> Signed-off-by: Mihail Dimoski <mihaildimoski@gmail.com>
> Acked-by: Ping-Ke Shih <pkshih@realtek.com>
1 patch(es) applied to rtw-next branch of rtw.git, thanks.
b0034a2e4156 wifi: rtw88: disable ASPM and deep PS on ASUS TUF Gaming A15 FA506II
---
https://github.com/pkshih/rtw.git
^ permalink raw reply
* Re: [PATCH rtw-next 01/13] wifi: rtw89: coex: Add version 107 TX/RX info for firmware feature
From: Ping-Ke Shih @ 2026-07-24 3:30 UTC (permalink / raw)
To: Ping-Ke Shih, linux-wireless; +Cc: ku920601
In-Reply-To: <20260717065739.64124-2-pkshih@realtek.com>
Ping-Ke Shih <pkshih@realtek.com> wrote:
> From: Ching-Te Ku <ku920601@realtek.com>
>
> The previous version 7 format is for Dual-Bluetooth using. This patch
> is for single Bluetooth solution using. Driver will summary Wi-Fi now
> status for firmware to train RF parameters & TDMA mechanism.
>
> Signed-off-by: Ching-Te Ku <ku920601@realtek.com>
> Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
13 patch(es) applied to rtw-next branch of rtw.git, thanks.
0427315a6ed1 wifi: rtw89: coex: Add version 107 TX/RX info for firmware feature
4bedf26e132c wifi: rtw89: coex: Add version 9 report control info
b5bfb43844e1 wifi: rtw89: coex: Fix unexpected grant-signal assignee
00961e2705b7 wifi: rtw89: coex: Branch out version 105 firmware report map index
49989a3fef37 wifi: rtw89: coex: Refine chip initial related structure
81d30a7be825 wifi: rtw89: coex: Add driver info H2C command index version 103
35b569b60aa0 wifi: rtw89: coex: Fix Wi-Fi role info H2C command header issue
58b1bde3712e wifi: rtw89: coex: Add firmware 0.29.133.X support for RTL8852B family
42a83b5f13e3 wifi: rtw89: coex: Add TDMA version 4
b87da03ee7e9 wifi: rtw89: coex: Add slots version 2
b8892add7e51 wifi: rtw89: coex: Add cycle status report version 105
236badaa50b3 wifi: rtw89: coex: Add wifi role info version 101
38c58d541cfe wifi: rtw89: coex: Add firmware 0.27.97.X support for RTL8852C
---
https://github.com/pkshih/rtw.git
^ permalink raw reply
* Re: [PATCH rtw-next 1/9] wifi: rtw89: add LED support to reflect the wireless association status
From: Ping-Ke Shih @ 2026-07-24 3:03 UTC (permalink / raw)
To: Ping-Ke Shih, linux-wireless; +Cc: gary.chang, sc.lee, wenjie.tsai
In-Reply-To: <20260717061910.54466-2-pkshih@realtek.com>
Ping-Ke Shih <pkshih@realtek.com> wrote:
> From: Johnson Tsai <wenjie.tsai@realtek.com>
>
> Add a new RTW89_LEDS Kconfig option, along with LED structures to describe
> flexible GPIO mappings for chip common LED definition. Core LED lifecycle
> and registration logic default to the mac80211 association trigger, and
> chips are wired up as the first user with a single-GPIO monochrome LED.
>
> Usage:
> - Auto-triggered (default):
> ON when connected, OFF when disconnected
> - Manual override:
> echo <1|0> > /sys/class/leds/rtw89-phyX/brightness
>
> Signed-off-by: Johnson Tsai <wenjie.tsai@realtek.com>
> Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
9 patch(es) applied to rtw-next branch of rtw.git, thanks.
d910631ff352 wifi: rtw89: add LED support to reflect the wireless association status
721d90c8509a wifi: rtw89: add multicolor LED support for RTL8852CU valve board
355626a2c232 wifi: rtw89: 8852cu: add quirk to disable 2.4 GHz band
e92d5b02b456 wifi: rtw89: rfk: update TXIQK H2C command format to v1
89833570775a wifi: rtw89: 8922d: bypass TXIQK when scan
ce18a0cbc39b wifi: rtw89: wow: extend timeout unit to avoid SER false alarm
238bc3141cde wifi: rtw89: pci: set PCIe maximum TS1 for RTL8922DE
e738d2ac80c9 wifi: rtw89: 8922d: reduce IO in power-on function
b3dc0a37fd3a wifi: rtw89: phy: set CFR to manual mode for some 2GHz channels
---
https://github.com/pkshih/rtw.git
^ permalink raw reply
* Re: [PATCH v2] wifi: ath12k: restore country code during resume
From: Baochen Qiang @ 2026-07-24 2:51 UTC (permalink / raw)
To: Jeff Johnson, Stian Knudsen, Jeff Johnson, ath12k; +Cc: linux-wireless
In-Reply-To: <bacea060-bae5-4136-841c-49db5b561b51@oss.qualcomm.com>
On 7/24/2026 8:15 AM, Jeff Johnson wrote:
> On 7/20/2026 5:09 AM, Stian Knudsen wrote:
>> The country code configured before suspend is lost after resume:
>> the device is powered down in suspend_late and powered back up in
>> resume_early, so firmware reboots with its default regulatory
>> settings and the previously set country code is no longer applied.
>>
>> On WCN7850 the firmware comes back in the world regulatory domain
>> (country 00) and takes ~7 seconds to rediscover the country code
>> from AP beacons, part of a ~16 second total delay before wifi
>> reconnects after resume.
>>
>> Restore it by resending WMI_SET_CURRENT_COUNTRY_CMDID during resume
>> if a country code was set before suspend, i.e. when ar->alpha2 is
>> valid. This follows the same approach as ath11k commit 7f0343b7b871
>> ("wifi: ath11k: restore country code during resume").
>>
>> Note that only single_pdev_only devices support suspend/resume (see
>> ath12k_core_continue_suspend_resume()), so handling the first and
>> only pdev is sufficient.
>>
>> Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c7-00108-QCAHMTSWPL_V1.0_V2.0_SILICONZ_UPSTREAM-3
>>
>> Fixes: 8d5f4da8d70b ("wifi: ath12k: support suspend/resume")
>> Suggested-by: Baochen Qiang <baochen.qiang@oss.qualcomm.com>
>> Link: https://lore.kernel.org/r/-_iDJ_M5RrqACWB0qmtleg@pokerfj.es
>> Signed-off-by: Stian Knudsen <stian@pokerfj.es>
>> ---
>> v2:
>> - Add Fixes tag, per Baochen's review comment.
>> - Link to v1: https://lore.kernel.org/r/20260717130254.36732-1-stian@pokerfj.es
>> ---
>> drivers/net/wireless/ath/ath12k/core.c | 21 +++++++++++++++++++++
>> 1 file changed, 21 insertions(+)
>>
>> diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c
>> --- a/drivers/net/wireless/ath/ath12k/core.c
>> +++ b/drivers/net/wireless/ath/ath12k/core.c
>> @@ -197,6 +197,7 @@ EXPORT_SYMBOL(ath12k_core_resume_early);
>>
>> int ath12k_core_resume(struct ath12k_base *ab)
>> {
>> + struct ath12k *ar;
>> long time_left;
>> int ret;
>>
>> @@ -211,6 +212,26 @@ int ath12k_core_resume(struct ath12k_base *ab)
>> return -ETIMEDOUT;
>> }
>>
>> + /* So far only single_pdev_only devices can reach here,
>> + * so it is valid to handle the first, and the only, pdev.
>> + */
>> + ar = ab->pdevs[0].ar;
>> + if (ab->hw_params->current_cc_support &&
>> + ar->alpha2[0] != 0 && ar->alpha2[1] != 0) {
>> + struct wmi_set_current_country_arg arg = {};
>> +
>> + memcpy(&arg.alpha2, ar->alpha2, 2);
>> +
>> + reinit_completion(&ar->regd_update_completed);
>> +
>> + ret = ath12k_wmi_send_set_current_country_cmd(ar, &arg);
>> + if (ret) {
>> + ath12k_warn(ab, "failed to set country code during resume: %d\n",
>> + ret);
>> + return ret;
>> + }
>
> Baochen, can you remind me why we don't need to wait for completion here?
> (the ath11k change doesn't wait)
Technically we will wait, but asynchronously instead of synchronously. It works in a way:
1. reinit completion
2. send new cc to firmware, proceed with no sync wait.
3. in reg channel list event host process reg rules, queue regd update work and signals
completion.
4. regd update work calls ath12k_regd_update() where we wait for the completion.
But I am feeling this is not a good design. As you can see, ath12k_regd_update() is
reachable via the regd update work, which is queued in reg chan list event. However it is
the event itself signals the completion. That means ath12k_regd_update() never blocks, the
whole reinit-wait thing is a no-ops.
>
> If that is legit, perhaps we should document it with a comment since
> reinit_completion() without waiting for it seems strange
>
>> + }
>> +
>> return 0;
>> }
>> EXPORT_SYMBOL(ath12k_core_resume);
>> --
>> 2.50.0
>
^ permalink raw reply
* Re: [PATCH v2 1/2] PCI: Move Spacemit vendor and device IDs to linux/pci_ids.h
From: Ping-Ke Shih @ 2026-07-24 2:23 UTC (permalink / raw)
To: Anirudh Srinivasan, Bjorn Helgaas, Yixun Lan, Ping-Ke Shih,
Lorenzo Pieralisi, Krzysztof Wilczyński,
Manivannan Sadhasivam, Rob Herring, Inochi Amaoto, Aurelien Jarno
Cc: linux-pci, linux-kernel, linux-riscv, spacemit, linux-wireless,
Anirudh Srinivasan
In-Reply-To: <20260716-rtw89-spacemit-k3-v2-1-392b577ebf75@oss.tenstorrent.com>
Anirudh Srinivasan <asrinivasan@oss.tenstorrent.com> wrote:
> Move the vendor and device ID for the existing Spacemit K1 PCIe Root
> Complex to include/linux/pci_ids.h. Also add K3's Root Complex device ID
> to this header. This is done so that these values can be referenced in
> the rtw89 driver to enable 36-bit DMA ability in it for WiFi to function
> on the K3 Pico ITX board.
>
> Acked-by: Bjorn Helgaas <bhelgaas@google.com>
> Signed-off-by: Anirudh Srinivasan <asrinivasan@oss.tenstorrent.com>
2 patch(es) applied to rtw-next branch of rtw.git, thanks.
888b1934642b PCI: Move Spacemit vendor and device IDs to linux/pci_ids.h
27a5046cc56e wifi: rtw89: pci: enable 36-bit DMA on spacemit K3
---
https://github.com/pkshih/rtw.git
^ permalink raw reply
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