]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
wifi: rtw89: handle IEEE80211_TX_CTL_REQ_TX_STATUS frames for USB
authorFedor Pchelkin <pchelkin@ispras.ru>
Tue, 4 Nov 2025 13:57:15 +0000 (16:57 +0300)
committerPing-Ke Shih <pkshih@realtek.com>
Thu, 6 Nov 2025 06:32:14 +0000 (14:32 +0800)
Frames flagged with IEEE80211_TX_CTL_REQ_TX_STATUS mean the driver has to
report to mac80211 stack whether AP sent ACK for the null frame/probe
request or not.  It's not implemented in USB part of the driver yet.

PCIe HCI has its own way of getting TX status incorporated into RPP
feature, and it's always enabled there.  Other HCIs need a different
scheme based on processing C2H messages.

Thus define a .tx_rpt_enabled flag indicating which HCIs need to enable a
TX report feature.  Currently it is USB only.

Toggle a bit in the TX descriptor and place flagged skbs in a fix-sized
queue to wait for a message from the firmware.  Firmware maintains a 4-bit
sequence number for required frames hence the queue can contain just 16
elements simultaneously.  That's enough for normal driver / firmware
communication.  If the firmware crashes for any reason and doesn't provide
TX reports in time, driver will handle this and report the obsolete frames
as dropped.

rtw89 also has a new feature providing a TX report for each transmission
attempt.  Ignore a failed TX status reported by the firmware until retry
limit is reached or successful status appears.  When there is no success
and the retry limit is reached, report the frame up to the wireless stack
as failed eventually.

HCI reset should stop all pending TX activity so forcefully flush the
queue there.

Found by Linux Verification Center (linuxtesting.org).

Signed-off-by: Fedor Pchelkin <pchelkin@ispras.ru>
Acked-by: Ping-Ke Shih <pkshih@realtek.com>
Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
Link: https://patch.msgid.link/20251104135720.321110-9-pchelkin@ispras.ru
drivers/net/wireless/realtek/rtw89/core.c
drivers/net/wireless/realtek/rtw89/core.h
drivers/net/wireless/realtek/rtw89/mac.c
drivers/net/wireless/realtek/rtw89/mac.h
drivers/net/wireless/realtek/rtw89/usb.c

index fd6624d12efeb0223ea22331d200c8fefa56d644..6059e30a0590c04d6b6bb1e1308b66098da6953d 100644 (file)
@@ -1131,6 +1131,9 @@ rtw89_core_tx_update_desc_info(struct rtw89_dev *rtwdev,
        if (addr_cam->valid && desc_info->mlo)
                upd_wlan_hdr = true;
 
+       if (rtw89_is_tx_rpt_skb(tx_req->skb))
+               rtw89_tx_rpt_init(rtwdev, tx_req);
+
        is_bmc = (is_broadcast_ether_addr(hdr->addr1) ||
                  is_multicast_ether_addr(hdr->addr1));
 
@@ -5865,6 +5868,7 @@ int rtw89_core_init(struct rtw89_dev *rtwdev)
        wiphy_work_init(&rtwdev->cancel_6ghz_probe_work, rtw89_cancel_6ghz_probe_work);
        INIT_WORK(&rtwdev->load_firmware_work, rtw89_load_firmware_work);
 
+       spin_lock_init(&rtwdev->tx_rpt.skb_lock);
        skb_queue_head_init(&rtwdev->c2h_queue);
        rtw89_core_ppdu_sts_init(rtwdev);
        rtw89_traffic_stats_init(rtwdev, &rtwdev->stats);
index 2914cc4e97d5e675178f974d34096bbc23642609..cb71bd87b193a41d7c1a72a883c227dd8742b04a 100644 (file)
@@ -3518,6 +3518,15 @@ struct rtw89_phy_rate_pattern {
 #define RTW89_TX_LIFE_TIME             0x2
 #define RTW89_TX_MACID_DROP            0x3
 
+#define RTW89_MAX_TX_RPTS              16
+#define RTW89_MAX_TX_RPTS_MASK         (RTW89_MAX_TX_RPTS - 1)
+struct rtw89_tx_rpt {
+       struct sk_buff *skbs[RTW89_MAX_TX_RPTS];
+       /* protect skbs array access/modification */
+       spinlock_t skb_lock;
+       atomic_t sn;
+};
+
 #define RTW89_TX_WAIT_WORK_TIMEOUT msecs_to_jiffies(500)
 struct rtw89_tx_wait_info {
        struct rcu_head rcu_head;
@@ -3529,6 +3538,8 @@ struct rtw89_tx_wait_info {
 
 struct rtw89_tx_skb_data {
        struct rtw89_tx_wait_info __rcu *wait;
+       u8 tx_rpt_sn;
+       u8 tx_pkt_cnt_lmt;
        u8 hci_priv[];
 };
 
@@ -3698,6 +3709,7 @@ struct rtw89_hci_info {
        u32 rpwm_addr;
        u32 cpwm_addr;
        bool paused;
+       bool tx_rpt_enabled;
 };
 
 struct rtw89_chip_ops {
@@ -6021,6 +6033,8 @@ struct rtw89_dev {
        struct list_head tx_waits;
        struct wiphy_delayed_work tx_wait_work;
 
+       struct rtw89_tx_rpt tx_rpt;
+
        struct rtw89_cam_info cam_info;
 
        struct sk_buff_head c2h_queue;
index 47048d125c01600f9ffafdd31dd0860fba170da3..0a8474002cb735d519715e82ca43727bc07bcf80 100644 (file)
@@ -5491,7 +5491,10 @@ rtw89_mac_c2h_mcc_status_rpt(struct rtw89_dev *rtwdev, struct sk_buff *c2h, u32
 static void
 rtw89_mac_c2h_tx_rpt(struct rtw89_dev *rtwdev, struct sk_buff *c2h, u32 len)
 {
+       struct rtw89_tx_rpt *tx_rpt = &rtwdev->tx_rpt;
+       struct rtw89_tx_skb_data *skb_data;
        u8 sw_define, tx_status, txcnt;
+       struct sk_buff *skb;
 
        if (rtwdev->chip->chip_id == RTL8922A) {
                const struct rtw89_c2h_mac_tx_rpt_v2 *rpt_v2;
@@ -5520,6 +5523,35 @@ rtw89_mac_c2h_tx_rpt(struct rtw89_dev *rtwdev, struct sk_buff *c2h, u32 len)
        rtw89_debug(rtwdev, RTW89_DBG_TXRX,
                    "C2H TX RPT: sn %d, tx_status %d, txcnt %d\n",
                    sw_define, tx_status, txcnt);
+
+       /* claim sw_define is not over size of tx_rpt->skbs[] */
+       static_assert(hweight32(RTW89_MAX_TX_RPTS_MASK) ==
+                     hweight32(RTW89_C2H_MAC_TX_RPT_W12_SW_DEFINE_V2) &&
+                     hweight32(RTW89_MAX_TX_RPTS_MASK) ==
+                     hweight32(RTW89_C2H_MAC_TX_RPT_W2_SW_DEFINE));
+
+       scoped_guard(spinlock_irqsave, &tx_rpt->skb_lock) {
+               skb = tx_rpt->skbs[sw_define];
+
+               /* skip if no skb (normally shouldn't happen) */
+               if (!skb) {
+                       rtw89_debug(rtwdev, RTW89_DBG_TXRX,
+                                   "C2H TX RPT: no skb found in queue\n");
+                       return;
+               }
+
+               skb_data = RTW89_TX_SKB_CB(skb);
+
+               /* skip if TX attempt has failed and retry limit has not been
+                * reached yet
+                */
+               if (tx_status != RTW89_TX_DONE &&
+                   txcnt != skb_data->tx_pkt_cnt_lmt)
+                       return;
+
+               tx_rpt->skbs[sw_define] = NULL;
+               rtw89_tx_rpt_tx_status(rtwdev, skb, tx_status);
+       }
 }
 
 static void
index dfa85ade38ce1018db0313cc379086c0a75417ba..56751dd6e99bb19744073615049899438291c712 100644 (file)
@@ -1626,4 +1626,85 @@ int rtw89_mac_scan_offload(struct rtw89_dev *rtwdev,
 
        return ret;
 }
+
+static inline
+void rtw89_tx_rpt_init(struct rtw89_dev *rtwdev,
+                      struct rtw89_core_tx_request *tx_req)
+{
+       struct rtw89_tx_rpt *tx_rpt = &rtwdev->tx_rpt;
+
+       if (!rtwdev->hci.tx_rpt_enabled)
+               return;
+
+       tx_req->desc_info.report = true;
+       /* firmware maintains a 4-bit sequence number */
+       tx_req->desc_info.sn = atomic_inc_return(&tx_rpt->sn) &
+                              RTW89_MAX_TX_RPTS_MASK;
+       tx_req->desc_info.tx_cnt_lmt_en = true;
+       tx_req->desc_info.tx_cnt_lmt = 8;
+}
+
+static inline
+bool rtw89_is_tx_rpt_skb(struct sk_buff *skb)
+{
+       struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+
+       return info->flags & IEEE80211_TX_CTL_REQ_TX_STATUS;
+}
+
+static inline
+void rtw89_tx_rpt_tx_status(struct rtw89_dev *rtwdev, struct sk_buff *skb,
+                           u8 tx_status)
+{
+       struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+
+       ieee80211_tx_info_clear_status(info);
+
+       if (tx_status == RTW89_TX_DONE)
+               info->flags |= IEEE80211_TX_STAT_ACK;
+       else
+               info->flags &= ~IEEE80211_TX_STAT_ACK;
+
+       ieee80211_tx_status_irqsafe(rtwdev->hw, skb);
+}
+
+static inline
+void rtw89_tx_rpt_skb_add(struct rtw89_dev *rtwdev, struct sk_buff *skb)
+{
+       struct rtw89_tx_rpt *tx_rpt = &rtwdev->tx_rpt;
+       struct rtw89_tx_skb_data *skb_data;
+       u8 idx;
+
+       skb_data = RTW89_TX_SKB_CB(skb);
+       idx = skb_data->tx_rpt_sn;
+
+       scoped_guard(spinlock_irqsave, &tx_rpt->skb_lock) {
+               /* if skb having the similar seq number is still in the queue,
+                * this means the queue is overflowed - it isn't normal and
+                * should indicate firmware doesn't provide TX reports in time;
+                * report the old skb as dropped, we can't do much more here
+                */
+               if (tx_rpt->skbs[idx])
+                       rtw89_tx_rpt_tx_status(rtwdev, tx_rpt->skbs[idx],
+                                              RTW89_TX_MACID_DROP);
+               tx_rpt->skbs[idx] = skb;
+       }
+}
+
+static inline
+void rtw89_tx_rpt_skbs_purge(struct rtw89_dev *rtwdev)
+{
+       struct rtw89_tx_rpt *tx_rpt = &rtwdev->tx_rpt;
+       struct sk_buff *skbs[RTW89_MAX_TX_RPTS];
+
+       scoped_guard(spinlock_irqsave, &tx_rpt->skb_lock) {
+               memcpy(skbs, tx_rpt->skbs, sizeof(tx_rpt->skbs));
+               memset(tx_rpt->skbs, 0, sizeof(tx_rpt->skbs));
+       }
+
+       for (int i = 0; i < ARRAY_SIZE(skbs); i++)
+               if (skbs[i])
+                       rtw89_tx_rpt_tx_status(rtwdev, skbs[i],
+                                              RTW89_TX_MACID_DROP);
+}
 #endif
index b7b981bac7bfbbe70712459d35676ec88b6db7b9..f54e00c3033e946e7e6e8d24f3e5baf944610875 100644 (file)
@@ -194,6 +194,15 @@ static void rtw89_usb_write_port_complete(struct urb *urb)
 
                skb_pull(skb, txdesc_size);
 
+               if (rtw89_is_tx_rpt_skb(skb)) {
+                       if (urb->status == 0)
+                               rtw89_tx_rpt_skb_add(rtwdev, skb);
+                       else
+                               rtw89_tx_rpt_tx_status(rtwdev, skb,
+                                                      RTW89_TX_MACID_DROP);
+                       continue;
+               }
+
                info = IEEE80211_SKB_CB(skb);
                ieee80211_tx_info_clear_status(info);
 
@@ -358,6 +367,7 @@ static int rtw89_usb_ops_tx_write(struct rtw89_dev *rtwdev,
 {
        struct rtw89_tx_desc_info *desc_info = &tx_req->desc_info;
        struct rtw89_usb *rtwusb = rtw89_usb_priv(rtwdev);
+       struct rtw89_tx_skb_data *skb_data;
        struct sk_buff *skb = tx_req->skb;
        struct rtw89_txwd_body *txdesc;
        u32 txdesc_size;
@@ -384,6 +394,12 @@ static int rtw89_usb_ops_tx_write(struct rtw89_dev *rtwdev,
 
        le32p_replace_bits(&txdesc->dword0, 1, RTW89_TXWD_BODY0_STF_MODE);
 
+       skb_data = RTW89_TX_SKB_CB(skb);
+       if (tx_req->desc_info.sn)
+               skb_data->tx_rpt_sn = tx_req->desc_info.sn;
+       if (tx_req->desc_info.tx_cnt_lmt)
+               skb_data->tx_pkt_cnt_lmt = tx_req->desc_info.tx_cnt_lmt;
+
        skb_queue_tail(&rtwusb->tx_queue[desc_info->ch_dma], skb);
 
        return 0;
@@ -672,6 +688,7 @@ static void rtw89_usb_ops_reset(struct rtw89_dev *rtwdev)
        struct rtw89_usb *rtwusb = rtw89_usb_priv(rtwdev);
 
        rtw89_usb_cancel_tx_bufs(rtwusb);
+       rtw89_tx_rpt_skbs_purge(rtwdev);
 }
 
 static int rtw89_usb_ops_start(struct rtw89_dev *rtwdev)
@@ -962,6 +979,7 @@ int rtw89_usb_probe(struct usb_interface *intf,
 
        rtwdev->hci.ops = &rtw89_usb_ops;
        rtwdev->hci.type = RTW89_HCI_TYPE_USB;
+       rtwdev->hci.tx_rpt_enabled = true;
 
        ret = rtw89_usb_intf_init(rtwdev, intf);
        if (ret) {