[PATCH v3 1/1] wifi: mt76: mt7921: fix txpower reporting from rate power configuration

Lucid Duck lucid_duck at justthetip.ca
Tue Mar 17 10:30:16 PDT 2026


The mt7921 driver never updates phy->txpower_cur
when TX power rate configuration is sent to firmware. This causes
mt76_get_txpower() to report bogus values to userspace (typically
3 dBm) regardless of actual regulatory or SAR limits. User-set
txpower limits via iw are also not reflected.

Three root causes are addressed:

1. The rate power loop in mt76_connac_mcu_rate_txpower_band() computes
   the correct bounded TX power for each channel but discards the return
   value of mt76_get_rate_power_limits(). Fix: capture the return value
   and store it to phy->txpower_cur when processing the current channel.

2. mt7921 uses the chanctx model but its add_chanctx callback bypasses
   the common mt76_phy_update_channel(), leaving phy->chandef stale.
   Fix: update phy->chandef from ctx->def in both add_chanctx and
   change_chanctx, and trigger the rate power path to refresh
   txpower_cur. Also trigger on IEEE80211_CONF_CHANGE_CHANNEL in
   config(), matching mt7915.

3. For chanctx drivers, mac80211 routes user txpower changes through
   BSS_CHANGED_TXPOWER in bss_info_changed() -- not through
   IEEE80211_CONF_CHANGE_POWER in config(). hw->conf.power_level is
   never updated. Fix: handle BSS_CHANGED_TXPOWER in
   mt7921_bss_info_changed(), bridge bss_conf.txpower to
   hw->conf.power_level, and re-trigger the rate power path.

Tested on Alfa AWUS036AXML (MT7921AU), kernel 6.17.1-300.fc43:

  Before: iw dev wlan0 info shows "txpower 3.00 dBm" (wrong)
  After:  correct per-band values, user limits reflected

Test results (regulatory domain: Canada/CA):
  - 2.4GHz ch6:  33 dBm (30 dBm limit + 3 dBm 2x2 path delta)
  - 5GHz ch36:   26 dBm (23 dBm limit + 3 dBm path delta)
  - 6GHz ch5:    15 dBm (12 dBm limit + 3 dBm path delta)
  - Band switch: 100 cycles, 0 failures
  - Module reload: 50 cycles, 0 failures
  - 2-hour soak: 480 samples, zero drift
  - Regdomain switching: 10 countries, all correct
  - User txpower limits: reflected on all bands
  - Monitor mode: correct on all tested channels

Signed-off-by: Lucid Duck <lucid_duck at justthetip.ca>
---
 .../wireless/mediatek/mt76/mt76_connac_mcu.c  | 12 +++++++---
 .../net/wireless/mediatek/mt76/mt7921/main.c  | 22 ++++++++++++++++++-
 2 files changed, 30 insertions(+), 4 deletions(-)

diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c
index 16db0f208..5856924a9 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c
@@ -2193,14 +2193,20 @@ mt76_connac_mcu_rate_txpower_band(struct mt76_phy *phy,
 				.hw_value = ch_list[idx],
 				.band = band,
 			};
-			s8 reg_power, sar_power;
+			s8 reg_power, sar_power, max_power;
 
 			reg_power = mt76_connac_get_ch_power(phy, &chan,
 							     tx_power);
 			sar_power = mt76_get_sar_power(phy, &chan, reg_power);
 
-			mt76_get_rate_power_limits(phy, &chan, limits,
-						   sar_power);
+			max_power = mt76_get_rate_power_limits(phy, &chan,
+							       limits,
+							       sar_power);
+
+			if (phy->chandef.chan &&
+			    phy->chandef.chan->hw_value == ch_list[idx] &&
+			    phy->chandef.chan->band == band)
+				phy->txpower_cur = max_power;
 
 			tx_power_tlv.last_msg = ch_list[idx] == last_ch;
 			sku_tlbv.channel = ch_list[idx];
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/main.c b/drivers/net/wireless/mediatek/mt76/mt7921/main.c
index 5881040ac..38a59c6f2 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/main.c
@@ -638,7 +638,8 @@ static int mt7921_config(struct ieee80211_hw *hw, int radio_idx, u32 changed)
 
 	mt792x_mutex_acquire(dev);
 
-	if (changed & IEEE80211_CONF_CHANGE_POWER) {
+	if (changed & (IEEE80211_CONF_CHANGE_POWER |
+		       IEEE80211_CONF_CHANGE_CHANNEL)) {
 		ret = mt7921_set_tx_sar_pwr(hw, NULL);
 		if (ret)
 			goto out;
@@ -719,6 +720,14 @@ static void mt7921_bss_info_changed(struct ieee80211_hw *hw,
 	if (changed & BSS_CHANGED_CQM)
 		mt7921_mcu_set_rssimonitor(dev, vif);
 
+	if (changed & BSS_CHANGED_TXPOWER) {
+		int tx_power = info->txpower;
+
+		if (tx_power != INT_MIN && tx_power > 0)
+			hw->conf.power_level = tx_power;
+		mt7921_set_tx_sar_pwr(hw, NULL);
+	}
+
 	if (changed & BSS_CHANGED_ASSOC) {
 		mt7921_mcu_sta_update(dev, NULL, vif, true,
 				      MT76_STA_INFO_STATE_ASSOC);
@@ -1360,8 +1369,15 @@ mt7921_add_chanctx(struct ieee80211_hw *hw,
 		   struct ieee80211_chanctx_conf *ctx)
 {
 	struct mt792x_dev *dev = mt792x_hw_dev(hw);
+	struct mt76_phy *mphy = hw->priv;
 
 	dev->new_ctx = ctx;
+	mphy->chandef = ctx->def;
+
+	mt792x_mutex_acquire(dev);
+	mt7921_set_tx_sar_pwr(hw, NULL);
+	mt792x_mutex_release(dev);
+
 	return 0;
 }
 
@@ -1396,6 +1412,10 @@ mt7921_change_chanctx(struct ieee80211_hw *hw,
 		mt7921_mcu_config_sniffer(mvif, ctx);
 	else
 		mt76_connac_mcu_uni_set_chctx(mvif->phy->mt76, &mvif->bss_conf.mt76, ctx);
+
+	phy->mt76->chandef = ctx->def;
+	mt7921_set_tx_sar_pwr(hw, NULL);
+
 	mt792x_mutex_release(phy->dev);
 }
 
-- 
2.51.0




More information about the Linux-mediatek mailing list