]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
wifi: rtw89: Drop malformed AMPDU frames with abnormal PN
authorPo-Hao Huang <phhuang@realtek.com>
Tue, 10 Mar 2026 08:01:44 +0000 (16:01 +0800)
committerPing-Ke Shih <pkshih@realtek.com>
Mon, 16 Mar 2026 06:21:16 +0000 (14:21 +0800)
Fix connection issue caused by AMPDU frames with abnormal PN patterns
(out-of-order packets with correct MPDU sequence numbers but paired
with abnormal PN values, which is next PN of previous in-order packet).
This is causing packet drops, low throughput and disconnections. It is
observed in fields with some specific AP firmwares. Do this workaround for
better interoperability since some APs could never receive a proper FW
update.

Signed-off-by: Po-Hao Huang <phhuang@realtek.com>
Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
Link: https://patch.msgid.link/20260310080146.31113-12-pkshih@realtek.com
drivers/net/wireless/realtek/rtw89/core.c
drivers/net/wireless/realtek/rtw89/core.h
drivers/net/wireless/realtek/rtw89/mac80211.c
drivers/net/wireless/realtek/rtw89/util.h
drivers/net/wireless/realtek/rtw89/wow.c
drivers/net/wireless/realtek/rtw89/wow.h

index 18dbf3664f0a1e193ea1c4821a65d214089d488d..9d3f651798fffe302eef8e6af25aa1492e14dca0 100644 (file)
@@ -3272,6 +3272,114 @@ out:
        rcu_read_unlock();
 }
 
+static void __rtw89_core_tid_rx_stats_reset(struct rtw89_tid_stats *tid_stats)
+{
+       tid_stats->last_pn = -1LL;
+       tid_stats->last_sn = IEEE80211_SN_MASK;
+}
+
+void rtw89_core_tid_rx_stats_ctrl(struct rtw89_dev *rtwdev, struct rtw89_sta *rtwsta,
+                                 struct ieee80211_ampdu_params *params, bool enable)
+{
+       struct rtw89_tid_stats *tid_stats;
+       u16 tid = params->tid;
+
+       tid_stats = &rtwsta->tid_rx_stats[tid];
+
+       if (enable) {
+               __rtw89_core_tid_rx_stats_reset(tid_stats);
+               tid_stats->started = true;
+       } else {
+               tid_stats->started = false;
+       }
+}
+
+void rtw89_core_tid_rx_stats_reset(struct rtw89_dev *rtwdev)
+{
+       struct rtw89_tid_stats *tid_stats;
+       struct ieee80211_sta *sta;
+       struct rtw89_sta *rtwsta;
+       u16 tid;
+
+       for_each_station(sta, rtwdev->hw) {
+               rtwsta = sta_to_rtwsta(sta);
+
+               for (tid = 0; tid < IEEE80211_NUM_TIDS; tid++) {
+                       tid_stats = &rtwsta->tid_rx_stats[tid];
+
+                       if (!tid_stats->started)
+                               continue;
+
+                       __rtw89_core_tid_rx_stats_reset(tid_stats);
+               }
+       }
+}
+
+static bool rtw89_core_skb_pn_valid(struct rtw89_dev *rtwdev,
+                                   struct rtw89_rx_desc_info *desc_info,
+                                   struct sk_buff *skb)
+{
+       struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
+       const struct rtw89_chip_info *chip = rtwdev->chip;
+       struct rtw89_sta_link *rtwsta_link;
+       struct rtw89_tid_stats *tid_stats;
+       struct rtw89_sta *rtwsta;
+       u8 tid, *ccmp_hdr_ptr;
+       s64 pn, last_pn;
+       u16 mpdu_sn;
+       int hdrlen;
+
+       if (chip->chip_gen != RTW89_CHIP_AX)
+               return true;
+
+       if (!ieee80211_is_data_qos(hdr->frame_control))
+               return true;
+
+       if (!desc_info->hw_dec || !desc_info->addr1_match)
+               return true;
+
+       guard(rcu)();
+
+       rtwsta_link = rtw89_assoc_link_rcu_dereference(rtwdev, desc_info->mac_id);
+       if (!rtwsta_link)
+               return true;
+
+       rtwsta = rtwsta_link->rtwsta;
+       tid = ieee80211_get_tid(hdr);
+       tid_stats = &rtwsta->tid_rx_stats[tid];
+
+       if (!tid_stats->started)
+               return true;
+
+       switch (desc_info->sec_type) {
+       case RTW89_SEC_KEY_TYPE_CCMP128:
+       case RTW89_SEC_KEY_TYPE_CCMP256:
+       case RTW89_SEC_KEY_TYPE_GCMP128:
+       case RTW89_SEC_KEY_TYPE_GCMP256:
+               mpdu_sn = ieee80211_get_sn(hdr);
+               hdrlen = ieee80211_hdrlen(hdr->frame_control);
+               ccmp_hdr_ptr = skb->data + hdrlen;
+               ccmp_hdr2pn(&pn, ccmp_hdr_ptr);
+               last_pn = tid_stats->last_pn;
+
+               if (pn > last_pn) {
+                       if (ieee80211_sn_less(mpdu_sn, tid_stats->last_sn)) {
+                               dev_kfree_skb_any(skb);
+
+                               return false;
+                       }
+
+                       tid_stats->last_sn = mpdu_sn;
+                       tid_stats->last_pn = pn;
+               }
+               break;
+       default:
+               break;
+       }
+
+       return true;
+}
+
 static void rtw89_core_rx_to_mac80211(struct rtw89_dev *rtwdev,
                                      struct rtw89_rx_phy_ppdu *phy_ppdu,
                                      struct rtw89_rx_desc_info *desc_info,
@@ -3421,6 +3529,7 @@ void rtw89_core_query_rxdesc(struct rtw89_dev *rtwdev,
        desc_info->sec_cam_id = le32_get_bits(rxd_l->dword5, AX_RXD_SEC_CAM_IDX_MASK);
        desc_info->mac_id = le32_get_bits(rxd_l->dword5, AX_RXD_MAC_ID_MASK);
        desc_info->rx_pl_id = le32_get_bits(rxd_l->dword5, AX_RXD_RX_PL_ID_MASK);
+       desc_info->sec_type = le32_get_bits(rxd_l->dword7, AX_RXD_SEC_TYPE_MASK);
 }
 EXPORT_SYMBOL(rtw89_core_query_rxdesc);
 
@@ -3450,6 +3559,7 @@ void rtw89_core_query_rxdesc_v2(struct rtw89_dev *rtwdev,
        desc_info->mac_id = le32_get_bits(rxd_s->dword2, BE_RXD_MAC_ID_MASK);
        desc_info->addr_cam_valid = le32_get_bits(rxd_s->dword2, BE_RXD_ADDR_CAM_VLD);
 
+       desc_info->sec_type = le32_get_bits(rxd_s->dword3, BE_RXD_SEC_TYPE_MASK);
        desc_info->icv_err = le32_get_bits(rxd_s->dword3, BE_RXD_ICV_ERR);
        desc_info->crc32_err = le32_get_bits(rxd_s->dword3, BE_RXD_CRC32_ERR);
        desc_info->hw_dec = le32_get_bits(rxd_s->dword3, BE_RXD_HW_DEC);
@@ -3523,6 +3633,7 @@ void rtw89_core_query_rxdesc_v3(struct rtw89_dev *rtwdev,
        desc_info->mac_id = le32_get_bits(rxd_s->dword2, BE_RXD_MAC_ID_V1);
        desc_info->addr_cam_valid = le32_get_bits(rxd_s->dword2, BE_RXD_ADDR_CAM_VLD);
 
+       desc_info->sec_type = le32_get_bits(rxd_s->dword3, BE_RXD_SEC_TYPE_MASK);
        desc_info->icv_err = le32_get_bits(rxd_s->dword3, BE_RXD_ICV_ERR);
        desc_info->crc32_err = le32_get_bits(rxd_s->dword3, BE_RXD_CRC32_ERR);
        desc_info->hw_dec = le32_get_bits(rxd_s->dword3, BE_RXD_HW_DEC);
@@ -3802,6 +3913,10 @@ void rtw89_core_rx(struct rtw89_dev *rtwdev,
        memset(rx_status, 0, sizeof(*rx_status));
        rtw89_core_update_rx_status(rtwdev, skb, desc_info, rx_status);
        rtw89_core_rx_pkt_hdl(rtwdev, skb, desc_info);
+
+       if (!rtw89_core_skb_pn_valid(rtwdev, desc_info, skb))
+               return;
+
        if (desc_info->long_rxdesc &&
            BIT(desc_info->frame_type) & PPDU_FILTER_BITMAP)
                skb_queue_tail(&ppdu_sts->rx_queue[band], skb);
index ce04ecaa3a5e5ea57da82d3298f0b930fbe86c7c..94e4faf70e12fe3a5078fad30e9e2f1bde7e05d9 100644 (file)
@@ -1126,6 +1126,7 @@ struct rtw89_rx_desc_info {
        bool addr_cam_valid;
        u8 addr_cam_id;
        u8 sec_cam_id;
+       u8 sec_type;
        u8 mac_id;
        u16 offset;
        u16 rxd_len;
@@ -6153,6 +6154,12 @@ struct rtw89_beacon_track_info {
        u32 tbtt_diff_th;
 };
 
+struct rtw89_tid_stats {
+       s64 last_pn;
+       u16 last_sn;
+       bool started;
+};
+
 struct rtw89_dev {
        struct ieee80211_hw *hw;
        struct device *dev;
@@ -6359,6 +6366,7 @@ struct rtw89_sta {
        struct sk_buff_head roc_queue;
 
        struct rtw89_ampdu_params ampdu_params[IEEE80211_NUM_TIDS];
+       struct rtw89_tid_stats tid_rx_stats[IEEE80211_NUM_TIDS];
        DECLARE_BITMAP(ampdu_map, IEEE80211_NUM_TIDS);
 
        DECLARE_BITMAP(pairwise_sec_cam_map, RTW89_MAX_SEC_CAM_NUM);
@@ -7769,6 +7777,9 @@ int rtw89_core_sta_link_remove(struct rtw89_dev *rtwdev,
 void rtw89_core_set_tid_config(struct rtw89_dev *rtwdev,
                               struct ieee80211_sta *sta,
                               struct cfg80211_tid_config *tid_config);
+void rtw89_core_tid_rx_stats_ctrl(struct rtw89_dev *rtwdev, struct rtw89_sta *rtwsta,
+                                 struct ieee80211_ampdu_params *params, bool enable);
+void rtw89_core_tid_rx_stats_reset(struct rtw89_dev *rtwdev);
 void rtw89_core_rfkill_poll(struct rtw89_dev *rtwdev, bool force);
 void rtw89_check_quirks(struct rtw89_dev *rtwdev, const struct dmi_system_id *quirks);
 int rtw89_core_init(struct rtw89_dev *rtwdev);
index cd8e2c8de8888e4e82af21e0ebd4dbc8c4244f27..501c3af1da01a9c3cd19f037361bc4b0230435e6 100644 (file)
@@ -964,6 +964,7 @@ static int rtw89_ops_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
                        rtw89_err(rtwdev, "failed to add key to sec cam\n");
                        return ret;
                }
+               rtw89_core_tid_rx_stats_reset(rtwdev);
                break;
        case DISABLE_KEY:
                flush_work(&rtwdev->txq_work);
@@ -1018,9 +1019,11 @@ static int rtw89_ops_ampdu_action(struct ieee80211_hw *hw,
                rtw89_phy_ra_recalc_agg_limit(rtwdev);
                break;
        case IEEE80211_AMPDU_RX_START:
+               rtw89_core_tid_rx_stats_ctrl(rtwdev, rtwsta, params, true);
                rtw89_chip_h2c_ba_cam(rtwdev, rtwsta, true, params);
                break;
        case IEEE80211_AMPDU_RX_STOP:
+               rtw89_core_tid_rx_stats_ctrl(rtwdev, rtwsta, params, false);
                rtw89_chip_h2c_ba_cam(rtwdev, rtwsta, false, params);
                break;
        default:
index bd08495301e45bcff226417c921e2d9cd1cf8f08..c16e7a7f8bc922f3de3e45e45c03876b0195dd7d 100644 (file)
@@ -6,6 +6,13 @@
 
 #include "core.h"
 
+#define RTW89_KEY_PN_0 GENMASK_ULL(7, 0)
+#define RTW89_KEY_PN_1 GENMASK_ULL(15, 8)
+#define RTW89_KEY_PN_2 GENMASK_ULL(23, 16)
+#define RTW89_KEY_PN_3 GENMASK_ULL(31, 24)
+#define RTW89_KEY_PN_4 GENMASK_ULL(39, 32)
+#define RTW89_KEY_PN_5 GENMASK_ULL(47, 40)
+
 #define rtw89_iterate_vifs_bh(rtwdev, iterator, data)                          \
        ieee80211_iterate_active_interfaces_atomic((rtwdev)->hw,               \
                        IEEE80211_IFACE_ITER_NORMAL, iterator, data)
@@ -73,6 +80,16 @@ static inline void ether_addr_copy_mask(u8 *dst, const u8 *src, u8 mask)
        }
 }
 
+static inline void ccmp_hdr2pn(s64 *pn, const u8 *hdr)
+{
+       *pn = u64_encode_bits(hdr[0], RTW89_KEY_PN_0) |
+             u64_encode_bits(hdr[1], RTW89_KEY_PN_1) |
+             u64_encode_bits(hdr[4], RTW89_KEY_PN_2) |
+             u64_encode_bits(hdr[5], RTW89_KEY_PN_3) |
+             u64_encode_bits(hdr[6], RTW89_KEY_PN_4) |
+             u64_encode_bits(hdr[7], RTW89_KEY_PN_5);
+}
+
 s32 rtw89_linear_to_db_quarter(u64 val);
 s32 rtw89_linear_to_db(u64 val);
 u64 rtw89_db_quarter_to_linear(s32 db);
index 368e08826f1e1b99f33c54e3dc09c7071dce7c62..8dadd8df4fc65b03273453f321f2ffe0b0bbfbf4 100644 (file)
@@ -1741,6 +1741,8 @@ static int rtw89_wow_disable(struct rtw89_dev *rtwdev)
 
        rtw89_wow_leave_ps(rtwdev, false);
 
+       rtw89_core_tid_rx_stats_reset(rtwdev);
+
        ret = rtw89_wow_fw_stop(rtwdev);
        if (ret) {
                rtw89_err(rtwdev, "wow: failed to swap to normal fw\n");
index 71e07f482174f1801f959545e7a8cc0b93a81fd0..d7e67632efebbcd25435de6f127d3c967a6dfe4c 100644 (file)
@@ -8,13 +8,6 @@
 #define RTW89_KEY_TKIP_PN_IV16 GENMASK_ULL(15, 0)
 #define RTW89_KEY_TKIP_PN_IV32 GENMASK_ULL(47, 16)
 
-#define RTW89_KEY_PN_0 GENMASK_ULL(7, 0)
-#define RTW89_KEY_PN_1 GENMASK_ULL(15, 8)
-#define RTW89_KEY_PN_2 GENMASK_ULL(23, 16)
-#define RTW89_KEY_PN_3 GENMASK_ULL(31, 24)
-#define RTW89_KEY_PN_4 GENMASK_ULL(39, 32)
-#define RTW89_KEY_PN_5 GENMASK_ULL(47, 40)
-
 #define RTW89_IGTK_IPN_0 GENMASK_ULL(7, 0)
 #define RTW89_IGTK_IPN_1 GENMASK_ULL(15, 8)
 #define RTW89_IGTK_IPN_2 GENMASK_ULL(23, 16)