diff mbox series

[6/8] mt76: mt7915: report tx-retries

Message ID 20211118164549.3863-6-greearb@candelatech.com (mailing list archive)
State Rejected
Delegated to: Felix Fietkau
Headers show
Series [1/8] mt76: mt7915: cache sgi in wcid. | expand

Commit Message

Ben Greear Nov. 18, 2021, 4:45 p.m. UTC
From: Ben Greear <greearb@candelatech.com>

mac80211 stack will only report tx-status for skb claiming to be ampdu heads,
so lie a bit in mt7915 and set the flag so that mac80211 will record status
for each skb.

mt7915 appears to report retry status on an individual per-skb manner,
so that method above seems to work.

Re-constitute the txinfo status rate info so that the rix and flags
is also at least close to correct.  No direct way to report HE
rates that way, so mac80211 might could use some tweaking in
the ieee80211_tx_status_ext to take both info and status->rate
into account.

Signed-off-by: Ben Greear <greearb@candelatech.com>
---
 drivers/net/wireless/mediatek/mt76/mac80211.c |  4 +
 drivers/net/wireless/mediatek/mt76/mt76.h     |  5 +
 .../net/wireless/mediatek/mt76/mt7915/init.c  |  1 +
 .../net/wireless/mediatek/mt76/mt7915/mac.c   | 93 ++++++++++++++++++-
 .../net/wireless/mediatek/mt76/mt7915/mac.h   |  4 +-
 .../net/wireless/mediatek/mt76/mt7915/main.c  |  4 +
 drivers/net/wireless/mediatek/mt76/tx.c       |  6 +-
 7 files changed, 110 insertions(+), 7 deletions(-)

Comments

Ryder Lee Nov. 18, 2021, 5:19 p.m. UTC | #1
On Thu, 2021-11-18 at 08:45 -0800, greearb@candelatech.com wrote:
> From: Ben Greear <greearb@candelatech.com>
> 
> mac80211 stack will only report tx-status for skb claiming to be
> ampdu heads,
> so lie a bit in mt7915 and set the flag so that mac80211 will record
> status
> for each skb.
> 
> mt7915 appears to report retry status on an individual per-skb
> manner,
> so that method above seems to work.
> 
> Re-constitute the txinfo status rate info so that the rix and flags
> is also at least close to correct.  No direct way to report HE
> rates that way, so mac80211 might could use some tweaking in
> the ieee80211_tx_status_ext to take both info and status->rate
> into account.
> 
> Signed-off-by: Ben Greear <greearb@candelatech.com>
> ---
>  drivers/net/wireless/mediatek/mt76/mac80211.c |  4 +
>  drivers/net/wireless/mediatek/mt76/mt76.h     |  5 +
>  .../net/wireless/mediatek/mt76/mt7915/init.c  |  1 +
>  .../net/wireless/mediatek/mt76/mt7915/mac.c   | 93
> ++++++++++++++++++-
>  .../net/wireless/mediatek/mt76/mt7915/mac.h   |  4 +-
>  .../net/wireless/mediatek/mt76/mt7915/main.c  |  4 +
>  drivers/net/wireless/mediatek/mt76/tx.c       |  6 +-
>  7 files changed, 110 insertions(+), 7 deletions(-)
> 
> diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c
> b/drivers/net/wireless/mediatek/mt76/mac80211.c
> index 62807dc311c1..a385c1850c61 100644
> --- a/drivers/net/wireless/mediatek/mt76/mac80211.c
> +++ b/drivers/net/wireless/mediatek/mt76/mac80211.c
> @@ -1517,6 +1517,10 @@ void mt76_ethtool_worker(struct
> mt76_ethtool_worker_info *wi,
>  
>  	wi->sta_count++;
>  
> +	data[ei++] += stats->tx_mpdu_attempts;
> +	data[ei++] += stats->tx_mpdu_fail;
> +	data[ei++] += stats->tx_mpdu_retry;
> +	data[ei++] += stats->tx_mpdu_ok;
>  	data[ei++] += stats->tx_mode[MT_PHY_TYPE_CCK];
>  	data[ei++] += stats->tx_mode[MT_PHY_TYPE_OFDM];
>  	data[ei++] += stats->tx_mode[MT_PHY_TYPE_HT];
> diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h
> b/drivers/net/wireless/mediatek/mt76/mt76.h
> index 7234703b3c60..5a431b39d5c1 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt76.h
> +++ b/drivers/net/wireless/mediatek/mt76/mt76.h
> @@ -785,6 +785,11 @@ enum mt76_phy_type {
>  };
>  
>  struct mt76_sta_stats {
> +	unsigned long tx_mpdu_attempts; /* counting any retries */
> +	unsigned long tx_mpdu_fail; /* frames that failed even after
> retry */
> +	unsigned long tx_mpdu_ok; /* frames that succeeded,
> +				     perhaps after retry */
> +	unsigned long tx_mpdu_retry; /* number of times frames were
> retried */
>  	u64 tx_mode[__MT_PHY_TYPE_HE_MAX];
>  	u64 tx_bw[4];		/* 20, 40, 80, 160 */
>  	u64 tx_nss[4];		/* 1, 2, 3, 4 */
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/init.c
> b/drivers/net/wireless/mediatek/mt76/mt7915/init.c
> index 3b35ea245b33..90aa62c64f3e 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7915/init.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt7915/init.c
> @@ -311,6 +311,7 @@ mt7915_init_wiphy(struct ieee80211_hw *hw)
>  	struct wiphy *wiphy = hw->wiphy;
>  
>  	hw->queues = 4;
> +	hw->max_report_rates = 1;
>  	hw->max_rx_aggregation_subframes = IEEE80211_MAX_AMPDU_BUF;
>  	hw->max_tx_aggregation_subframes = IEEE80211_MAX_AMPDU_BUF;
>  	hw->netdev_features = NETIF_F_RXCSUM;
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c
> b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c
> index e37e6b05c7a4..4f565a77770c 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c
> @@ -1246,17 +1246,22 @@ mt7915_txp_skb_unmap(struct mt76_dev *dev,
> struct mt76_txwi_cache *t)
>  
>  static void
>  mt7915_txwi_free(struct mt7915_dev *dev, struct mt76_txwi_cache *t,
> -		 struct ieee80211_sta *sta, struct list_head
> *free_list)
> +		 struct ieee80211_sta *sta, struct list_head
> *free_list,
> +		 u32 tx_cnt, u32 tx_status, u32 ampdu)
>  {
>  	struct mt76_dev *mdev = &dev->mt76;
>  	struct mt76_wcid *wcid;
>  	__le32 *txwi;
>  	u16 wcid_idx;
> +	struct ieee80211_tx_info *info;
> +	struct ieee80211_tx_rate *rate;
>  
>  	mt7915_txp_skb_unmap(mdev, t);
>  	if (!t->skb)
>  		goto out;
>  
> +	rcu_read_lock(); /* protect wcid access */
> +
>  	txwi = (__le32 *)mt76_get_txwi_ptr(mdev, t);
>  	if (sta) {
>  		wcid = (struct mt76_wcid *)sta->drv_priv;
> @@ -1266,6 +1271,75 @@ mt7915_txwi_free(struct mt7915_dev *dev,
> struct mt76_txwi_cache *t,
>  			mt7915_tx_check_aggr(sta, txwi);
>  	} else {
>  		wcid_idx = FIELD_GET(MT_TXD1_WLAN_IDX,
> le32_to_cpu(txwi[1]));
> +		wcid = rcu_dereference(mdev->wcid[wcid_idx]);
> +	}
> +
> +	info = IEEE80211_SKB_CB(t->skb);
> +
> +	/* Cannot clear all of info->status, we need the driver private
> +	 * status intact.
> +	 */
> +	info->status.is_valid_ack_signal = 0;
> +
> +	rate = &info->status.rates[0];
> +	rate->idx = -1; /* will over-write below if we found wcid */
> +	info->status.rates[1].idx = -1; /* terminate rate list */
> +
> +	/* force TX_STAT_AMPDU to be set, or mac80211 will ignore
> status */
> +	if (ampdu || (info->flags & IEEE80211_TX_CTL_AMPDU)) {
> +		info->flags |= IEEE80211_TX_STAT_AMPDU |
> IEEE80211_TX_CTL_AMPDU;
> +		info->status.ampdu_len = 1;
> +	}
> +
> +	/* update info status based on cached wcid rate info since
> +	 * txfree path doesn't give us a lot of info.
> +	 */
> +	if (wcid) {
> +		struct mt7915_sta *msta = container_of(wcid, struct
> mt7915_sta, wcid);
> +		struct mt76_sta_stats *stats = &msta->stats;
> +
> +		if (wcid->rate.flags & RATE_INFO_FLAGS_MCS) {
> +			rate->flags |= IEEE80211_TX_RC_MCS;
> +			rate->idx = wcid->rate.mcs + wcid->rate.nss *
> 8;
> +		} else if (wcid->rate.flags & RATE_INFO_FLAGS_VHT_MCS)
> {
> +			rate->flags |= IEEE80211_TX_RC_VHT_MCS;
> +			rate->idx = (wcid->rate.nss << 4) | wcid-
> >rate.mcs;
> +		} else if (wcid->rate.flags & RATE_INFO_FLAGS_HE_MCS) {
> +			rate->idx = (wcid->rate.nss << 4) | wcid-
> >rate.mcs;

Can ieee80211_tx_rate tell HE rate apart now?

Ryder
Ben Greear Nov. 18, 2021, 5:26 p.m. UTC | #2
On 11/18/21 9:19 AM, Ryder Lee wrote:
> On Thu, 2021-11-18 at 08:45 -0800, greearb@candelatech.com wrote:
>> From: Ben Greear <greearb@candelatech.com>
>>
>> mac80211 stack will only report tx-status for skb claiming to be
>> ampdu heads,
>> so lie a bit in mt7915 and set the flag so that mac80211 will record
>> status
>> for each skb.
>>
>> mt7915 appears to report retry status on an individual per-skb
>> manner,
>> so that method above seems to work.
>>
>> Re-constitute the txinfo status rate info so that the rix and flags
>> is also at least close to correct.  No direct way to report HE
>> rates that way, so mac80211 might could use some tweaking in
>> the ieee80211_tx_status_ext to take both info and status->rate
>> into account.
>>
>> Signed-off-by: Ben Greear <greearb@candelatech.com>

>> +			rate->flags |= IEEE80211_TX_RC_VHT_MCS;
>> +			rate->idx = (wcid->rate.nss << 4) | wcid-
>>> rate.mcs;
>> +		} else if (wcid->rate.flags & RATE_INFO_FLAGS_HE_MCS) {
>> +			rate->idx = (wcid->rate.nss << 4) | wcid-
>>> rate.mcs;
> 
> Can ieee80211_tx_rate tell HE rate apart now?
Upstream code cannot, but it has other ways of dealing with HE
rates, and this patch does not break that as far as I can tell.

Thanks,
Ben

> 
> Ryder
> 
>
diff mbox series

Patch

diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c
index 62807dc311c1..a385c1850c61 100644
--- a/drivers/net/wireless/mediatek/mt76/mac80211.c
+++ b/drivers/net/wireless/mediatek/mt76/mac80211.c
@@ -1517,6 +1517,10 @@  void mt76_ethtool_worker(struct mt76_ethtool_worker_info *wi,
 
 	wi->sta_count++;
 
+	data[ei++] += stats->tx_mpdu_attempts;
+	data[ei++] += stats->tx_mpdu_fail;
+	data[ei++] += stats->tx_mpdu_retry;
+	data[ei++] += stats->tx_mpdu_ok;
 	data[ei++] += stats->tx_mode[MT_PHY_TYPE_CCK];
 	data[ei++] += stats->tx_mode[MT_PHY_TYPE_OFDM];
 	data[ei++] += stats->tx_mode[MT_PHY_TYPE_HT];
diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h
index 7234703b3c60..5a431b39d5c1 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76.h
@@ -785,6 +785,11 @@  enum mt76_phy_type {
 };
 
 struct mt76_sta_stats {
+	unsigned long tx_mpdu_attempts; /* counting any retries */
+	unsigned long tx_mpdu_fail; /* frames that failed even after retry */
+	unsigned long tx_mpdu_ok; /* frames that succeeded,
+				     perhaps after retry */
+	unsigned long tx_mpdu_retry; /* number of times frames were retried */
 	u64 tx_mode[__MT_PHY_TYPE_HE_MAX];
 	u64 tx_bw[4];		/* 20, 40, 80, 160 */
 	u64 tx_nss[4];		/* 1, 2, 3, 4 */
diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/init.c b/drivers/net/wireless/mediatek/mt76/mt7915/init.c
index 3b35ea245b33..90aa62c64f3e 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7915/init.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7915/init.c
@@ -311,6 +311,7 @@  mt7915_init_wiphy(struct ieee80211_hw *hw)
 	struct wiphy *wiphy = hw->wiphy;
 
 	hw->queues = 4;
+	hw->max_report_rates = 1;
 	hw->max_rx_aggregation_subframes = IEEE80211_MAX_AMPDU_BUF;
 	hw->max_tx_aggregation_subframes = IEEE80211_MAX_AMPDU_BUF;
 	hw->netdev_features = NETIF_F_RXCSUM;
diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c
index e37e6b05c7a4..4f565a77770c 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c
@@ -1246,17 +1246,22 @@  mt7915_txp_skb_unmap(struct mt76_dev *dev, struct mt76_txwi_cache *t)
 
 static void
 mt7915_txwi_free(struct mt7915_dev *dev, struct mt76_txwi_cache *t,
-		 struct ieee80211_sta *sta, struct list_head *free_list)
+		 struct ieee80211_sta *sta, struct list_head *free_list,
+		 u32 tx_cnt, u32 tx_status, u32 ampdu)
 {
 	struct mt76_dev *mdev = &dev->mt76;
 	struct mt76_wcid *wcid;
 	__le32 *txwi;
 	u16 wcid_idx;
+	struct ieee80211_tx_info *info;
+	struct ieee80211_tx_rate *rate;
 
 	mt7915_txp_skb_unmap(mdev, t);
 	if (!t->skb)
 		goto out;
 
+	rcu_read_lock(); /* protect wcid access */
+
 	txwi = (__le32 *)mt76_get_txwi_ptr(mdev, t);
 	if (sta) {
 		wcid = (struct mt76_wcid *)sta->drv_priv;
@@ -1266,6 +1271,75 @@  mt7915_txwi_free(struct mt7915_dev *dev, struct mt76_txwi_cache *t,
 			mt7915_tx_check_aggr(sta, txwi);
 	} else {
 		wcid_idx = FIELD_GET(MT_TXD1_WLAN_IDX, le32_to_cpu(txwi[1]));
+		wcid = rcu_dereference(mdev->wcid[wcid_idx]);
+	}
+
+	info = IEEE80211_SKB_CB(t->skb);
+
+	/* Cannot clear all of info->status, we need the driver private
+	 * status intact.
+	 */
+	info->status.is_valid_ack_signal = 0;
+
+	rate = &info->status.rates[0];
+	rate->idx = -1; /* will over-write below if we found wcid */
+	info->status.rates[1].idx = -1; /* terminate rate list */
+
+	/* force TX_STAT_AMPDU to be set, or mac80211 will ignore status */
+	if (ampdu || (info->flags & IEEE80211_TX_CTL_AMPDU)) {
+		info->flags |= IEEE80211_TX_STAT_AMPDU | IEEE80211_TX_CTL_AMPDU;
+		info->status.ampdu_len = 1;
+	}
+
+	/* update info status based on cached wcid rate info since
+	 * txfree path doesn't give us a lot of info.
+	 */
+	if (wcid) {
+		struct mt7915_sta *msta = container_of(wcid, struct mt7915_sta, wcid);
+		struct mt76_sta_stats *stats = &msta->stats;
+
+		if (wcid->rate.flags & RATE_INFO_FLAGS_MCS) {
+			rate->flags |= IEEE80211_TX_RC_MCS;
+			rate->idx = wcid->rate.mcs + wcid->rate.nss * 8;
+		} else if (wcid->rate.flags & RATE_INFO_FLAGS_VHT_MCS) {
+			rate->flags |= IEEE80211_TX_RC_VHT_MCS;
+			rate->idx = (wcid->rate.nss << 4) | wcid->rate.mcs;
+		} else if (wcid->rate.flags & RATE_INFO_FLAGS_HE_MCS) {
+			rate->idx = (wcid->rate.nss << 4) | wcid->rate.mcs;
+		} else {
+			rate->idx = wcid->rate.mcs;
+		}
+
+		switch (wcid->rate.bw) {
+		case RATE_INFO_BW_160:
+			rate->flags |= IEEE80211_TX_RC_160_MHZ_WIDTH;
+			break;
+		case RATE_INFO_BW_80:
+			rate->flags |= IEEE80211_TX_RC_80_MHZ_WIDTH;
+			break;
+		case RATE_INFO_BW_40:
+			rate->flags |= IEEE80211_TX_RC_40_MHZ_WIDTH;
+			break;
+		}
+
+		stats->tx_mpdu_attempts += tx_cnt;
+		stats->tx_mpdu_retry += tx_cnt - 1;
+
+		if (tx_status == 0)
+			stats->tx_mpdu_ok++;
+		else
+			stats->tx_mpdu_fail++;
+	}
+
+	rcu_read_unlock();
+
+	/* Apply the values that this txfree path reports */
+	rate->count = tx_cnt;
+	if (tx_status == 0) {
+		info->flags |= IEEE80211_TX_STAT_ACK;
+		info->status.ampdu_ack_len = 1;
+	} else {
+		info->flags &= ~IEEE80211_TX_STAT_ACK;
 	}
 
 	__mt76_tx_complete_skb(mdev, wcid_idx, t->skb, free_list);
@@ -1285,7 +1359,8 @@  mt7915_mac_tx_free(struct mt7915_dev *dev, struct sk_buff *skb)
 	struct ieee80211_sta *sta = NULL;
 	LIST_HEAD(free_list);
 	struct sk_buff *tmp;
-	u8 i, count;
+	u8 i;
+	u16 count;
 	bool wake = false;
 
 	/* clean DMA queues and unmap buffers first */
@@ -1301,9 +1376,12 @@  mt7915_mac_tx_free(struct mt7915_dev *dev, struct sk_buff *skb)
 	 * to the time ack is received or dropped by hw (air + hw queue time).
 	 * Should avoid accessing WTBL to get Tx airtime, and use it instead.
 	 */
+	/* free->ctrl is high u16 of first DW in the txfree struct */
 	count = FIELD_GET(MT_TX_FREE_MSDU_CNT, le16_to_cpu(free->ctrl));
 	for (i = 0; i < count; i++) {
-		u32 msdu, info = le32_to_cpu(free->info[i]);
+		u32 msdu, tx_cnt, tx_status;
+		u32 info = le32_to_cpu(free->info[i]); /* DW3+ */
+		u32 ampdu;
 
 		/*
 		 * 1'b1: new wcid pair.
@@ -1334,7 +1412,12 @@  mt7915_mac_tx_free(struct mt7915_dev *dev, struct sk_buff *skb)
 		if (!txwi)
 			continue;
 
-		mt7915_txwi_free(dev, txwi, sta, &free_list);
+		tx_cnt = FIELD_GET(MT_TX_FREE_TXCNT, info);
+		/* 0 = success, 1 dropped-by-hw, 2 dropped-by-cpu */
+		tx_status = FIELD_GET(MT_TX_FREE_STATUS, info);
+		ampdu = FIELD_GET(MT_TX_FREE_HEAD_OF_PAGE, info);
+
+		mt7915_txwi_free(dev, txwi, sta, &free_list, tx_cnt, tx_status, ampdu);
 	}
 
 	mt7915_mac_sta_poll(dev);
@@ -1839,7 +1922,7 @@  void mt7915_tx_token_put(struct mt7915_dev *dev)
 
 	spin_lock_bh(&dev->mt76.token_lock);
 	idr_for_each_entry(&dev->mt76.token, txwi, id) {
-		mt7915_txwi_free(dev, txwi, NULL, NULL);
+		mt7915_txwi_free(dev, txwi, NULL, NULL, 0, 1, 0);
 		dev->mt76.token_count--;
 	}
 	spin_unlock_bh(&dev->mt76.token_lock);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mac.h b/drivers/net/wireless/mediatek/mt76/mt7915/mac.h
index 4ba5574cc6f3..1b08bbe769c4 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7915/mac.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7915/mac.h
@@ -300,7 +300,7 @@  struct mt7915_tx_free {
 	__le16 ctrl;
 	u8 txd_cnt;
 	u8 rsv[3];
-	__le32 info[];
+	__le32 info[]; /* DW3+ */
 } __packed __aligned(4);
 
 #define MT_TX_FREE_MSDU_CNT		GENMASK(9, 0)
@@ -312,6 +312,8 @@  struct mt7915_tx_free {
 /* when configured for txcount mode.  See MT_PLE_HOST_RPT0_TX_LATENCY. */
 #define MT_TX_FREE_TXCNT		GENMASK(12, 0)
 #define MT_TX_FREE_STATUS		GENMASK(14, 13)
+/* 0:  not MPDU, 1:  MSDU is head pkt of TXD page (MPDU) */
+#define MT_TX_FREE_HEAD_OF_PAGE		BIT(15)
 #define MT_TX_FREE_MSDU_ID		GENMASK(30, 16)
 #define MT_TX_FREE_PAIR			BIT(31)
 
diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/main.c b/drivers/net/wireless/mediatek/mt76/mt7915/main.c
index 057ab27b7083..3d702dc899ba 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7915/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7915/main.c
@@ -1140,6 +1140,10 @@  static const char mt7915_gstrings_stats[][ETH_GSTRING_LEN] = {
 	"rx_ba_cnt",
 
 	/* per vif counters */
+	"v_tx_mpdu_attempts", /* counting any retries */
+	"v_tx_mpdu_fail",  /* frames that failed even after retry */
+	"v_tx_mpdu_retry", /* number of times frames were retried */
+	"v_tx_mpdu_ok", /* frames that succeeded, perhaps after retry */
 	"v_tx_mode_cck",
 	"v_tx_mode_ofdm",
 	"v_tx_mode_ht",
diff --git a/drivers/net/wireless/mediatek/mt76/tx.c b/drivers/net/wireless/mediatek/mt76/tx.c
index 11719ef034d8..be3227008f57 100644
--- a/drivers/net/wireless/mediatek/mt76/tx.c
+++ b/drivers/net/wireless/mediatek/mt76/tx.c
@@ -229,6 +229,7 @@  void __mt76_tx_complete_skb(struct mt76_dev *dev, u16 wcid_idx, struct sk_buff *
 	struct ieee80211_tx_status status = {
 		.skb = skb,
 		.free_list = free_list,
+		.info = IEEE80211_SKB_CB(skb),
 	};
 	struct mt76_wcid *wcid = NULL;
 	struct ieee80211_hw *hw;
@@ -236,8 +237,11 @@  void __mt76_tx_complete_skb(struct mt76_dev *dev, u16 wcid_idx, struct sk_buff *
 
 	rcu_read_lock();
 
-	if (wcid_idx < ARRAY_SIZE(dev->wcid))
+	if (wcid_idx < ARRAY_SIZE(dev->wcid)) {
 		wcid = rcu_dereference(dev->wcid[wcid_idx]);
+		if (wcid)
+			status.rate = &wcid->rate;
+	}
 
 	mt76_tx_check_non_aql(dev, wcid, skb);