[PATCH v4] wifi: mt76: mt7921: fix txpower reporting from rate power configuration

Lucid Duck lucid_duck at justthetip.ca
Thu Mar 19 13:38:54 PDT 2026


The mt7921 driver never updates phy->txpower_cur from the rate power
configuration sent to firmware, causing mt76_get_txpower() to report
bogus values to userspace (typically 3 dBm) regardless of actual
regulatory or SAR limits.

Two issues are addressed:

1. The rate power loop in mt76_connac_mcu_rate_txpower_band() computes
   the correct bounded TX power per channel via
   mt76_get_rate_power_limits() but discards the return value. Capture
   it and store to phy->txpower_cur when processing the current
   channel, matching how mt7915 handles this in
   mt7915_mcu_set_txpower_sku(). Subtract the multi-chain path delta
   before storing, since mt76_get_txpower() adds it back when
   reporting -- consistent with mt7915's use of mt76_get_power_bound()
   which performs the same subtraction.

2. mt7921 uses the chanctx model but its add_chanctx callback does not
   update phy->chandef, leaving it stale after association. The rate
   power loop's channel comparison then fails silently. Sync
   phy->chandef from ctx->def in add_chanctx and change_chanctx, and
   recompute txpower_cur via a lightweight helper that performs the
   same bounded power calculation for the current channel without
   reprogramming firmware rate tables.

Tested on Alfa AWUS036AXML (MT7921AU), kernel 6.19.8, Canada:

  Before: iw dev wlan0 info shows "txpower 3.00 dBm" (wrong)
  After:  2.4GHz 36 dBm, 5GHz 23 dBm, 6GHz 12 dBm (match regulatory)

Cc: stable at vger.kernel.org
Fixes: 1c099ab44727 ("mt76: mt7921: add MAC support")
Signed-off-by: Lucid Duck <lucid_duck at justthetip.ca>
---
Changes since v3:
- Removed mt7921_set_tx_sar_pwr() from add_chanctx and change_chanctx.
  Channel transitions don't change underlying power constraints, so
  reprogramming the full rate table is unnecessary. Replaced with a
  lightweight helper that recomputes txpower_cur locally.
- Removed IEEE80211_CONF_CHANGE_CHANNEL trigger from config().
- Removed BSS_CHANGED_TXPOWER handler from bss_info_changed(). Writing
  per-vif txpower into per-HW hw->conf.power_level breaks multi-vif
  semantics. User txpower limits need a different approach (follow-up).
- Subtracted path delta before storing txpower_cur. The connac rate
  loop stores total bounded power, but mt76_get_txpower() adds the
  multi-chain path delta when reporting. mt7915 accounts for this via
  mt76_get_power_bound(), which subtracts the delta before storing.
  Without the same subtraction, reported values were inflated by 3 dBm
  on 2x2 devices.

 .../wireless/mediatek/mt76/mt76_connac_mcu.c  | 14 +++++++--
 .../net/wireless/mediatek/mt76/mt7921/main.c  | 30 +++++++++++++++++++
 2 files changed, 41 insertions(+), 3 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..e26a2cb39 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c
@@ -2193,14 +2193,22 @@ 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 -
+					mt76_tx_power_path_delta(
+						hweight16(phy->chainmask));
 
 			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..a77ae5791 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/main.c
@@ -1355,13 +1355,39 @@ mt7921_stop_ap(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
 	mt792x_mutex_release(dev);
 }
 
+static void mt7921_update_txpower_cur(struct mt76_phy *phy)
+{
+	struct mt76_power_limits limits;
+	struct ieee80211_channel *chan = phy->chandef.chan;
+	int n_chains = hweight16(phy->chainmask);
+	s8 reg_power, sar_power, max_power;
+	int tx_power;
+
+	if (!chan)
+		return;
+
+	tx_power = 2 * phy->hw->conf.power_level;
+	if (!tx_power)
+		tx_power = 127;
+
+	reg_power = mt76_connac_get_ch_power(phy, chan, tx_power);
+	sar_power = mt76_get_sar_power(phy, chan, reg_power);
+	max_power = mt76_get_rate_power_limits(phy, chan, &limits, sar_power);
+
+	phy->txpower_cur = max_power - mt76_tx_power_path_delta(n_chains);
+}
+
 static int
 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;
+	mt7921_update_txpower_cur(mphy);
+
 	return 0;
 }
 
@@ -1396,6 +1422,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_update_txpower_cur(phy->mt76);
+
 	mt792x_mutex_release(phy->dev);
 }
 
-- 
2.53.0




More information about the Linux-mediatek mailing list