| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/08/24 20:41 | flow | patch-triage |
0mArgs:null Results: {
"EnableConfigs": [
"MAC80211",
"MAC80211_HWSIM",
"CFG80211"
],
"FocusSymbols": [
"ieee80211_rx_napi",
"ieee80211_rx_list",
"__ieee80211_rx_handle_packet",
"__ieee80211_rx_handle_8023",
"ieee80211_prepare_and_rx_handle",
"ieee80211_rx_handlers",
"nl80211_tx_mgmt",
"nl80211_send_mgmt"
],
"KMSANReasoning": "The patch refactors how `link_id` and `link_valid` are passed from wireless drivers to mac80211. Instead of setting `link_valid` and `link_id` in `struct ieee80211_rx_status` (which is stored in `skb-\u003ecb`), drivers now resolve the `link_sta` and pass it directly to `ieee80211_rx_napi()` and `ieee80211_rx_list()`. The patch also adds a `no_sta` boolean to `struct cfg80211_mgmt_tx_params` and `struct cfg80211_rx_info`, which is exposed to userspace via a new netlink flag attribute `NL80211_ATTR_FRAME_CMD_NO_STA`.\n\nSince `NL80211_ATTR_FRAME_CMD_NO_STA` is an `NLA_FLAG`, it carries no payload and does not leak any uninitialized memory to userspace. The changes to `struct ieee80211_rx_status` only affect internal kernel bitfields and do not expose uninitialized memory. Any potential bugs introduced by this patch would be logic errors, NULL pointer dereferences, or out-of-bounds accesses (e.g., incorrect link ID indexing), which are well within the capabilities of standard KASAN and other bug detectors. There is no risk of uninitialized memory usage or info-leaks that would require a dedicated KMSAN fuzzing session.",
"NeedsKMSAN": false,
"Reasoning": "The patch modifies the core mac80211 RX path, changing the signature of ieee80211_rx_napi and ieee80211_rx_list to take a link_sta instead of a sta. It also modifies how link_id is handled in RX status. These changes affect the core 802.11 stack and virtual drivers like mac80211_hwsim, making them reachable and worth fuzzing.",
"WorthFuzzing": true
} |
| 1/1 | 2026/08/24 20:41 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit d20e704263f8e8096339e5e49f4820aff11946db\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Mon Aug 24 20:41:33 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/drivers/net/wireless/ath/ath11k/dp_rx.c b/drivers/net/wireless/ath/ath11k/dp_rx.c\nindex 33425707c0842..fbb3d18f55ddd 100644\n--- a/drivers/net/wireless/ath/ath11k/dp_rx.c\n+++ b/drivers/net/wireless/ath/ath11k/dp_rx.c\n@@ -2500,7 +2500,7 @@ static void ath11k_dp_rx_deliver_msdu(struct ath11k *ar, struct napi_struct *nap\n \t !(is_mcbc \u0026\u0026 rx_status-\u003eflag \u0026 RX_FLAG_DECRYPTED))\n \t\trx_status-\u003eflag |= RX_FLAG_8023;\n \n-\tieee80211_rx_napi(ar-\u003ehw, pubsta, msdu, napi);\n+\tieee80211_rx_napi(ar-\u003ehw, pubsta ? \u0026pubsta-\u003edeflink : NULL, msdu, napi);\n }\n \n static bool ath11k_dp_rx_check_nwifi_hdr_len_valid(struct ath11k_base *ab,\ndiff --git a/drivers/net/wireless/ath/ath12k/dp_mon.c b/drivers/net/wireless/ath/ath12k/dp_mon.c\nindex 7d5be77b081f8..1faf1896d0ab1 100644\n--- a/drivers/net/wireless/ath/ath12k/dp_mon.c\n+++ b/drivers/net/wireless/ath/ath12k/dp_mon.c\n@@ -503,8 +503,6 @@ void ath12k_dp_mon_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev,\n \tstruct ieee80211_rx_status *rx_status;\n \tstruct ieee80211_radiotap_he *he = NULL;\n \n-\tstatus-\u003elink_valid = 0;\n-\n \tif ((status-\u003eencoding == RX_ENC_HE) \u0026\u0026 !(status-\u003eflag \u0026 RX_FLAG_RADIOTAP_HE) \u0026\u0026\n \t !(status-\u003eflag \u0026 RX_FLAG_SKIP_MONITOR)) {\n \t\the = skb_push(msdu, sizeof(known));\ndiff --git a/drivers/net/wireless/ath/ath12k/dp_rx.c b/drivers/net/wireless/ath/ath12k/dp_rx.c\nindex 8fa0e90b45316..41ac0a4072aeb 100644\n--- a/drivers/net/wireless/ath/ath12k/dp_rx.c\n+++ b/drivers/net/wireless/ath/ath12k/dp_rx.c\n@@ -1375,6 +1375,7 @@ void ath12k_dp_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev, struct napi_struc\n \tstruct ath12k_dp *dp = dp_pdev-\u003edp;\n \tstruct ieee80211_rx_status *rx_status;\n \tstruct ieee80211_sta *pubsta;\n+\tstruct ieee80211_link_sta *link_pubsta = NULL;\n \tstruct ath12k_dp_peer *peer;\n \tstruct ath12k_skb_rxcb *rxcb = ATH12K_SKB_RXCB(msdu);\n \tstruct ieee80211_rx_status *status = rx_info-\u003erx_status;\n@@ -1384,9 +1385,19 @@ void ath12k_dp_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev, struct napi_struc\n \n \tpubsta = peer ? peer-\u003esta : NULL;\n \n-\tstatus-\u003elink_valid = 0;\n-\tif (pubsta \u0026\u0026 pubsta-\u003evalid_links)\n-\t\tath12k_hw_set_rx_link_id(dp-\u003ehw_params, peer, rxcb, status);\n+\tif (pubsta) {\n+\t\tif (pubsta-\u003evalid_links) {\n+\t\t\tint link_id =\n+\t\t\t\tath12k_hw_get_rx_link_id(dp-\u003ehw_params, peer,\n+\t\t\t\t\t\t\t rxcb, status);\n+\n+\t\t\tif (link_id \u003e= 0)\n+\t\t\t\tlink_pubsta =\n+\t\t\t\t\trcu_dereference(pubsta-\u003elink[link_id]);\n+\t\t} else {\n+\t\t\tlink_pubsta = \u0026pubsta-\u003edeflink;\n+\t\t}\n+\t}\n \n \tath12k_dbg(dp-\u003eab, ATH12K_DBG_DATA,\n \t\t \"rx skb %p len %u peer %pM %d %s sn %u %s%s%s%s%s%s%s%s%s%s rate_idx %u vht_nss %u freq %u band %u flag 0x%x fcs-err %i mic-err %i amsdu-more %i\\n\",\n@@ -1422,7 +1433,8 @@ void ath12k_dp_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev, struct napi_struc\n \n \t/* TODO: trace rx packet */\n \n-\tieee80211_rx_napi(ath12k_pdev_dp_to_hw(dp_pdev), pubsta, msdu, napi);\n+\tieee80211_rx_napi(ath12k_pdev_dp_to_hw(dp_pdev), link_pubsta, msdu,\n+\t\t\t napi);\n }\n EXPORT_SYMBOL(ath12k_dp_rx_deliver_msdu);\n \ndiff --git a/drivers/net/wireless/ath/ath12k/hw.h b/drivers/net/wireless/ath/ath12k/hw.h\nindex 3ed38f8f2b48a..7a2345f883d70 100644\n--- a/drivers/net/wireless/ath/ath12k/hw.h\n+++ b/drivers/net/wireless/ath/ath12k/hw.h\n@@ -247,9 +247,9 @@ struct ath12k_hw_ops {\n \tbool (*dp_srng_is_tx_comp_ring)(int ring_num);\n \tbool (*is_frame_link_agnostic)(struct ath12k_link_vif *arvif,\n \t\t\t\t struct ieee80211_mgmt *mgmt);\n-\tvoid (*set_rx_link_id)(struct ath12k_dp_peer *dp_peer,\n-\t\t\t struct ath12k_skb_rxcb *rxcb,\n-\t\t\t struct ieee80211_rx_status *status);\n+\tint (*get_rx_link_id)(struct ath12k_dp_peer *dp_peer,\n+\t\t\t struct ath12k_skb_rxcb *rxcb,\n+\t\t\t struct ieee80211_rx_status *status);\n };\n \n static inline\n@@ -280,13 +280,15 @@ static inline int ath12k_hw_mac_id_to_srng_id(const struct ath12k_hw_params *hw,\n \treturn 0;\n }\n \n-static inline void ath12k_hw_set_rx_link_id(const struct ath12k_hw_params *hw,\n-\t\t\t\t\t struct ath12k_dp_peer *dp_peer,\n-\t\t\t\t\t struct ath12k_skb_rxcb *rxcb,\n-\t\t\t\t\t struct ieee80211_rx_status *status)\n+static inline int ath12k_hw_get_rx_link_id(const struct ath12k_hw_params *hw,\n+\t\t\t\t\t struct ath12k_dp_peer *dp_peer,\n+\t\t\t\t\t struct ath12k_skb_rxcb *rxcb,\n+\t\t\t\t\t struct ieee80211_rx_status *status)\n {\n-\tif (hw-\u003ehw_ops-\u003eset_rx_link_id)\n-\t\thw-\u003ehw_ops-\u003eset_rx_link_id(dp_peer, rxcb, status);\n+\tif (hw-\u003ehw_ops-\u003eget_rx_link_id)\n+\t\treturn hw-\u003ehw_ops-\u003eget_rx_link_id(dp_peer, rxcb, status);\n+\n+\treturn -1;\n }\n \n struct ath12k_fw_ie {\ndiff --git a/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.c b/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.c\nindex 95d87dd67872f..8b722ee0ad8fa 100644\n--- a/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.c\n+++ b/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.c\n@@ -2317,17 +2317,16 @@ ath12k_wifi7_dp_rxdesc_mpdu_valid(struct ath12k_base *ab,\n \treturn tlv_tag == HAL_RX_MPDU_START;\n }\n \n-void\n-ath12k_wifi7_dp_rx_set_link_id_qcn9274(struct ath12k_dp_peer *dp_peer,\n+int\n+ath12k_wifi7_dp_rx_get_link_id_qcn9274(struct ath12k_dp_peer *dp_peer,\n \t\t\t\t struct ath12k_skb_rxcb *rxcb,\n \t\t\t\t struct ieee80211_rx_status *status)\n {\n-\tstatus-\u003elink_valid = 1;\n-\tstatus-\u003elink_id = dp_peer-\u003ehw_links[rxcb-\u003ehw_link_id];\n+\treturn dp_peer-\u003ehw_links[rxcb-\u003ehw_link_id];\n }\n \n-void\n-ath12k_wifi7_dp_rx_set_link_id_wcn7850(struct ath12k_dp_peer *dp_peer,\n+int\n+ath12k_wifi7_dp_rx_get_link_id_wcn7850(struct ath12k_dp_peer *dp_peer,\n \t\t\t\t struct ath12k_skb_rxcb *rxcb,\n \t\t\t\t struct ieee80211_rx_status *status)\n {\n@@ -2341,10 +2340,9 @@ ath12k_wifi7_dp_rx_set_link_id_wcn7850(struct ath12k_dp_peer *dp_peer,\n \tlinks_map = READ_ONCE(dp_peer-\u003elink_peers_map);\n \tfor_each_set_bit(i, \u0026links_map, ATH12K_NUM_MAX_LINKS) {\n \t\tlink_peer = rcu_dereference(dp_peer-\u003elink_peers[i]);\n-\t\tif (link_peer \u0026\u0026 link_peer-\u003epeer_id == rxcb-\u003epeer_id) {\n-\t\t\tstatus-\u003elink_valid = 1;\n-\t\t\tstatus-\u003elink_id = link_peer-\u003elink_id;\n-\t\t\treturn;\n-\t\t}\n+\t\tif (link_peer \u0026\u0026 link_peer-\u003epeer_id == rxcb-\u003epeer_id)\n+\t\t\treturn link_peer-\u003elink_id;\n \t}\n+\n+\treturn -1;\n }\ndiff --git a/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.h b/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.h\nindex 1d3a4788a2dd9..d68489ff7b037 100644\n--- a/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.h\n+++ b/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.h\n@@ -57,10 +57,10 @@ ath12k_wifi7_dp_rxdesc_mpdu_valid(struct ath12k_base *ab,\n \t\t\t\t struct hal_rx_desc *rx_desc);\n int ath12k_wifi7_dp_rx_tid_delete_handler(struct ath12k_base *ab,\n \t\t\t\t\t struct ath12k_dp_rx_tid_rxq *rx_tid);\n-void ath12k_wifi7_dp_rx_set_link_id_qcn9274(struct ath12k_dp_peer *dp_peer,\n-\t\t\t\t\t struct ath12k_skb_rxcb *rxcb,\n-\t\t\t\t\t struct ieee80211_rx_status *status);\n-void ath12k_wifi7_dp_rx_set_link_id_wcn7850(struct ath12k_dp_peer *dp_peer,\n-\t\t\t\t\t struct ath12k_skb_rxcb *rxcb,\n-\t\t\t\t\t struct ieee80211_rx_status *status);\n+int ath12k_wifi7_dp_rx_get_link_id_qcn9274(struct ath12k_dp_peer *dp_peer,\n+\t\t\t\t\t struct ath12k_skb_rxcb *rxcb,\n+\t\t\t\t\t struct ieee80211_rx_status *status);\n+int ath12k_wifi7_dp_rx_get_link_id_wcn7850(struct ath12k_dp_peer *dp_peer,\n+\t\t\t\t\t struct ath12k_skb_rxcb *rxcb,\n+\t\t\t\t\t struct ieee80211_rx_status *status);\n #endif\ndiff --git a/drivers/net/wireless/ath/ath12k/wifi7/hw.c b/drivers/net/wireless/ath/ath12k/wifi7/hw.c\nindex d890801d58223..a6ac4a40f7cc2 100644\n--- a/drivers/net/wireless/ath/ath12k/wifi7/hw.c\n+++ b/drivers/net/wireless/ath/ath12k/wifi7/hw.c\n@@ -158,7 +158,7 @@ static const struct ath12k_hw_ops qcn9274_ops = {\n \t.get_ring_selector = ath12k_wifi7_hw_get_ring_selector_qcn9274,\n \t.dp_srng_is_tx_comp_ring = ath12k_wifi7_dp_srng_is_comp_ring_qcn9274,\n \t.is_frame_link_agnostic = ath12k_wifi7_is_frame_link_agnostic_qcn9274,\n-\t.set_rx_link_id = ath12k_wifi7_dp_rx_set_link_id_qcn9274,\n+\t.get_rx_link_id = ath12k_wifi7_dp_rx_get_link_id_qcn9274,\n };\n \n static const struct ath12k_hw_ops wcn7850_ops = {\n@@ -169,7 +169,7 @@ static const struct ath12k_hw_ops wcn7850_ops = {\n \t.get_ring_selector = ath12k_wifi7_hw_get_ring_selector_wcn7850,\n \t.dp_srng_is_tx_comp_ring = ath12k_wifi7_dp_srng_is_comp_ring_wcn7850,\n \t.is_frame_link_agnostic = ath12k_wifi7_is_frame_link_agnostic_wcn7850,\n-\t.set_rx_link_id = ath12k_wifi7_dp_rx_set_link_id_wcn7850,\n+\t.get_rx_link_id = ath12k_wifi7_dp_rx_get_link_id_wcn7850,\n };\n \n static const struct ath12k_hw_ops qcc2072_ops = {\n@@ -180,7 +180,7 @@ static const struct ath12k_hw_ops qcc2072_ops = {\n \t.get_ring_selector = ath12k_wifi7_hw_get_ring_selector_wcn7850,\n \t.dp_srng_is_tx_comp_ring = ath12k_wifi7_dp_srng_is_comp_ring_wcn7850,\n \t.is_frame_link_agnostic = ath12k_wifi7_is_frame_link_agnostic_wcn7850,\n-\t.set_rx_link_id = ath12k_wifi7_dp_rx_set_link_id_wcn7850,\n+\t.get_rx_link_id = ath12k_wifi7_dp_rx_get_link_id_wcn7850,\n };\n \n #define ATH12K_TX_RING_MASK_0 0x1\ndiff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c\nindex d5160af60e00d..7e3655966f02d 100644\n--- a/drivers/net/wireless/ath/ath12k/wmi.c\n+++ b/drivers/net/wireless/ath/ath12k/wmi.c\n@@ -7271,7 +7271,6 @@ static void ath12k_mgmt_rx_event(struct ath12k_base *ab, struct sk_buff *skb)\n \tstruct ath12k_wmi_mgmt_rx_arg rx_ev = {};\n \tstruct ath12k *ar;\n \tstruct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);\n-\tstruct ieee80211_sta *pubsta = NULL;\n \tstruct ath12k_dp_link_peer *peer;\n \tstruct ieee80211_hdr *hdr;\n \tbool is_4addr_null_pkt;\n@@ -7373,11 +7372,6 @@ static void ath12k_mgmt_rx_event(struct ath12k_base *ab, struct sk_buff *skb)\n \t\t\tdev_kfree_skb(skb);\n \t\t\tgoto exit;\n \t\t}\n-\t\tpubsta = peer-\u003esta;\n-\t\tif (pubsta \u0026\u0026 pubsta-\u003evalid_links) {\n-\t\t\tstatus-\u003elink_valid = 1;\n-\t\t\tstatus-\u003elink_id = peer-\u003elink_id;\n-\t\t}\n \t\tspin_unlock_bh(\u0026dp-\u003edp_lock);\n \t\tgoto send_rx;\n \t}\ndiff --git a/drivers/net/wireless/intel/iwlwifi/mld/agg.c b/drivers/net/wireless/intel/iwlwifi/mld/agg.c\nindex 96102d2af2cf9..c45c47337509e 100644\n--- a/drivers/net/wireless/intel/iwlwifi/mld/agg.c\n+++ b/drivers/net/wireless/intel/iwlwifi/mld/agg.c\n@@ -7,7 +7,8 @@\n #include \"hcmd.h\"\n \n static void\n-iwl_mld_reorder_release_frames(struct iwl_mld *mld, struct ieee80211_sta *sta,\n+iwl_mld_reorder_release_frames(struct iwl_mld *mld,\n+\t\t\t struct ieee80211_link_sta *link_sta,\n \t\t\t struct napi_struct *napi,\n \t\t\t struct iwl_mld_baid_data *baid_data,\n \t\t\t struct iwl_mld_reorder_buffer *reorder_buf,\n@@ -32,7 +33,7 @@ iwl_mld_reorder_release_frames(struct iwl_mld *mld, struct ieee80211_sta *sta,\n \t\twhile ((skb = __skb_dequeue(skb_list))) {\n \t\t\tiwl_mld_pass_packet_to_mac80211(mld, napi, skb,\n \t\t\t\t\t\t\treorder_buf-\u003equeue,\n-\t\t\t\t\t\t\tsta);\n+\t\t\t\t\t\t\tlink_sta);\n \t\t\treorder_buf-\u003enum_stored--;\n \t\t}\n \t}\n@@ -74,7 +75,7 @@ static void iwl_mld_release_frames_from_notif(struct iwl_mld *mld,\n \n \treorder_buf = \u0026ba_data-\u003ereorder_buf[queue];\n \n-\tiwl_mld_reorder_release_frames(mld, link_sta-\u003esta, napi, ba_data,\n+\tiwl_mld_reorder_release_frames(mld, link_sta, napi, ba_data,\n \t\t\t\t reorder_buf, nssn);\n out_unlock:\n \trcu_read_unlock();\n@@ -180,7 +181,7 @@ void iwl_mld_del_ba(struct iwl_mld *mld, int queue,\n \treorder_buf = \u0026ba_data-\u003ereorder_buf[queue];\n \n \t/* release all frames that are in the reorder buffer to the stack */\n-\tiwl_mld_reorder_release_frames(mld, link_sta-\u003esta, NULL,\n+\tiwl_mld_reorder_release_frames(mld, link_sta, NULL,\n \t\t\t\t ba_data, reorder_buf,\n \t\t\t\t ieee80211_sn_add(reorder_buf-\u003ehead_sn,\n \t\t\t\t\t\t\tba_data-\u003ebuf_size));\n@@ -193,7 +194,7 @@ void iwl_mld_del_ba(struct iwl_mld *mld, int queue,\n */\n enum iwl_mld_reorder_result\n iwl_mld_reorder(struct iwl_mld *mld, struct napi_struct *napi,\n-\t\tint queue, struct ieee80211_sta *sta,\n+\t\tint queue, struct ieee80211_link_sta *link_sta,\n \t\tstruct sk_buff *skb, struct iwl_rx_mpdu_desc *desc)\n {\n \tstruct ieee80211_hdr *hdr = (void *)skb_mac_header(skb);\n@@ -228,12 +229,12 @@ iwl_mld_reorder(struct iwl_mld *mld, struct napi_struct *napi,\n \t\treturn IWL_MLD_PASS_SKB;\n \n \t/* no sta yet */\n-\tif (IWL_FW_CHECK(mld, !sta,\n+\tif (IWL_FW_CHECK(mld, !link_sta,\n \t\t\t \"Got valid BAID without a valid station assigned - %d\\n\",\n \t\t\t baid))\n \t\treturn IWL_MLD_PASS_SKB;\n \n-\tmld_sta = iwl_mld_sta_from_mac80211(sta);\n+\tmld_sta = iwl_mld_sta_from_mac80211(link_sta-\u003esta);\n \n \t/* not a data packet */\n \tif (!ieee80211_is_data_qos(hdr-\u003eframe_control) ||\n@@ -324,7 +325,7 @@ iwl_mld_reorder(struct iwl_mld *mld, struct napi_struct *napi,\n \t * will be released when the frame release notification arrives.\n \t */\n \tif (!amsdu || last_subframe)\n-\t\tiwl_mld_reorder_release_frames(mld, sta, napi, baid_data,\n+\t\tiwl_mld_reorder_release_frames(mld, link_sta, napi, baid_data,\n \t\t\t\t\t buffer, nssn);\n \telse if (buffer-\u003enum_stored == 1)\n \t\tbuffer-\u003ehead_sn = nssn;\ndiff --git a/drivers/net/wireless/intel/iwlwifi/mld/agg.h b/drivers/net/wireless/intel/iwlwifi/mld/agg.h\nindex 651c80d1c7cda..c6cd5fa219bed 100644\n--- a/drivers/net/wireless/intel/iwlwifi/mld/agg.h\n+++ b/drivers/net/wireless/intel/iwlwifi/mld/agg.h\n@@ -1,6 +1,6 @@\n /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */\n /*\n- * Copyright (C) 2024 Intel Corporation\n+ * Copyright (C) 2024, 2026 Intel Corporation\n */\n #ifndef __iwl_agg_h__\n #define __iwl_agg_h__\n@@ -106,7 +106,7 @@ int iwl_mld_ampdu_rx_stop(struct iwl_mld *mld, struct ieee80211_sta *sta,\n \n enum iwl_mld_reorder_result\n iwl_mld_reorder(struct iwl_mld *mld, struct napi_struct *napi,\n-\t\tint queue, struct ieee80211_sta *sta,\n+\t\tint queue, struct ieee80211_link_sta *link_sta,\n \t\tstruct sk_buff *skb, struct iwl_rx_mpdu_desc *desc);\n \n void iwl_mld_handle_frame_release_notif(struct iwl_mld *mld,\ndiff --git a/drivers/net/wireless/intel/iwlwifi/mld/rx.c b/drivers/net/wireless/intel/iwlwifi/mld/rx.c\nindex 269439d789f46..02bdf6ccbcdc9 100644\n--- a/drivers/net/wireless/intel/iwlwifi/mld/rx.c\n+++ b/drivers/net/wireless/intel/iwlwifi/mld/rx.c\n@@ -47,7 +47,8 @@ iwl_mld_fill_phy_data_from_mpdu(struct iwl_mld *mld,\n }\n \n static inline int iwl_mld_check_pn(struct iwl_mld *mld, struct sk_buff *skb,\n-\t\t\t\t int queue, struct ieee80211_sta *sta)\n+\t\t\t\t int queue,\n+\t\t\t\t struct ieee80211_link_sta *link_sta)\n {\n \tstruct ieee80211_hdr *hdr = (void *)skb_mac_header(skb);\n \tstruct ieee80211_rx_status *stats = IEEE80211_SKB_RXCB(skb);\n@@ -71,13 +72,13 @@ static inline int iwl_mld_check_pn(struct iwl_mld *mld, struct sk_buff *skb,\n \t\treturn 0;\n \n \t/* if we are here - this for sure is either CCMP or GCMP */\n-\tif (!sta) {\n+\tif (!link_sta) {\n \t\tIWL_DEBUG_DROP(mld,\n \t\t\t \"expected hw-decrypted unicast frame for station\\n\");\n \t\treturn -1;\n \t}\n \n-\tmld_sta = iwl_mld_sta_from_mac80211(sta);\n+\tmld_sta = iwl_mld_sta_from_mac80211(link_sta-\u003esta);\n \n \textiv = (u8 *)hdr + ieee80211_hdrlen(hdr-\u003eframe_control);\n \tkeyidx = extiv[3] \u003e\u003e 6;\n@@ -119,17 +120,17 @@ static inline int iwl_mld_check_pn(struct iwl_mld *mld, struct sk_buff *skb,\n void iwl_mld_pass_packet_to_mac80211(struct iwl_mld *mld,\n \t\t\t\t struct napi_struct *napi,\n \t\t\t\t struct sk_buff *skb, int queue,\n-\t\t\t\t struct ieee80211_sta *sta)\n+\t\t\t\t struct ieee80211_link_sta *link_sta)\n {\n \tKUNIT_STATIC_STUB_REDIRECT(iwl_mld_pass_packet_to_mac80211,\n-\t\t\t\t mld, napi, skb, queue, sta);\n+\t\t\t\t mld, napi, skb, queue, link_sta);\n \n-\tif (unlikely(iwl_mld_check_pn(mld, skb, queue, sta))) {\n+\tif (unlikely(iwl_mld_check_pn(mld, skb, queue, link_sta))) {\n \t\tkfree_skb(skb);\n \t\treturn;\n \t}\n \n-\tieee80211_rx_napi(mld-\u003ehw, sta, skb, napi);\n+\tieee80211_rx_napi(mld-\u003ehw, link_sta, skb, napi);\n }\n EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(iwl_mld_pass_packet_to_mac80211);\n \n@@ -1728,7 +1729,7 @@ static void iwl_mld_update_last_rx_timestamp(struct iwl_mld *mld, u8 baid)\n * Sets *drop to true if the packet should be dropped.\n * Returns the station if found, or NULL otherwise.\n */\n-static struct ieee80211_sta *\n+static struct ieee80211_link_sta *\n iwl_mld_rx_with_sta(struct iwl_mld *mld, struct ieee80211_hdr *hdr,\n \t\t struct sk_buff *skb,\n \t\t const struct iwl_rx_mpdu_desc *mpdu_desc,\n@@ -1764,11 +1765,6 @@ iwl_mld_rx_with_sta(struct iwl_mld *mld, struct ieee80211_hdr *hdr,\n \n \trx_status = IEEE80211_SKB_RXCB(skb);\n \n-\tif (link_sta \u0026\u0026 sta-\u003evalid_links) {\n-\t\trx_status-\u003elink_valid = true;\n-\t\trx_status-\u003elink_id = link_sta-\u003elink_id;\n-\t}\n-\n \t/* fill checksum */\n \tif (ieee80211_is_data(hdr-\u003eframe_control) \u0026\u0026\n \t pkt-\u003elen_n_flags \u0026 cpu_to_le32(FH_RSCSR_RPA_EN)) {\n@@ -1803,10 +1799,10 @@ iwl_mld_rx_with_sta(struct iwl_mld *mld, struct ieee80211_hdr *hdr,\n \t\t\t\t\t\t\t queue);\n \t}\n \n-\treturn sta;\n+\treturn link_sta;\n }\n \n-static int iwl_mld_rx_mgmt_prot(struct ieee80211_sta *sta,\n+static int iwl_mld_rx_mgmt_prot(struct ieee80211_link_sta *link_sta,\n \t\t\t\tstruct ieee80211_hdr *hdr,\n \t\t\t\tstruct ieee80211_rx_status *rx_status,\n \t\t\t\tu32 mpdu_status,\n@@ -1820,7 +1816,6 @@ static int iwl_mld_rx_mgmt_prot(struct ieee80211_sta *sta,\n \tstruct ieee80211_key_conf *key;\n \tconst u8 *frame = (void *)hdr;\n \tconst u8 *mmie;\n-\tu8 link_id;\n \n \tif ((mpdu_status \u0026 IWL_RX_MPDU_STATUS_SEC_MASK) ==\n \t IWL_RX_MPDU_STATUS_SEC_NONE)\n@@ -1836,10 +1831,10 @@ static int iwl_mld_rx_mgmt_prot(struct ieee80211_sta *sta,\n \tif (!ieee80211_is_beacon(hdr-\u003eframe_control))\n \t\treturn 0;\n \n-\tif (!sta)\n+\tif (!link_sta)\n \t\treturn -1;\n \n-\tmld_sta = iwl_mld_sta_from_mac80211(sta);\n+\tmld_sta = iwl_mld_sta_from_mac80211(link_sta-\u003esta);\n \tmld_vif = iwl_mld_vif_from_mac80211(mld_sta-\u003evif);\n \n \t/* key mismatch - will also report !MIC_OK but we shouldn't count it */\n@@ -1853,8 +1848,7 @@ static int iwl_mld_rx_mgmt_prot(struct ieee80211_sta *sta,\n \t\treturn 0;\n \t}\n \n-\tlink_id = rx_status-\u003elink_valid ? rx_status-\u003elink_id : 0;\n-\tlink = rcu_dereference(mld_vif-\u003elink[link_id]);\n+\tlink = rcu_dereference(mld_vif-\u003elink[link_sta-\u003elink_id]);\n \tif (WARN_ON_ONCE(!link))\n \t\treturn -1;\n \n@@ -1905,7 +1899,7 @@ static int iwl_mld_rx_mgmt_prot(struct ieee80211_sta *sta,\n }\n \n static int iwl_mld_rx_crypto(struct iwl_mld *mld,\n-\t\t\t struct ieee80211_sta *sta,\n+\t\t\t struct ieee80211_link_sta *link_sta,\n \t\t\t struct ieee80211_hdr *hdr,\n \t\t\t struct ieee80211_rx_status *rx_status,\n \t\t\t struct iwl_rx_mpdu_desc *desc, int queue,\n@@ -1915,7 +1909,7 @@ static int iwl_mld_rx_crypto(struct iwl_mld *mld,\n \n \tif (unlikely(ieee80211_is_mgmt(hdr-\u003eframe_control) \u0026\u0026\n \t\t !ieee80211_has_protected(hdr-\u003eframe_control)))\n-\t\treturn iwl_mld_rx_mgmt_prot(sta, hdr, rx_status, status,\n+\t\treturn iwl_mld_rx_mgmt_prot(link_sta, hdr, rx_status, status,\n \t\t\t\t\t le16_to_cpu(desc-\u003empdu_len));\n \n \tif (!ieee80211_has_protected(hdr-\u003eframe_control) ||\n@@ -2023,7 +2017,7 @@ void iwl_mld_rx_mpdu(struct iwl_mld *mld, struct napi_struct *napi,\n \tstruct iwl_rx_packet *pkt = rxb_addr(rxb);\n \tstruct iwl_mld_rx_phy_data phy_data = {};\n \tstruct iwl_rx_mpdu_desc *mpdu_desc = (void *)pkt-\u003edata;\n-\tstruct ieee80211_sta *sta;\n+\tstruct ieee80211_link_sta *link_sta;\n \tstruct ieee80211_hdr *hdr;\n \tstruct sk_buff *skb;\n \tsize_t mpdu_desc_size = sizeof(*mpdu_desc);\n@@ -2086,7 +2080,8 @@ void iwl_mld_rx_mpdu(struct iwl_mld *mld, struct napi_struct *napi,\n \n \trcu_read_lock();\n \n-\tsta = iwl_mld_rx_with_sta(mld, hdr, skb, mpdu_desc, pkt, queue, \u0026drop);\n+\tlink_sta = iwl_mld_rx_with_sta(mld, hdr, skb, mpdu_desc, pkt, queue,\n+\t\t\t\t \u0026drop);\n \tif (drop)\n \t\tgoto drop;\n \n@@ -2127,7 +2122,7 @@ void iwl_mld_rx_mpdu(struct iwl_mld *mld, struct napi_struct *napi,\n \n \tiwl_mld_rx_fill_status(mld, link_id, hdr, skb, \u0026phy_data);\n \n-\tif (iwl_mld_rx_crypto(mld, sta, hdr, rx_status, mpdu_desc, queue,\n+\tif (iwl_mld_rx_crypto(mld, link_sta, hdr, rx_status, mpdu_desc, queue,\n \t\t\t le32_to_cpu(pkt-\u003elen_n_flags), \u0026crypto_len))\n \t\tgoto drop;\n \n@@ -2140,7 +2135,8 @@ void iwl_mld_rx_mpdu(struct iwl_mld *mld, struct napi_struct *napi,\n \tif (iwl_mld_time_sync_frame(mld, skb, hdr-\u003eaddr2))\n \t\tgoto out;\n \n-\treorder_res = iwl_mld_reorder(mld, napi, queue, sta, skb, mpdu_desc);\n+\treorder_res = iwl_mld_reorder(mld, napi, queue, link_sta, skb,\n+\t\t\t\t mpdu_desc);\n \tswitch (reorder_res) {\n \tcase IWL_MLD_PASS_SKB:\n \t\tbreak;\n@@ -2153,7 +2149,7 @@ void iwl_mld_rx_mpdu(struct iwl_mld *mld, struct napi_struct *napi,\n \t\tgoto drop;\n \t}\n \n-\tiwl_mld_pass_packet_to_mac80211(mld, napi, skb, queue, sta);\n+\tiwl_mld_pass_packet_to_mac80211(mld, napi, skb, queue, link_sta);\n \n \tgoto out;\n \ndiff --git a/drivers/net/wireless/intel/iwlwifi/mld/rx.h b/drivers/net/wireless/intel/iwlwifi/mld/rx.h\nindex 573b89c3c9c61..b08c5ade3db8f 100644\n--- a/drivers/net/wireless/intel/iwlwifi/mld/rx.h\n+++ b/drivers/net/wireless/intel/iwlwifi/mld/rx.h\n@@ -69,7 +69,7 @@ void iwl_mld_handle_rsc_notif(struct iwl_mld *mld,\n void iwl_mld_pass_packet_to_mac80211(struct iwl_mld *mld,\n \t\t\t\t struct napi_struct *napi,\n \t\t\t\t struct sk_buff *skb, int queue,\n-\t\t\t\t struct ieee80211_sta *sta);\n+\t\t\t\t struct ieee80211_link_sta *link_sta);\n \n void iwl_mld_handle_phy_air_sniffer_notif(struct iwl_mld *mld,\n \t\t\t\t\t struct napi_struct *napi,\ndiff --git a/drivers/net/wireless/intel/iwlwifi/mld/tests/agg.c b/drivers/net/wireless/intel/iwlwifi/mld/tests/agg.c\nindex 29b0248cec3df..e9efe2996f071 100644\n--- a/drivers/net/wireless/intel/iwlwifi/mld/tests/agg.c\n+++ b/drivers/net/wireless/intel/iwlwifi/mld/tests/agg.c\n@@ -2,7 +2,7 @@\n /*\n * KUnit tests for channel helper functions\n *\n- * Copyright (C) 2024-2025 Intel Corporation\n+ * Copyright (C) 2024-2026 Intel Corporation\n */\n #include \u003ckunit/test.h\u003e\n #include \u003ckunit/static_stub.h\u003e\n@@ -441,7 +441,7 @@ static void\n fake_iwl_mld_pass_packet_to_mac80211(struct iwl_mld *mld,\n \t\t\t\t struct napi_struct *napi,\n \t\t\t\t struct sk_buff *skb, int queue,\n-\t\t\t\t struct ieee80211_sta *sta)\n+\t\t\t\t struct ieee80211_link_sta *link_sta)\n {\n \t__skb_queue_tail(\u0026g_released_skbs, skb);\n \tg_num_released_skbs++;\n@@ -630,7 +630,8 @@ static void test_reorder_buffer(struct kunit *test)\n \tmpdu_desc = setup_mpdu_desc();\n \n \trcu_read_lock();\n-\treorder_res = iwl_mld_reorder(mld, NULL, QUEUE, sta, skb, mpdu_desc);\n+\treorder_res = iwl_mld_reorder(mld, NULL, QUEUE, \u0026sta-\u003edeflink, skb,\n+\t\t\t\t mpdu_desc);\n \trcu_read_unlock();\n \n \tKUNIT_ASSERT_EQ(test, reorder_res, param-\u003eexpected.reorder_res);\ndiff --git a/drivers/net/wireless/intel/iwlwifi/mvm/rx.c b/drivers/net/wireless/intel/iwlwifi/mvm/rx.c\nindex ab1eb2eb0c3cd..8c197a090c2b1 100644\n--- a/drivers/net/wireless/intel/iwlwifi/mvm/rx.c\n+++ b/drivers/net/wireless/intel/iwlwifi/mvm/rx.c\n@@ -90,7 +90,7 @@ static void iwl_mvm_pass_packet_to_mac80211(struct iwl_mvm *mvm,\n \t\t\t\tfraglen, rxb-\u003etruesize);\n \t}\n \n-\tieee80211_rx_napi(mvm-\u003ehw, sta, skb, napi);\n+\tieee80211_rx_napi(mvm-\u003ehw, sta ? \u0026sta-\u003edeflink : NULL, skb, napi);\n }\n \n /*\ndiff --git a/drivers/net/wireless/intel/iwlwifi/mvm/rxmq.c b/drivers/net/wireless/intel/iwlwifi/mvm/rxmq.c\nindex 7f0b4f5daa214..8c4009d3a00ca 100644\n--- a/drivers/net/wireless/intel/iwlwifi/mvm/rxmq.c\n+++ b/drivers/net/wireless/intel/iwlwifi/mvm/rxmq.c\n@@ -1,6 +1,6 @@\n // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause\n /*\n- * Copyright (C) 2012-2014, 2018-2025 Intel Corporation\n+ * Copyright (C) 2012-2014, 2018-2026 Intel Corporation\n * Copyright (C) 2013-2015 Intel Mobile Communications GmbH\n * Copyright (C) 2015-2017 Intel Deutschland GmbH\n */\n@@ -243,7 +243,7 @@ static void iwl_mvm_pass_packet_to_mac80211(struct iwl_mvm *mvm,\n \t\treturn;\n \t}\n \n-\tieee80211_rx_napi(mvm-\u003ehw, sta, skb, napi);\n+\tieee80211_rx_napi(mvm-\u003ehw, sta ? \u0026sta-\u003edeflink : NULL, skb, napi);\n }\n \n static bool iwl_mvm_used_average_energy(struct iwl_mvm *mvm,\n@@ -2528,7 +2528,7 @@ void iwl_mvm_rx_monitor_no_data(struct iwl_mvm *mvm, struct napi_struct *napi,\n \t}\n \n \trcu_read_lock();\n-\tieee80211_rx_napi(mvm-\u003ehw, sta, skb, napi);\n+\tieee80211_rx_napi(mvm-\u003ehw, sta ? \u0026sta-\u003edeflink : NULL, skb, napi);\n \trcu_read_unlock();\n }\n \ndiff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c\nindex abbe65cbcd89e..3da06a55913ef 100644\n--- a/drivers/net/wireless/mediatek/mt76/mac80211.c\n+++ b/drivers/net/wireless/mediatek/mt76/mac80211.c\n@@ -1255,7 +1255,7 @@ EXPORT_SYMBOL(mt76_rx_signal);\n static void\n mt76_rx_convert(struct mt76_dev *dev, struct sk_buff *skb,\n \t\tstruct ieee80211_hw **hw,\n-\t\tstruct ieee80211_sta **sta)\n+\t\tstruct ieee80211_link_sta **link_sta)\n {\n \tstruct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);\n \tstruct ieee80211_hdr *hdr = mt76_skb_get_hdr(skb);\n@@ -1302,11 +1302,15 @@ mt76_rx_convert(struct mt76_dev *dev, struct sk_buff *skb,\n \t sizeof(mstat.chain_signal));\n \n \tif (mstat.wcid) {\n-\t\tstatus-\u003elink_valid = mstat.wcid-\u003elink_valid;\n-\t\tstatus-\u003elink_id = mstat.wcid-\u003elink_id;\n+\t\tstruct ieee80211_sta *sta = wcid_to_sta(mstat.wcid);\n+\n+\t\tif (mstat.wcid-\u003elink_valid)\n+\t\t\t*link_sta =\n+\t\t\t\trcu_dereference(sta-\u003elink[mstat.wcid-\u003elink_id]);\n+\t\telse\n+\t\t\t*link_sta = \u0026sta-\u003edeflink;\n \t}\n \n-\t*sta = wcid_to_sta(mstat.wcid);\n \t*hw = mt76_phy_hw(dev, mstat.phy_idx);\n }\n \n@@ -1530,7 +1534,7 @@ mt76_check_sta(struct mt76_dev *dev, struct sk_buff *skb)\n void mt76_rx_complete(struct mt76_dev *dev, struct sk_buff_head *frames,\n \t\t struct napi_struct *napi)\n {\n-\tstruct ieee80211_sta *sta;\n+\tstruct ieee80211_link_sta *link_sta;\n \tstruct ieee80211_hw *hw;\n \tstruct sk_buff *skb, *tmp;\n \tLIST_HEAD(list);\n@@ -1541,8 +1545,8 @@ void mt76_rx_complete(struct mt76_dev *dev, struct sk_buff_head *frames,\n \n \t\tmt76_check_ccmp_pn(skb);\n \t\tskb_shinfo(skb)-\u003efrag_list = NULL;\n-\t\tmt76_rx_convert(dev, skb, \u0026hw, \u0026sta);\n-\t\tieee80211_rx_list(hw, sta, skb, \u0026list);\n+\t\tmt76_rx_convert(dev, skb, \u0026hw, \u0026link_sta);\n+\t\tieee80211_rx_list(hw, link_sta, skb, \u0026list);\n \n \t\t/* subsequent amsdu frames */\n \t\twhile (nskb) {\n@@ -1550,8 +1554,8 @@ void mt76_rx_complete(struct mt76_dev *dev, struct sk_buff_head *frames,\n \t\t\tnskb = nskb-\u003enext;\n \t\t\tskb-\u003enext = NULL;\n \n-\t\t\tmt76_rx_convert(dev, skb, \u0026hw, \u0026sta);\n-\t\t\tieee80211_rx_list(hw, sta, skb, \u0026list);\n+\t\t\tmt76_rx_convert(dev, skb, \u0026hw, \u0026link_sta);\n+\t\t\tieee80211_rx_list(hw, link_sta, skb, \u0026list);\n \t\t}\n \t}\n \tspin_unlock(\u0026dev-\u003erx_lock);\ndiff --git a/drivers/net/wireless/realtek/rtw89/core.c b/drivers/net/wireless/realtek/rtw89/core.c\nindex 397ebbfcac09a..454d876aeb886 100644\n--- a/drivers/net/wireless/realtek/rtw89/core.c\n+++ b/drivers/net/wireless/realtek/rtw89/core.c\n@@ -3073,10 +3073,8 @@ static void rtw89_vif_rx_stats_iter(void *data, u8 *mac,\n \tstruct rtw89_vif *rtwvif = vif_to_rtwvif(vif);\n \tstruct rtw89_rx_desc_info *desc_info = iter_data-\u003edesc_info;\n \tstruct sk_buff *skb = iter_data-\u003eskb;\n-\tstruct ieee80211_rx_status *rx_status = IEEE80211_SKB_RXCB(skb);\n \tstruct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb-\u003edata;\n \tstruct rtw89_rx_phy_ppdu *phy_ppdu = iter_data-\u003ephy_ppdu;\n-\tbool is_mld = ieee80211_vif_is_mld(vif);\n \tstruct ieee80211_bss_conf *bss_conf;\n \tstruct rtw89_vif_link *rtwvif_link;\n \tconst u8 *bssid = iter_data-\u003ebssid;\n@@ -3110,11 +3108,6 @@ static void rtw89_vif_rx_stats_iter(void *data, u8 *mac,\n \tif (!ether_addr_equal(target_bssid, bssid))\n \t\tgoto out;\n \n-\tif (is_mld) {\n-\t\trx_status-\u003elink_valid = true;\n-\t\trx_status-\u003elink_id = rtwvif_link-\u003elink_id;\n-\t}\n-\n \tbb = rtw89_get_bb_ctx(rtwdev, rtwvif_link-\u003ephy_idx);\n \tpkt_stat = \u0026bb-\u003ecur_pkt_stat;\n \ndiff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c\nindex 02b6d81cccd11..a0d519cd9c4fa 100644\n--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c\n+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c\n@@ -1880,9 +1880,6 @@ static void mac80211_hwsim_rx(struct mac80211_hwsim_data *data,\n \t\t\t\tsp-\u003eactive_links_rx \u0026= ~BIT(link_id);\n \t\t\telse\n \t\t\t\tsp-\u003eactive_links_rx |= BIT(link_id);\n-\n-\t\t\trx_status-\u003elink_valid = true;\n-\t\t\trx_status-\u003elink_id = link_id;\n \t\t}\n \t\trcu_read_unlock();\n \t}\ndiff --git a/include/net/cfg80211.h b/include/net/cfg80211.h\nindex 97c16d4ff1271..436478daf5596 100644\n--- a/include/net/cfg80211.h\n+++ b/include/net/cfg80211.h\n@@ -4009,6 +4009,7 @@ struct cfg80211_update_ft_ies_params {\n * @link_id: for MLO, the link ID to transmit on, -1 if not given; note\n *\tthat the link ID isn't validated (much), it's in range but the\n *\tlink might not exist (or be used by the receiver STA)\n+ * @no_sta: set if the frame should not be transmitted using an existing STA\n */\n struct cfg80211_mgmt_tx_params {\n \tstruct ieee80211_channel *chan;\n@@ -4021,6 +4022,7 @@ struct cfg80211_mgmt_tx_params {\n \tint n_csa_offsets;\n \tconst u16 *csa_offsets;\n \tint link_id;\n+\tbool no_sta;\n };\n \n /**\n@@ -9501,6 +9503,7 @@ void cfg80211_conn_failed(struct net_device *dev, const u8 *mac_addr,\n * @flags: flags, as defined in \u0026enum nl80211_rxmgmt_flags\n * @rx_tstamp: Hardware timestamp of frame RX in nanoseconds\n * @ack_tstamp: Hardware timestamp of ack TX in nanoseconds\n+ * @no_sta: set if no station is known for the frame (relevant for MLD)\n */\n struct cfg80211_rx_info {\n \tint freq;\n@@ -9512,6 +9515,7 @@ struct cfg80211_rx_info {\n \tu32 flags;\n \tu64 rx_tstamp;\n \tu64 ack_tstamp;\n+\tbool no_sta;\n };\n \n /**\ndiff --git a/include/net/mac80211.h b/include/net/mac80211.h\nindex 9d1fac6e80829..7aae728e96d84 100644\n--- a/include/net/mac80211.h\n+++ b/include/net/mac80211.h\n@@ -1770,10 +1770,9 @@ enum mac80211_rx_encoding {\n * @ampdu_reference: A-MPDU reference number, must be a different value for\n *\teach A-MPDU but the same for each subframe within one A-MPDU\n * @zero_length_psdu_type: radiotap type of the 0-length PSDU\n- * @link_valid: if the link which is identified by @link_id is valid. This flag\n- *\tis set only when connection is MLO.\n- * @link_id: id of the link used to receive the packet. This is used along with\n- *\t@link_valid.\n+ * @link_id: id of the link used to receive the packet. Set and used by\n+ *\tmac80211 internally, it uses @freq set by the driver to identify the\n+ *\tcorrect link per vif.\n */\n struct ieee80211_rx_status {\n \tu64 mactime;\n@@ -1813,7 +1812,7 @@ struct ieee80211_rx_status {\n \tu8 chains;\n \ts8 chain_signal[IEEE80211_MAX_CHAINS];\n \tu8 zero_length_psdu_type;\n-\tu8 link_valid:1, link_id:4;\n+\tu8 link_id:4;\n };\n \n static_assert(sizeof(struct ieee80211_rx_status) \u003c= sizeof_field(struct sk_buff, cb));\n@@ -5389,14 +5388,18 @@ void ieee80211_restart_hw(struct ieee80211_hw *hw);\n * mixed for a single hardware. Must not run concurrently with\n * ieee80211_tx_status_skb() or ieee80211_tx_status_ni().\n *\n+ * For data frames, when hardware has done address translation, a link station\n+ * has to be provided and the frequency information may be skipped.\n+ *\n * This function must be called with BHs disabled and RCU read lock\n *\n * @hw: the hardware this frame came in on\n- * @sta: the station the frame was received from, or %NULL\n+ * @link_sta: the link station the data frame was received from, or %NULL\n * @skb: the buffer to receive, owned by mac80211 after this call\n * @list: the destination list\n */\n-void ieee80211_rx_list(struct ieee80211_hw *hw, struct ieee80211_sta *sta,\n+void ieee80211_rx_list(struct ieee80211_hw *hw,\n+\t\t struct ieee80211_link_sta *link_sta,\n \t\t struct sk_buff *skb, struct list_head *list);\n \n /**\n@@ -5414,14 +5417,18 @@ void ieee80211_rx_list(struct ieee80211_hw *hw, struct ieee80211_sta *sta,\n * mixed for a single hardware. Must not run concurrently with\n * ieee80211_tx_status_skb() or ieee80211_tx_status_ni().\n *\n+ * For data frames, when hardware has done address translation, a link station\n+ * has to be provided and the frequency information may be skipped.\n+ *\n * This function must be called with BHs disabled.\n *\n * @hw: the hardware this frame came in on\n- * @sta: the station the frame was received from, or %NULL\n+ * @link_sta: the link station the data frame was received from, or %NULL\n * @skb: the buffer to receive, owned by mac80211 after this call\n * @napi: the NAPI context\n */\n-void ieee80211_rx_napi(struct ieee80211_hw *hw, struct ieee80211_sta *sta,\n+void ieee80211_rx_napi(struct ieee80211_hw *hw,\n+\t\t struct ieee80211_link_sta *link_sta,\n \t\t struct sk_buff *skb, struct napi_struct *napi);\n \n /**\ndiff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h\nindex 020387d764122..4653f8f26ee70 100644\n--- a/include/uapi/linux/nl80211.h\n+++ b/include/uapi/linux/nl80211.h\n@@ -3185,6 +3185,11 @@ enum nl80211_commands {\n *\tThe aggregated message always precedes the per-link messages for the\n *\tsame station within a dump sequence.\n *\n+ * @NL80211_ATTR_FRAME_CMD_NO_STA: Valid for NL80211_CMD_FRAME to denote that\n+ *\tthe kernel had no station for a received frame or should not use a\n+ *\tknown station to transmit a frame. This is relevant to know whether\n+ *\tMLD address translation happened or to disable it when sending a frame.\n+ *\n * @NUM_NL80211_ATTR: total number of nl80211_attrs available\n * @NL80211_ATTR_MAX: highest attribute number currently defined\n * @__NL80211_ATTR_AFTER_LAST: internal use\n@@ -3785,6 +3790,8 @@ enum nl80211_attrs {\n \n \tNL80211_ATTR_STA_DUMP_LINK_STATS,\n \n+\tNL80211_ATTR_FRAME_CMD_NO_STA,\n+\n \t/* add attributes here, update the policy in nl80211.c */\n \n \t__NL80211_ATTR_AFTER_LAST,\ndiff --git a/net/mac80211/agg-tx.c b/net/mac80211/agg-tx.c\nindex 4833b46770b62..dd8a11ef6e52b 100644\n--- a/net/mac80211/agg-tx.c\n+++ b/net/mac80211/agg-tx.c\n@@ -98,7 +98,7 @@ static void ieee80211_send_addba_request(struct sta_info *sta, u16 tid,\n \tif (sta-\u003esta.deflink.he_cap.has_he)\n \t\tieee80211_add_addbaext(skb, 0, agg_size);\n \n-\tieee80211_tx_skb_tid(sdata, skb, tid, -1);\n+\tieee80211_tx_skb_tid(sdata, skb, NULL, tid, -1);\n }\n \n void ieee80211_send_bar(struct ieee80211_vif *vif, u8 *ra, u16 tid, u16 ssn)\n@@ -127,7 +127,7 @@ void ieee80211_send_bar(struct ieee80211_vif *vif, u8 *ra, u16 tid, u16 ssn)\n \n \tIEEE80211_SKB_CB(skb)-\u003eflags |= IEEE80211_TX_INTFL_DONT_ENCRYPT |\n \t\t\t\t\tIEEE80211_TX_CTL_REQ_TX_STATUS;\n-\tieee80211_tx_skb_tid(sdata, skb, tid, -1);\n+\tieee80211_tx_skb_tid(sdata, skb, NULL, tid, -1);\n }\n EXPORT_SYMBOL(ieee80211_send_bar);\n \ndiff --git a/net/mac80211/ap.c b/net/mac80211/ap.c\nindex e7ac99c5a22eb..fe255891c35f0 100644\n--- a/net/mac80211/ap.c\n+++ b/net/mac80211/ap.c\n@@ -82,9 +82,6 @@ ieee80211_rx_eml_op_mode_notif(struct ieee80211_sub_if_data *sdata,\n \tif (!ift_ext_capa)\n \t\treturn;\n \n-\tif (!status-\u003elink_valid)\n-\t\treturn;\n-\n \tsta = sta_info_get_bss(sdata, mgmt-\u003esa);\n \tif (!sta)\n \t\treturn;\ndiff --git a/net/mac80211/ht.c b/net/mac80211/ht.c\nindex e1e1b7f82f3fe..c5c2113fa4f79 100644\n--- a/net/mac80211/ht.c\n+++ b/net/mac80211/ht.c\n@@ -545,7 +545,7 @@ int ieee80211_send_smps_action(struct ieee80211_sub_if_data *sdata,\n \tinfo-\u003estatus_data = IEEE80211_STATUS_TYPE_SMPS |\n \t\t\t u16_encode_bits(status_link_id \u003c\u003c 2 | smps,\n \t\t\t\t\t IEEE80211_STATUS_SUBDATA_MASK);\n-\tieee80211_tx_skb_tid(sdata, skb, 7, link_id);\n+\tieee80211_tx_skb_tid(sdata, skb, NULL, 7, link_id);\n \n \treturn 0;\n }\ndiff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h\nindex 5761e96214914..0d8eb042887a9 100644\n--- a/net/mac80211/ieee80211_i.h\n+++ b/net/mac80211/ieee80211_i.h\n@@ -2463,7 +2463,8 @@ void ieee80211_xmit(struct ieee80211_sub_if_data *sdata,\n \t\t struct sta_info *sta, struct sk_buff *skb);\n \n void __ieee80211_tx_skb_tid_band(struct ieee80211_sub_if_data *sdata,\n-\t\t\t\t struct sk_buff *skb, int tid, int link_id,\n+\t\t\t\t struct sk_buff *skb, struct sta_info *sta,\n+\t\t\t\t int tid, int link_id,\n \t\t\t\t enum nl80211_band band);\n \n static inline bool ieee80211_require_encrypted_assoc(__le16 fc,\n@@ -2483,18 +2484,20 @@ ieee80211_tx_skb_tid_band(struct ieee80211_sub_if_data *sdata,\n \t\t\t enum nl80211_band band)\n {\n \trcu_read_lock();\n-\t__ieee80211_tx_skb_tid_band(sdata, skb, tid, -1, band);\n+\t__ieee80211_tx_skb_tid_band(sdata, skb, ERR_PTR(-ENOENT),\n+\t\t\t\t tid, -1, band);\n \trcu_read_unlock();\n }\n \n void ieee80211_tx_skb_tid(struct ieee80211_sub_if_data *sdata,\n-\t\t\t struct sk_buff *skb, int tid, int link_id);\n+\t\t\t struct sk_buff *skb, struct sta_info *sta,\n+\t\t\t int tid, int link_id);\n \n static inline void ieee80211_tx_skb(struct ieee80211_sub_if_data *sdata,\n \t\t\t\t struct sk_buff *skb)\n {\n \t/* Send all internal mgmt frames on VO. Accordingly set TID to 7. */\n-\tieee80211_tx_skb_tid(sdata, skb, 7, -1);\n+\tieee80211_tx_skb_tid(sdata, skb, NULL, 7, -1);\n }\n \n /**\ndiff --git a/net/mac80211/iface.c b/net/mac80211/iface.c\nindex 43460a705a6bd..a0b214dbb8af8 100644\n--- a/net/mac80211/iface.c\n+++ b/net/mac80211/iface.c\n@@ -1673,11 +1673,8 @@ static void ieee80211_iface_process_skb(struct ieee80211_local *local,\n \t\t\t\tbreak;\n \n \t\t\tstatus = IEEE80211_SKB_RXCB(skb);\n-\t\t\tif (!status-\u003elink_valid)\n-\t\t\t\tlink_sta = \u0026sta-\u003edeflink;\n-\t\t\telse\n-\t\t\t\tlink_sta = rcu_dereference_protected(sta-\u003elink[status-\u003elink_id],\n-\t\t\t\t\t\t\tlockdep_is_held(\u0026local-\u003ehw.wiphy-\u003emtx));\n+\t\t\tlink_sta = wiphy_dereference(local-\u003ehw.wiphy,\n+\t\t\t\t\t\t sta-\u003elink[status-\u003elink_id]);\n \t\t\tif (link_sta)\n \t\t\t\tieee80211_ht_handle_chanwidth_notif(local, sdata, sta,\n \t\t\t\t\t\t\t\t link_sta, chanwidth,\ndiff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c\nindex f51167f0fc46d..361775fb931ec 100644\n--- a/net/mac80211/mlme.c\n+++ b/net/mac80211/mlme.c\n@@ -11831,12 +11831,10 @@ void ieee80211_sta_rx_queued_frame(struct ieee80211_sub_if_data *sdata,\n \trx_status = (struct ieee80211_rx_status *) skb-\u003ecb;\n \tfc = le16_to_cpu(mgmt-\u003eframe_control);\n \n-\tif (rx_status-\u003elink_valid) {\n-\t\tlink = sdata_dereference(sdata-\u003elink[rx_status-\u003elink_id],\n-\t\t\t\t\t sdata);\n-\t\tif (!link)\n-\t\t\treturn;\n-\t}\n+\tlink = sdata_dereference(sdata-\u003elink[rx_status-\u003elink_id],\n+\t\t\t\t sdata);\n+\tif (!link)\n+\t\treturn;\n \n \tswitch (fc \u0026 IEEE80211_FCTL_STYPE) {\n \tcase IEEE80211_STYPE_BEACON:\ndiff --git a/net/mac80211/offchannel.c b/net/mac80211/offchannel.c\nindex 7acef80d5f1fb..ddd7a96a2f817 100644\n--- a/net/mac80211/offchannel.c\n+++ b/net/mac80211/offchannel.c\n@@ -854,8 +854,10 @@ int ieee80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev,\n \t\t\tneed_offchan = true;\n \n \t\trcu_read_lock();\n-\t\tsta = sta_info_get_bss(sdata, mgmt-\u003eda);\n-\t\tmlo_sta = sta \u0026\u0026 sta-\u003esta.mlo;\n+\t\tif (!params-\u003eno_sta) {\n+\t\t\tsta = sta_info_get_bss(sdata, mgmt-\u003eda);\n+\t\t\tmlo_sta = sta \u0026\u0026 sta-\u003esta.mlo;\n+\t\t}\n \n \t\tif (!ieee80211_is_action(mgmt-\u003eframe_control) ||\n \t\t mgmt-\u003eu.action.category == WLAN_CATEGORY_PUBLIC ||\n@@ -884,7 +886,8 @@ int ieee80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev,\n \t\t local-\u003eops-\u003eremain_on_channel \u0026\u0026\n \t\t memcmp(sdata-\u003evif.cfg.ap_addr, mgmt-\u003ebssid, ETH_ALEN))) {\n \t\t\tneed_offchan = true;\n-\t\t} else if (sdata-\u003eu.mgd.associated \u0026\u0026\n+\t\t} else if (!params-\u003eno_sta \u0026\u0026\n+\t\t\t sdata-\u003eu.mgd.associated \u0026\u0026\n \t\t\t ether_addr_equal(sdata-\u003evif.cfg.ap_addr, mgmt-\u003eda)) {\n \t\t\tsta = sta_info_get_bss(sdata, mgmt-\u003eda);\n \t\t\tmlo_sta = sta \u0026\u0026 sta-\u003esta.mlo;\n@@ -1022,7 +1025,9 @@ int ieee80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev,\n \t}\n \n \tif (!need_offchan) {\n-\t\tieee80211_tx_skb_tid(sdata, skb, 7, link_id);\n+\t\tieee80211_tx_skb_tid(sdata, skb,\n+\t\t\t\t params-\u003eno_sta ? ERR_PTR(-ENOENT) : sta,\n+\t\t\t\t 7, link_id);\n \t\tret = 0;\n \t\tgoto out_unlock;\n \t}\ndiff --git a/net/mac80211/rx.c b/net/mac80211/rx.c\nindex 5e26be8e27d8f..7896e6eee130b 100644\n--- a/net/mac80211/rx.c\n+++ b/net/mac80211/rx.c\n@@ -222,43 +222,21 @@ ieee80211_rx_radiotap_hdrlen(struct ieee80211_local *local,\n }\n \n static void __ieee80211_queue_skb_to_iface(struct ieee80211_sub_if_data *sdata,\n-\t\t\t\t\t int link_id,\n-\t\t\t\t\t struct sta_info *sta,\n+\t\t\t\t\t struct link_sta_info *link_sta,\n \t\t\t\t\t struct sk_buff *skb)\n {\n-\tstruct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);\n-\n-\tif (link_id \u003e= 0) {\n-\t\tstatus-\u003elink_valid = 1;\n-\t\tstatus-\u003elink_id = link_id;\n-\t} else {\n-\t\tstatus-\u003elink_valid = 0;\n-\t}\n-\n \tskb_queue_tail(\u0026sdata-\u003eskb_queue, skb);\n \twiphy_work_queue(sdata-\u003elocal-\u003ehw.wiphy, \u0026sdata-\u003ework);\n-\tif (sta) {\n-\t\tstruct link_sta_info *link_sta_info;\n-\n-\t\tif (link_id \u003e= 0) {\n-\t\t\tlink_sta_info = rcu_dereference(sta-\u003elink[link_id]);\n-\t\t\tif (!link_sta_info)\n-\t\t\t\treturn;\n-\t\t} else {\n-\t\t\tlink_sta_info = \u0026sta-\u003edeflink;\n-\t\t}\n-\n-\t\tlink_sta_info-\u003erx_stats.packets++;\n-\t}\n+\tif (link_sta)\n+\t\tlink_sta-\u003erx_stats.packets++;\n }\n \n static void ieee80211_queue_skb_to_iface(struct ieee80211_sub_if_data *sdata,\n-\t\t\t\t\t int link_id,\n-\t\t\t\t\t struct sta_info *sta,\n+\t\t\t\t\t struct link_sta_info *link_sta,\n \t\t\t\t\t struct sk_buff *skb)\n {\n \tskb-\u003eprotocol = 0;\n-\t__ieee80211_queue_skb_to_iface(sdata, link_id, sta, skb);\n+\t__ieee80211_queue_skb_to_iface(sdata, link_sta, skb);\n }\n \n static void ieee80211_handle_mu_mimo_mon(struct ieee80211_sub_if_data *sdata,\n@@ -301,7 +279,7 @@ static void ieee80211_handle_mu_mimo_mon(struct ieee80211_sub_if_data *sdata,\n \tif (!skb)\n \t\treturn;\n \n-\tieee80211_queue_skb_to_iface(sdata, -1, NULL, skb);\n+\tieee80211_queue_skb_to_iface(sdata, NULL, skb);\n }\n \n /*\n@@ -1498,7 +1476,7 @@ static void ieee80211_rx_reorder_ampdu(struct ieee80211_rx_data *rx,\n \t/* if this mpdu is fragmented - terminate rx aggregation session */\n \tsc = le16_to_cpu(hdr-\u003eseq_ctrl);\n \tif (sc \u0026 IEEE80211_SCTL_FRAG) {\n-\t\tieee80211_queue_skb_to_iface(rx-\u003esdata, rx-\u003elink_id, NULL, skb);\n+\t\tieee80211_queue_skb_to_iface(rx-\u003esdata, NULL, skb);\n \t\treturn;\n \t}\n \n@@ -2524,7 +2502,7 @@ ieee80211_rx_h_defragment(struct ieee80211_rx_data *rx)\n \n out:\n \tieee80211_led_rx(rx-\u003elocal);\n-\tif (rx-\u003esta)\n+\tif (rx-\u003elink_sta)\n \t\trx-\u003elink_sta-\u003erx_stats.packets++;\n \treturn RX_CONTINUE;\n }\n@@ -3335,8 +3313,8 @@ ieee80211_rx_h_data(struct ieee80211_rx_data *rx)\n \t\t (tf-\u003eaction_code == WLAN_TDLS_CHANNEL_SWITCH_REQUEST ||\n \t\t tf-\u003eaction_code == WLAN_TDLS_CHANNEL_SWITCH_RESPONSE)) {\n \t\t\trx-\u003eskb-\u003eprotocol = cpu_to_be16(ETH_P_TDLS);\n-\t\t\t__ieee80211_queue_skb_to_iface(sdata, rx-\u003elink_id,\n-\t\t\t\t\t\t rx-\u003esta, rx-\u003eskb);\n+\t\t\t__ieee80211_queue_skb_to_iface(sdata, rx-\u003elink_sta,\n+\t\t\t\t\t\t rx-\u003eskb);\n \t\t\treturn RX_QUEUED;\n \t\t}\n \t}\n@@ -3991,7 +3969,7 @@ ieee80211_rx_h_action(struct ieee80211_rx_data *rx)\n \treturn RX_QUEUED;\n \n queue:\n-\tieee80211_queue_skb_to_iface(sdata, rx-\u003elink_id, rx-\u003esta, rx-\u003eskb);\n+\tieee80211_queue_skb_to_iface(sdata, rx-\u003elink_sta, rx-\u003eskb);\n \treturn RX_QUEUED;\n }\n \n@@ -4005,6 +3983,7 @@ ieee80211_rx_h_userspace_mgmt(struct ieee80211_rx_data *rx)\n \t\t.len = rx-\u003eskb-\u003elen,\n \t\t.link_id = rx-\u003elink_id,\n \t\t.have_link_id = rx-\u003elink_id \u003e= 0,\n+\t\t.no_sta = !rx-\u003esta,\n \t};\n \n \t/* skip known-bad action frames and return them in the next handler */\n@@ -4127,7 +4106,7 @@ ieee80211_rx_h_action_return(struct ieee80211_rx_data *rx)\n \t\t\t\t\tlocal-\u003ehw.offchannel_tx_hw_queue;\n \t\t}\n \n-\t\t__ieee80211_tx_skb_tid_band(rx-\u003esdata, nskb, 7, -1,\n+\t\t__ieee80211_tx_skb_tid_band(rx-\u003esdata, nskb, rx-\u003esta, 7, -1,\n \t\t\t\t\t status-\u003eband);\n \t}\n \n@@ -4147,7 +4126,7 @@ ieee80211_rx_h_ext(struct ieee80211_rx_data *rx)\n \t\treturn RX_DROP_U_UNEXPECTED_EXT_FRAME;\n \n \t/* for now only beacons are ext, so queue them */\n-\tieee80211_queue_skb_to_iface(sdata, rx-\u003elink_id, rx-\u003esta, rx-\u003eskb);\n+\tieee80211_queue_skb_to_iface(sdata, rx-\u003elink_sta, rx-\u003eskb);\n \n \treturn RX_QUEUED;\n }\n@@ -4204,7 +4183,7 @@ ieee80211_rx_h_mgmt(struct ieee80211_rx_data *rx)\n \t\treturn RX_DROP_U_UNHANDLED_MGMT_STYPE;\n \t}\n \n-\tieee80211_queue_skb_to_iface(sdata, rx-\u003elink_id, rx-\u003esta, rx-\u003eskb);\n+\tieee80211_queue_skb_to_iface(sdata, rx-\u003elink_sta, rx-\u003eskb);\n \n \treturn RX_QUEUED;\n }\n@@ -4248,12 +4227,28 @@ static void ieee80211_rx_handlers(struct ieee80211_rx_data *rx,\n \tspin_lock_bh(\u0026rx-\u003elocal-\u003erx_path_lock);\n \n \twhile ((skb = __skb_dequeue(frames))) {\n+\t\tstruct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);\n+\n \t\t/*\n-\t\t * all the other fields are valid across frames\n-\t\t * that belong to an aMPDU since they are on the\n-\t\t * same TID from the same station\n+\t\t * Most fields are valid across frames. However,\n+\t\t * in the case of reordering frames may have arrived\n+\t\t * on different links, so adjust the link_id, link\n+\t\t * and link_sta accordingly.\n \t\t */\n \t\trx-\u003eskb = skb;\n+\t\tif (ieee80211_vif_is_mld(\u0026rx-\u003esdata-\u003evif) \u0026\u0026\n+\t\t unlikely(rx-\u003elink_id != status-\u003elink_id)) {\n+\t\t\tif (rx-\u003esta) {\n+\t\t\t\trx-\u003elink_sta =\n+\t\t\t\t\trcu_dereference(rx-\u003esta-\u003elink[rx-\u003elink_id]);\n+\n+\t\t\t\t/* Link got disabled, just use deflink */\n+\t\t\t\tif (!rx-\u003elink_sta)\n+\t\t\t\t\trx-\u003elink_sta = \u0026rx-\u003esta-\u003edeflink;\n+\t\t\t}\n+\t\t\trx-\u003elink_id = status-\u003elink_id;\n+\t\t\trx-\u003elink = rcu_dereference(rx-\u003esdata-\u003elink[status-\u003elink_id]);\n+\t\t}\n \n \t\tif (WARN_ON_ONCE(!rx-\u003elink)) {\n \t\t\tres = RX_DROP_U_NO_LINK;\n@@ -4323,7 +4318,34 @@ static void ieee80211_invoke_rx_handlers(struct ieee80211_rx_data *rx)\n static bool\n ieee80211_rx_is_valid_sta_link_id(struct ieee80211_sta *sta, u8 link_id)\n {\n-\treturn !!(sta-\u003evalid_links \u0026 BIT(link_id));\n+\tif (sta-\u003emlo)\n+\t\treturn sta-\u003evalid_links \u0026 BIT(link_id);\n+\n+\treturn sta-\u003edeflink.link_id == link_id;\n+}\n+\n+static bool ieee80211_rx_data_set_link_sta(struct ieee80211_rx_data *rx,\n+\t\t\t\t\t struct link_sta_info *link_sta)\n+{\n+\tstruct sta_info *sta;\n+\n+\tif (WARN_ON_ONCE(!link_sta) ||\n+\t !ieee80211_rx_is_valid_sta_link_id(\u0026link_sta-\u003esta-\u003esta,\n+\t\t\t\t\t link_sta-\u003elink_id))\n+\t\treturn false;\n+\n+\tsta = link_sta-\u003esta;\n+\n+\trx-\u003esta = sta;\n+\trx-\u003elocal = sta-\u003esdata-\u003elocal;\n+\trx-\u003elink = rcu_dereference(sta-\u003esdata-\u003elink[link_sta-\u003elink_id]);\n+\tif (!rx-\u003elink)\n+\t\treturn false;\n+\n+\trx-\u003elink_id = rx-\u003elink-\u003elink_id;\n+\trx-\u003elink_sta = link_sta;\n+\n+\treturn true;\n }\n \n static bool ieee80211_rx_data_set_link(struct ieee80211_rx_data *rx,\n@@ -4360,11 +4382,13 @@ static bool ieee80211_rx_data_set_sta(struct ieee80211_rx_data *rx,\n \n \tif (link_id \u003c 0) {\n \t\tif (ieee80211_vif_is_mld(\u0026rx-\u003esdata-\u003evif) \u0026\u0026\n-\t\t sta \u0026\u0026 !sta-\u003esta.valid_links)\n+\t\t sta \u0026\u0026 !sta-\u003esta.valid_links) {\n \t\t\trx-\u003elink =\n \t\t\t\trcu_dereference(rx-\u003esdata-\u003elink[sta-\u003edeflink.link_id]);\n-\t\telse\n+\t\t\trx-\u003elink_sta = \u0026sta-\u003edeflink;\n+\t\t} else {\n \t\t\trx-\u003elink = \u0026rx-\u003esdata-\u003edeflink;\n+\t\t}\n \t} else if (!ieee80211_rx_data_set_link(rx, link_id)) {\n \t\treturn false;\n \t}\n@@ -4387,7 +4411,7 @@ void ieee80211_release_reorder_timeout(struct sta_info *sta, int tid)\n \tstruct tid_ampdu_rx *tid_agg_rx;\n \tint link_id = -1;\n \n-\t/* FIXME: statistics won't be right with this */\n+\t/* NOTE: the correct link STA for the frame is set later */\n \tif (sta-\u003esta.valid_links)\n \t\tlink_id = ffs(sta-\u003esta.valid_links) - 1;\n \n@@ -4914,20 +4938,14 @@ static void ieee80211_rx_8023(struct ieee80211_rx_data *rx,\n {\n \tstruct ieee80211_sta_rx_stats *stats;\n \tstruct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(rx-\u003eskb);\n-\tstruct sta_info *sta = rx-\u003esta;\n-\tstruct link_sta_info *link_sta;\n+\tstruct link_sta_info *link_sta = rx-\u003elink_sta;\n \tstruct sk_buff *skb = rx-\u003eskb;\n \tvoid *sa = skb-\u003edata + ETH_ALEN;\n \tvoid *da = skb-\u003edata;\n \n-\tif (rx-\u003elink_id \u003e= 0) {\n-\t\tlink_sta = rcu_dereference(sta-\u003elink[rx-\u003elink_id]);\n-\t\tif (WARN_ON_ONCE(!link_sta)) {\n-\t\t\tdev_kfree_skb(rx-\u003eskb);\n-\t\t\treturn;\n-\t\t}\n-\t} else {\n-\t\tlink_sta = \u0026sta-\u003edeflink;\n+\tif (WARN_ON_ONCE(!link_sta)) {\n+\t\tdev_kfree_skb(rx-\u003eskb);\n+\t\treturn;\n \t}\n \n \tstats = \u0026link_sta-\u003erx_stats;\n@@ -5154,6 +5172,9 @@ static bool ieee80211_invoke_fast_rx(struct ieee80211_rx_data *rx,\n \t\tgoto drop;\n \t}\n \n+\tstatus = IEEE80211_SKB_RXCB(skb);\n+\tstatus-\u003elink_id = rx-\u003elink_id \u003c 0 ? 0 : rx-\u003elink_id;\n+\n \tieee80211_rx_8023(rx, fast_rx, orig_len);\n \n \treturn true;\n@@ -5178,6 +5199,7 @@ static bool ieee80211_prepare_and_rx_handle(struct ieee80211_rx_data *rx,\n \tstruct ieee80211_hdr *hdr = (void *)skb-\u003edata;\n \tstruct link_sta_info *link_sta = rx-\u003elink_sta;\n \tstruct ieee80211_link_data *link = rx-\u003elink;\n+\tstruct ieee80211_rx_status *status;\n \n \trx-\u003eskb = skb;\n \n@@ -5221,6 +5243,9 @@ static bool ieee80211_prepare_and_rx_handle(struct ieee80211_rx_data *rx,\n \t\thdr = (struct ieee80211_hdr *)rx-\u003eskb-\u003edata;\n \t}\n \n+\tstatus = IEEE80211_SKB_RXCB(rx-\u003eskb);\n+\tstatus-\u003elink_id = rx-\u003elink_id \u003c 0 ? 0 : rx-\u003elink_id;\n+\n \tif (ieee80211_is_s1g_beacon(hdr-\u003eframe_control)) {\n \t\tieee80211_invoke_rx_handlers(rx);\n \t\treturn true;\n@@ -5256,22 +5281,19 @@ static bool ieee80211_prepare_and_rx_handle(struct ieee80211_rx_data *rx,\n }\n \n static void __ieee80211_rx_handle_8023(struct ieee80211_hw *hw,\n-\t\t\t\t struct ieee80211_sta *pubsta,\n+\t\t\t\t struct ieee80211_link_sta *link_pubsta,\n \t\t\t\t struct sk_buff *skb,\n \t\t\t\t struct list_head *list)\n {\n \tstruct ieee80211_local *local = hw_to_local(hw);\n-\tstruct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);\n \tstruct ieee80211_fast_rx *fast_rx;\n \tstruct ieee80211_rx_data rx;\n \tstruct sta_info *sta;\n-\tint link_id = -1;\n \n \tmemset(\u0026rx, 0, sizeof(rx));\n \trx.skb = skb;\n \trx.local = local;\n \trx.list = list;\n-\trx.link_id = -1;\n \n \tI802_DEBUG_INC(local-\u003edot11ReceivedFragmentCount);\n \n@@ -5279,23 +5301,15 @@ static void __ieee80211_rx_handle_8023(struct ieee80211_hw *hw,\n \tif (skb-\u003elen \u003c sizeof(struct ethhdr))\n \t\tgoto drop;\n \n-\tif (!pubsta)\n+\tif (!link_pubsta)\n \t\tgoto drop;\n \n-\tif (status-\u003elink_valid)\n-\t\tlink_id = status-\u003elink_id;\n-\n-\t/*\n-\t * TODO: Should the frame be dropped if the right link_id is not\n-\t * available? Or may be it is fine in the current form to proceed with\n-\t * the frame processing because with frame being in 802.3 format,\n-\t * link_id is used only for stats purpose and updating the stats on\n-\t * the deflink is fine?\n-\t */\n-\tsta = container_of(pubsta, struct sta_info, sta);\n-\tif (!ieee80211_rx_data_set_sta(\u0026rx, sta, link_id))\n+\tsta = container_of(link_pubsta-\u003esta, struct sta_info, sta);\n+\tif (!ieee80211_rx_data_set_sta(\u0026rx, sta, link_pubsta-\u003elink_id))\n \t\tgoto drop;\n \n+\trx.link_id = link_pubsta-\u003elink_id;\n+\n \tfast_rx = rcu_dereference(rx.sta-\u003efast_rx);\n \tif (!fast_rx)\n \t\tgoto drop;\n@@ -5307,59 +5321,19 @@ static void __ieee80211_rx_handle_8023(struct ieee80211_hw *hw,\n \tdev_kfree_skb(skb);\n }\n \n-static bool ieee80211_rx_for_interface(struct ieee80211_rx_data *rx,\n-\t\t\t\t struct sk_buff *skb, bool consume)\n+static bool ieee80211_rx_valid_freq(int freq, struct ieee80211_link_data *link)\n {\n-\tstruct link_sta_info *link_sta;\n-\tstruct ieee80211_hdr *hdr = (void *)skb-\u003edata;\n-\tstruct sta_info *sta;\n-\tint link_id = -1;\n-\n-\tif (ieee80211_is_s1g_beacon(hdr-\u003eframe_control)) {\n-\t\tif (!ieee80211_rx_data_set_sta(rx, NULL, -1))\n-\t\t\treturn false;\n-\n-\t\treturn ieee80211_prepare_and_rx_handle(rx, skb, consume);\n-\t}\n+\tstruct ieee80211_chanctx_conf *conf;\n \n-\t/*\n-\t * Look up link station first, in case there's a\n-\t * chance that they might have a link address that\n-\t * is identical to the MLD address, that way we'll\n-\t * have the link information if needed.\n-\t */\n-\tlink_sta = link_sta_info_get_bss(rx-\u003esdata, hdr-\u003eaddr2);\n-\tif (link_sta) {\n-\t\tsta = link_sta-\u003esta;\n-\t\tlink_id = link_sta-\u003elink_id;\n-\t} else {\n-\t\tstruct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);\n-\n-\t\tsta = sta_info_get_bss(rx-\u003esdata, hdr-\u003eaddr2);\n-\t\tif (status-\u003elink_valid) {\n-\t\t\tlink_id = status-\u003elink_id;\n-\t\t} else if (ieee80211_vif_is_mld(\u0026rx-\u003esdata-\u003evif) \u0026\u0026\n-\t\t\t status-\u003efreq) {\n-\t\t\tstruct ieee80211_link_data *link;\n-\t\t\tstruct ieee80211_chanctx_conf *conf;\n-\n-\t\t\tfor_each_link_data_rcu(rx-\u003esdata, link) {\n-\t\t\t\tconf = rcu_dereference(link-\u003econf-\u003echanctx_conf);\n-\t\t\t\tif (!conf || !conf-\u003edef.chan)\n-\t\t\t\t\tcontinue;\n-\n-\t\t\t\tif (status-\u003efreq == conf-\u003edef.chan-\u003ecenter_freq) {\n-\t\t\t\t\tlink_id = link-\u003elink_id;\n-\t\t\t\t\tbreak;\n-\t\t\t\t}\n-\t\t\t}\n-\t\t}\n-\t}\n+\tif (!freq || link-\u003esdata-\u003evif.type == NL80211_IFTYPE_NAN ||\n+\t link-\u003esdata-\u003evif.type == NL80211_IFTYPE_NAN_DATA)\n+\t\treturn true;\n \n-\tif (!ieee80211_rx_data_set_sta(rx, sta, link_id))\n+\tconf = rcu_dereference(link-\u003econf-\u003echanctx_conf);\n+\tif (!conf || !conf-\u003edef.chan)\n \t\treturn false;\n \n-\treturn ieee80211_prepare_and_rx_handle(rx, skb, consume);\n+\treturn freq == conf-\u003edef.chan-\u003ecenter_freq;\n }\n \n /*\n@@ -5367,18 +5341,20 @@ static bool ieee80211_rx_for_interface(struct ieee80211_rx_data *rx,\n * be called with rcu_read_lock protection.\n */\n static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw,\n-\t\t\t\t\t struct ieee80211_sta *pubsta,\n+\t\t\t\t\t struct ieee80211_link_sta *link_pubsta,\n \t\t\t\t\t struct sk_buff *skb,\n \t\t\t\t\t struct list_head *list)\n {\n \tstruct ieee80211_local *local = hw_to_local(hw);\n-\tstruct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);\n \tstruct ieee80211_sub_if_data *sdata;\n+\tstruct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);\n \tstruct ieee80211_hdr *hdr;\n+\tstruct link_sta_info *link_sta;\n+\tstruct sta_info *sta;\n \t__le16 fc;\n \tstruct ieee80211_rx_data rx;\n-\tstruct ieee80211_sub_if_data *prev;\n \tstruct rhlist_head *tmp;\n+\tbool rx_data_pending;\n \tint err = 0;\n \n \tfc = ((struct ieee80211_hdr *)skb-\u003edata)-\u003eframe_control;\n@@ -5386,7 +5362,6 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw,\n \trx.skb = skb;\n \trx.local = local;\n \trx.list = list;\n-\trx.link_id = -1;\n \n \tif (ieee80211_is_data(fc) || ieee80211_is_mgmt(fc))\n \t\tI802_DEBUG_INC(local-\u003edot11ReceivedFragmentCount);\n@@ -5426,97 +5401,102 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw,\n \t\t ieee80211_is_s1g_beacon(hdr-\u003eframe_control)))\n \t\tieee80211_scan_rx(local, skb);\n \n+\t/*\n+\t * RX of a data frame should only happen to existing stations.\n+\t * It is therefore more efficient to use the one provided by the driver\n+\t * or search based on the station's address.\n+\t *\n+\t * Note we will fall through and handle a spurious data frame in the\n+\t * same way as a management frame.\n+\t */\n \tif (ieee80211_is_data(fc)) {\n-\t\tstruct sta_info *sta, *prev_sta;\n-\t\tint link_id = -1;\n-\n-\t\tif (status-\u003elink_valid)\n-\t\t\tlink_id = status-\u003elink_id;\n-\n-\t\tif (pubsta) {\n-\t\t\tsta = container_of(pubsta, struct sta_info, sta);\n-\t\t\tif (!ieee80211_rx_data_set_sta(\u0026rx, sta, link_id))\n-\t\t\t\tgoto out;\n-\n-\t\t\t/*\n-\t\t\t * In MLO connection, fetch the link_id using addr2\n-\t\t\t * when the driver does not pass link_id in status.\n-\t\t\t * When the address translation is already performed by\n-\t\t\t * driver/hw, the valid link_id must be passed in\n-\t\t\t * status.\n-\t\t\t */\n-\n-\t\t\tif (!status-\u003elink_valid \u0026\u0026 pubsta-\u003emlo) {\n-\t\t\t\tstruct link_sta_info *link_sta;\n-\n-\t\t\t\tlink_sta = link_sta_info_get_bss(rx.sdata,\n-\t\t\t\t\t\t\t\t hdr-\u003eaddr2);\n-\t\t\t\tif (!link_sta)\n-\t\t\t\t\tgoto out;\n-\n-\t\t\t\tif (!ieee80211_rx_data_set_link(\u0026rx,\n-\t\t\t\t\t\t\t\tlink_sta-\u003elink_id))\n-\t\t\t\t\tgoto out;\n-\t\t\t}\n-\n-\t\t\tif (ieee80211_prepare_and_rx_handle(\u0026rx, skb, true))\n+\t\tif (link_pubsta) {\n+\t\t\tsta = container_of(link_pubsta-\u003esta, struct sta_info,\n+\t\t\t\t\t sta);\n+\t\t\tlink_sta = rcu_dereference(sta-\u003elink[link_pubsta-\u003elink_id]);\n+\n+\t\t\trx.sdata = sta-\u003esdata;\n+\t\t\tif (ieee80211_rx_data_set_link_sta(\u0026rx, link_sta) \u0026\u0026\n+\t\t\t ieee80211_prepare_and_rx_handle(\u0026rx, skb, true))\n \t\t\t\treturn;\n+\n \t\t\tgoto out;\n \t\t}\n \n-\t\tprev_sta = NULL;\n+\t\trx_data_pending = false;\n \n+\t\t/* Search for stations on non-MLD interfaces */\n \t\tfor_each_sta_info(local, hdr-\u003eaddr2, sta, tmp) {\n-\t\t\tif (!prev_sta) {\n-\t\t\t\tprev_sta = sta;\n-\t\t\t\tcontinue;\n-\t\t\t}\n+\t\t\tstruct ieee80211_link_data *link;\n \n-\t\t\trx.sdata = prev_sta-\u003esdata;\n-\t\t\tif (!status-\u003elink_valid \u0026\u0026 prev_sta-\u003esta.mlo) {\n-\t\t\t\tstruct link_sta_info *link_sta;\n+\t\t\tif (ieee80211_vif_is_mld(\u0026sta-\u003esdata-\u003evif))\n+\t\t\t\tcontinue;\n \n-\t\t\t\tlink_sta = link_sta_info_get_bss(rx.sdata,\n-\t\t\t\t\t\t\t\t hdr-\u003eaddr2);\n-\t\t\t\tif (!link_sta)\n-\t\t\t\t\tcontinue;\n+\t\t\tlink = \u0026sta-\u003esdata-\u003edeflink;\n+\t\t\tif (!ieee80211_rx_valid_freq(status-\u003efreq, link))\n+\t\t\t\tcontinue;\n \n-\t\t\t\tlink_id = link_sta-\u003elink_id;\n+\t\t\tif (rx_data_pending) {\n+\t\t\t\tieee80211_prepare_and_rx_handle(\u0026rx, skb,\n+\t\t\t\t\t\t\t\tfalse);\n+\t\t\t\trx_data_pending = false;\n \t\t\t}\n \n-\t\t\tif (!ieee80211_rx_data_set_sta(\u0026rx, prev_sta, link_id))\n-\t\t\t\tgoto out;\n-\n-\t\t\tieee80211_prepare_and_rx_handle(\u0026rx, skb, false);\n+\t\t\trx.sdata = sta-\u003esdata;\n+\t\t\tif (!ieee80211_rx_data_set_link_sta(\u0026rx, \u0026sta-\u003edeflink))\n+\t\t\t\tcontinue;\n \n-\t\t\tprev_sta = sta;\n+\t\t\trx_data_pending = true;\n \t\t}\n \n-\t\tif (prev_sta) {\n-\t\t\trx.sdata = prev_sta-\u003esdata;\n-\t\t\tif (!status-\u003elink_valid \u0026\u0026 prev_sta-\u003esta.mlo) {\n-\t\t\t\tstruct link_sta_info *link_sta;\n+\t\t/* And search stations on MLD interfaces */\n+\t\tfor_each_link_sta_info(local, hdr-\u003eaddr2, link_sta, tmp) {\n+\t\t\tstruct ieee80211_link_data *link;\n \n-\t\t\t\tlink_sta = link_sta_info_get_bss(rx.sdata,\n-\t\t\t\t\t\t\t\t hdr-\u003eaddr2);\n-\t\t\t\tif (!link_sta)\n-\t\t\t\t\tgoto out;\n+\t\t\tsta = link_sta-\u003esta;\n+\t\t\tsdata =\tsta-\u003esdata;\n+\t\t\tlink = rcu_dereference(sdata-\u003elink[link_sta-\u003elink_id]);\n \n-\t\t\t\tlink_id = link_sta-\u003elink_id;\n+\t\t\tif (!ieee80211_rx_valid_freq(status-\u003efreq, link))\n+\t\t\t\tcontinue;\n+\n+\t\t\tif (rx_data_pending) {\n+\t\t\t\tieee80211_prepare_and_rx_handle(\u0026rx, skb,\n+\t\t\t\t\t\t\t\tfalse);\n+\t\t\t\trx_data_pending = false;\n \t\t\t}\n \n-\t\t\tif (!ieee80211_rx_data_set_sta(\u0026rx, prev_sta, link_id))\n-\t\t\t\tgoto out;\n+\t\t\trx.sdata = sta-\u003esdata;\n+\t\t\tif (!ieee80211_rx_data_set_link_sta(\u0026rx, link_sta))\n+\t\t\t\tcontinue;\n+\n+\t\t\trx_data_pending = true;\n+\t\t}\n \n+\t\tif (rx_data_pending) {\n \t\t\tif (ieee80211_prepare_and_rx_handle(\u0026rx, skb, true))\n \t\t\t\treturn;\n+\n \t\t\tgoto out;\n \t\t}\n+\n+\t\t/* fall through, e.g. for spurious frame notification */\n \t}\n \n-\tprev = NULL;\n+\t/*\n+\t * This is a management frame (or a data frame without a station) and\n+\t * it will be delivered independent of whether a station exists,\n+\t * so iterate the interfaces.\n+\t */\n+\trx_data_pending = false;\n+\n+\t/* We expect the driver to provide frequency information */\n+\tif (WARN_ON_ONCE(!local-\u003eemulate_chanctx \u0026\u0026 !status-\u003efreq))\n+\t\tgoto out;\n \n \tlist_for_each_entry_rcu(sdata, \u0026local-\u003einterfaces, list) {\n+\t\tstruct ieee80211_link_data *link = NULL;\n+\n \t\tif (!ieee80211_sdata_running(sdata))\n \t\t\tcontinue;\n \n@@ -5524,30 +5504,85 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw,\n \t\t sdata-\u003evif.type == NL80211_IFTYPE_AP_VLAN)\n \t\t\tcontinue;\n \n+\t\tlink_sta = NULL;\n+\t\tlink = NULL;\n+\n+\t\t/* Try to resolve the station */\n+\t\tif (!ieee80211_vif_is_mld(\u0026sdata-\u003evif)) {\n+\t\t\tsta = sta_info_get_bss(sdata, hdr-\u003eaddr2);\n+\n+\t\t\tif (sta) {\n+\t\t\t\tlink_sta = \u0026sta-\u003edeflink;\n+\t\t\t\tlink = \u0026sdata-\u003edeflink;\n+\t\t\t}\n+\t\t} else {\n+\t\t\tlink_sta = link_sta_info_get_bss(sdata, hdr-\u003eaddr2);\n+\n+\t\t\tif (link_sta)\n+\t\t\t\tlink = rcu_dereference(sdata-\u003elink[link_sta-\u003elink_id]);\n+\t\t}\n+\n \t\t/*\n-\t\t * frame is destined for this interface, but if it's\n-\t\t * not also for the previous one we handle that after\n-\t\t * the loop to avoid copying the SKB once too much\n+\t\t * If we found a STA and it has a valid link information, then\n+\t\t * RX using the station.\n \t\t */\n+\t\tif (link_sta \u0026\u0026 link \u0026\u0026\n+\t\t ieee80211_rx_valid_freq(status-\u003efreq, link)) {\n+\t\t\tif (rx_data_pending) {\n+\t\t\t\tieee80211_prepare_and_rx_handle(\u0026rx, skb, false);\n+\t\t\t\trx_data_pending = false;\n+\t\t\t}\n+\n+\t\t\t/* No valid_links check as we need to RX beacons */\n+\n+\t\t\trx.sdata = sdata;\n+\t\t\tif (ieee80211_rx_data_set_link_sta(\u0026rx, link_sta))\n+\t\t\t\trx_data_pending = true;\n \n-\t\tif (!prev) {\n-\t\t\tprev = sdata;\n \t\t\tcontinue;\n \t\t}\n \n-\t\trx.sdata = prev;\n-\t\tieee80211_rx_for_interface(\u0026rx, skb, false);\n+\t\t/* No station, try to resolve the link and RX */\n+\t\tif (ieee80211_vif_is_mld(\u0026sdata-\u003evif)) {\n+\t\t\tstruct ieee80211_chanctx_conf *conf;\n+\t\t\tbool found = false;\n \n-\t\tprev = sdata;\n-\t}\n+\t\t\tfor_each_link_data_rcu(sdata, link) {\n+\t\t\t\tconf = rcu_dereference(link-\u003econf-\u003echanctx_conf);\n+\t\t\t\tif (!conf || !conf-\u003edef.chan)\n+\t\t\t\t\tcontinue;\n \n-\tif (prev) {\n-\t\trx.sdata = prev;\n+\t\t\t\tif (status-\u003efreq == conf-\u003edef.chan-\u003ecenter_freq) {\n+\t\t\t\t\tfound = true;\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n+\t\t\t}\n \n-\t\tif (ieee80211_rx_for_interface(\u0026rx, skb, true))\n-\t\t\treturn;\n+\t\t\tif (!found)\n+\t\t\t\tlink = \u0026sdata-\u003edeflink;\n+\t\t} else {\n+\t\t\tlink = \u0026sdata-\u003edeflink;\n+\t\t}\n+\n+\t\tif (rx_data_pending) {\n+\t\t\tieee80211_prepare_and_rx_handle(\u0026rx, skb, false);\n+\t\t\trx_data_pending = false;\n+\t\t}\n+\n+\t\trx.sdata = sdata;\n+\t\trx.local = sdata-\u003elocal;\n+\t\trx.link = link;\n+\t\trx.link_id = link-\u003elink_id;\n+\t\trx.sta = NULL;\n+\t\trx.link_sta = NULL;\n+\n+\t\trx_data_pending = true;\n \t}\n \n+\tif (rx_data_pending \u0026\u0026\n+\t ieee80211_prepare_and_rx_handle(\u0026rx, skb, true))\n+\t\treturn;\n+\n out:\n \tdev_kfree_skb(skb);\n }\n@@ -5556,7 +5591,8 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw,\n * This is the receive path handler. It is called by a low level driver when an\n * 802.11 MPDU is received from the hardware.\n */\n-void ieee80211_rx_list(struct ieee80211_hw *hw, struct ieee80211_sta *pubsta,\n+void ieee80211_rx_list(struct ieee80211_hw *hw,\n+\t\t struct ieee80211_link_sta *link_pubsta,\n \t\t struct sk_buff *skb, struct list_head *list)\n {\n \tstruct ieee80211_local *local = hw_to_local(hw);\n@@ -5694,9 +5730,6 @@ void ieee80211_rx_list(struct ieee80211_hw *hw, struct ieee80211_sta *pubsta,\n \t\t}\n \t}\n \n-\tif (WARN_ON_ONCE(status-\u003elink_id \u003e= IEEE80211_LINK_UNSPECIFIED))\n-\t\tgoto drop;\n-\n \tstatus-\u003erx_flags = 0;\n \n \tkcov_remote_start_common(skb_get_kcov_handle(skb));\n@@ -5715,9 +5748,10 @@ void ieee80211_rx_list(struct ieee80211_hw *hw, struct ieee80211_sta *pubsta,\n \t\t\tieee80211_tpt_led_trig_rx(local, skb-\u003elen);\n \n \t\tif (status-\u003eflag \u0026 RX_FLAG_8023)\n-\t\t\t__ieee80211_rx_handle_8023(hw, pubsta, skb, list);\n+\t\t\t__ieee80211_rx_handle_8023(hw, link_pubsta, skb, list);\n \t\telse\n-\t\t\t__ieee80211_rx_handle_packet(hw, pubsta, skb, list);\n+\t\t\t__ieee80211_rx_handle_packet(hw, link_pubsta, skb,\n+\t\t\t\t\t\t list);\n \t}\n \n \tkcov_remote_stop();\n@@ -5727,7 +5761,8 @@ void ieee80211_rx_list(struct ieee80211_hw *hw, struct ieee80211_sta *pubsta,\n }\n EXPORT_SYMBOL(ieee80211_rx_list);\n \n-void ieee80211_rx_napi(struct ieee80211_hw *hw, struct ieee80211_sta *pubsta,\n+void ieee80211_rx_napi(struct ieee80211_hw *hw,\n+\t\t struct ieee80211_link_sta *link_pubsta,\n \t\t struct sk_buff *skb, struct napi_struct *napi)\n {\n \tstruct sk_buff *tmp;\n@@ -5740,7 +5775,7 @@ void ieee80211_rx_napi(struct ieee80211_hw *hw, struct ieee80211_sta *pubsta,\n \t * receive processing\n \t */\n \trcu_read_lock();\n-\tieee80211_rx_list(hw, pubsta, skb, \u0026list);\n+\tieee80211_rx_list(hw, link_pubsta, skb, \u0026list);\n \trcu_read_unlock();\n \n \tif (!napi) {\ndiff --git a/net/mac80211/scan.c b/net/mac80211/scan.c\nindex eeff230bd909f..324c2b6d7ce1b 100644\n--- a/net/mac80211/scan.c\n+++ b/net/mac80211/scan.c\n@@ -9,7 +9,7 @@\n * Copyright 2007, Michael Wu \u003cflamingice@sourmilk.net\u003e\n * Copyright 2013-2015 Intel Mobile Communications GmbH\n * Copyright 2016-2017 Intel Deutschland GmbH\n- * Copyright (C) 2018-2025 Intel Corporation\n+ * Copyright (C) 2018-2026 Intel Corporation\n */\n \n #include \u003clinux/if_arp.h\u003e\n@@ -204,12 +204,10 @@ ieee80211_bss_info_update(struct ieee80211_local *local,\n \t\t * an indication on which of the links the frame was received\n \t\t */\n \t\tif (ieee80211_vif_is_mld(\u0026scan_sdata-\u003evif)) {\n-\t\t\tif (rx_status-\u003elink_valid) {\n-\t\t\t\ts8 link_id = rx_status-\u003elink_id;\n+\t\t\ts8 link_id = rx_status-\u003elink_id;\n \n-\t\t\t\tlink_conf =\n-\t\t\t\t\trcu_dereference(scan_sdata-\u003evif.link_conf[link_id]);\n-\t\t\t}\n+\t\t\tlink_conf =\n+\t\t\t\trcu_dereference(scan_sdata-\u003evif.link_conf[link_id]);\n \t\t} else {\n \t\t\tlink_conf = \u0026scan_sdata-\u003evif.bss_conf;\n \t\t}\ndiff --git a/net/mac80211/tdls.c b/net/mac80211/tdls.c\nindex dc2f662fe4c46..e17d492be5902 100644\n--- a/net/mac80211/tdls.c\n+++ b/net/mac80211/tdls.c\n@@ -1066,7 +1066,7 @@ ieee80211_tdls_prep_mgmt_packet(struct wiphy *wiphy, struct net_device *dev,\n \t}\n \n \tif (action_code == WLAN_PUB_ACTION_TDLS_DISCOVER_RES) {\n-\t\tieee80211_tx_skb_tid(sdata, skb, 7, link_id);\n+\t\tieee80211_tx_skb_tid(sdata, skb, sta, 7, link_id);\n \t\treturn 0;\n \t}\n \ndiff --git a/net/mac80211/tx.c b/net/mac80211/tx.c\nindex 3a1e2c9e1565e..6f52e88402371 100644\n--- a/net/mac80211/tx.c\n+++ b/net/mac80211/tx.c\n@@ -6436,7 +6436,8 @@ void ieee80211_unreserve_tid(struct ieee80211_sta *pubsta, u8 tid)\n EXPORT_SYMBOL(ieee80211_unreserve_tid);\n \n void __ieee80211_tx_skb_tid_band(struct ieee80211_sub_if_data *sdata,\n-\t\t\t\t struct sk_buff *skb, int tid, int link_id,\n+\t\t\t\t struct sk_buff *skb, struct sta_info *sta,\n+\t\t\t\t int tid, int link_id,\n \t\t\t\t enum nl80211_band band)\n {\n \tconst struct ieee80211_hdr *hdr = (void *)skb-\u003edata;\n@@ -6488,12 +6489,13 @@ void __ieee80211_tx_skb_tid_band(struct ieee80211_sub_if_data *sdata,\n \t */\n \tlocal_bh_disable();\n \tIEEE80211_SKB_CB(skb)-\u003eband = band;\n-\tieee80211_xmit(sdata, NULL, skb);\n+\tieee80211_xmit(sdata, sta, skb);\n \tlocal_bh_enable();\n }\n \n void ieee80211_tx_skb_tid(struct ieee80211_sub_if_data *sdata,\n-\t\t\t struct sk_buff *skb, int tid, int link_id)\n+\t\t\t struct sk_buff *skb, struct sta_info *sta,\n+\t\t\t int tid, int link_id)\n {\n \tstruct ieee80211_chanctx_conf *chanctx_conf;\n \tenum nl80211_band band;\n@@ -6519,7 +6521,7 @@ void ieee80211_tx_skb_tid(struct ieee80211_sub_if_data *sdata,\n \t\tband = 0;\n \t}\n \n-\t__ieee80211_tx_skb_tid_band(sdata, skb, tid, link_id, band);\n+\t__ieee80211_tx_skb_tid_band(sdata, skb, sta, tid, link_id, band);\n \trcu_read_unlock();\n }\n \ndiff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c\nindex 44f2bad086705..437bebcc02748 100644\n--- a/net/wireless/nl80211.c\n+++ b/net/wireless/nl80211.c\n@@ -1096,6 +1096,7 @@ static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = {\n \t[NL80211_ATTR_NPCA_PUNCT_BITMAP] =\n \t\tNLA_POLICY_FULL_RANGE(NLA_U32, \u0026nl80211_punct_bitmap_range),\n \t[NL80211_ATTR_STA_DUMP_LINK_STATS] = { .type = NLA_FLAG },\n+\t[NL80211_ATTR_FRAME_CMD_NO_STA] = { .type = NLA_FLAG },\n };\n \n /* policy for the key attributes */\n@@ -14860,6 +14861,9 @@ static int nl80211_tx_mgmt(struct sk_buff *skb, struct genl_info *info)\n \t !(wdev-\u003evalid_links \u0026 BIT(params.link_id)))\n \t\treturn -EINVAL;\n \n+\tparams.no_sta =\n+\t\tnla_get_flag(info-\u003eattrs[NL80211_ATTR_FRAME_CMD_NO_STA]);\n+\n \tparams.buf = nla_data(info-\u003eattrs[NL80211_ATTR_FRAME]);\n \tparams.len = nla_len(info-\u003eattrs[NL80211_ATTR_FRAME]);\n \n@@ -22024,7 +22028,9 @@ int nl80211_send_mgmt(struct cfg80211_registered_device *rdev,\n \t (info-\u003eack_tstamp \u0026\u0026 nla_put_u64_64bit(msg,\n \t\t\t\t\t\t NL80211_ATTR_TX_HW_TIMESTAMP,\n \t\t\t\t\t\t info-\u003eack_tstamp,\n-\t\t\t\t\t\t NL80211_ATTR_PAD)))\n+\t\t\t\t\t\t NL80211_ATTR_PAD)) ||\n+\t (info-\u003eno_sta \u0026\u0026\n+\t nla_put_flag(msg, NL80211_ATTR_FRAME_CMD_NO_STA)))\n \t\tgoto nla_put_failure;\n \n \tgenlmsg_end(msg, hdr);\n"
} |
| 2/1 | 2026/08/24 20:41 | agent | patch-evaluator |
0mModel:best-expensive Args: null Results: {
"EnableConfigs": [
"MAC80211",
"MAC80211_HWSIM",
"CFG80211"
],
"FocusSymbols": [
"ieee80211_rx_napi",
"ieee80211_rx_list",
"__ieee80211_rx_handle_packet",
"__ieee80211_rx_handle_8023",
"ieee80211_prepare_and_rx_handle",
"ieee80211_rx_handlers",
"nl80211_tx_mgmt",
"nl80211_send_mgmt"
],
"Reasoning": "The patch modifies the core mac80211 RX path, changing the signature of ieee80211_rx_napi and ieee80211_rx_list to take a link_sta instead of a sta. It also modifies how link_id is handled in RX status. These changes affect the core 802.11 stack and virtual drivers like mac80211_hwsim, making them reachable and worth fuzzing.",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer. Your job is to review a provided patch series and determine if it makes functional changes to the kernel that should be fuzzed. IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively use your code access tools to examine the actual source code and confirm any assumptions. Return WorthFuzzing=false if the patch only contains: - Modifications to Documentation/, Kconfig files, or code comments. - Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints. - Changes to numeric constants or macros that do not functionally alter execution flow. - Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim). - Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed, ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU. - Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove) that are executed only during PCI hot-unplug or sysfs driver unbind operations. If it modifies reachable core kernel logic, drivers, or architectures, use your code search tools to verify the code can be executed, then return WorthFuzzing=true. When returning WorthFuzzing=true, you MUST ALSO: 1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols. Avoid listing generic hot-path functions to prevent skewed test distributions. Prefer non-static, non-inlined API entrypoint functions over internal static helper functions (which are inlined by the compiler and do not have distinct symbol addresses). 2. Identify any specific CONFIG_ options required to properly test this new/modified feature. Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions. Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4"). Prefer calling several tools at the same time to save round-trips. Use set-results tool to provide results of the analysis. It must be called exactly once before the final reply. Ignore results of this tool. Prompt: For your convenience, here is the diff of the changes:
commit d20e704263f8e8096339e5e49f4820aff11946db
Author: syz-cluster <triage@syzkaller.com>
Date: Mon Aug 24 20:41:33 2026 +0000
syz-cluster: applied patch under review
diff --git a/drivers/net/wireless/ath/ath11k/dp_rx.c b/drivers/net/wireless/ath/ath11k/dp_rx.c
index 33425707c0842..fbb3d18f55ddd 100644
--- a/drivers/net/wireless/ath/ath11k/dp_rx.c
+++ b/drivers/net/wireless/ath/ath11k/dp_rx.c
@@ -2500,7 +2500,7 @@ static void ath11k_dp_rx_deliver_msdu(struct ath11k *ar, struct napi_struct *nap
!(is_mcbc && rx_status->flag & RX_FLAG_DECRYPTED))
rx_status->flag |= RX_FLAG_8023;
- ieee80211_rx_napi(ar->hw, pubsta, msdu, napi);
+ ieee80211_rx_napi(ar->hw, pubsta ? &pubsta->deflink : NULL, msdu, napi);
}
static bool ath11k_dp_rx_check_nwifi_hdr_len_valid(struct ath11k_base *ab,
diff --git a/drivers/net/wireless/ath/ath12k/dp_mon.c b/drivers/net/wireless/ath/ath12k/dp_mon.c
index 7d5be77b081f8..1faf1896d0ab1 100644
--- a/drivers/net/wireless/ath/ath12k/dp_mon.c
+++ b/drivers/net/wireless/ath/ath12k/dp_mon.c
@@ -503,8 +503,6 @@ void ath12k_dp_mon_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev,
struct ieee80211_rx_status *rx_status;
struct ieee80211_radiotap_he *he = NULL;
- status->link_valid = 0;
-
if ((status->encoding == RX_ENC_HE) && !(status->flag & RX_FLAG_RADIOTAP_HE) &&
!(status->flag & RX_FLAG_SKIP_MONITOR)) {
he = skb_push(msdu, sizeof(known));
diff --git a/drivers/net/wireless/ath/ath12k/dp_rx.c b/drivers/net/wireless/ath/ath12k/dp_rx.c
index 8fa0e90b45316..41ac0a4072aeb 100644
--- a/drivers/net/wireless/ath/ath12k/dp_rx.c
+++ b/drivers/net/wireless/ath/ath12k/dp_rx.c
@@ -1375,6 +1375,7 @@ void ath12k_dp_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev, struct napi_struc
struct ath12k_dp *dp = dp_pdev->dp;
struct ieee80211_rx_status *rx_status;
struct ieee80211_sta *pubsta;
+ struct ieee80211_link_sta *link_pubsta = NULL;
struct ath12k_dp_peer *peer;
struct ath12k_skb_rxcb *rxcb = ATH12K_SKB_RXCB(msdu);
struct ieee80211_rx_status *status = rx_info->rx_status;
@@ -1384,9 +1385,19 @@ void ath12k_dp_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev, struct napi_struc
pubsta = peer ? peer->sta : NULL;
- status->link_valid = 0;
- if (pubsta && pubsta->valid_links)
- ath12k_hw_set_rx_link_id(dp->hw_params, peer, rxcb, status);
+ if (pubsta) {
+ if (pubsta->valid_links) {
+ int link_id =
+ ath12k_hw_get_rx_link_id(dp->hw_params, peer,
+ rxcb, status);
+
+ if (link_id >= 0)
+ link_pubsta =
+ rcu_dereference(pubsta->link[link_id]);
+ } else {
+ link_pubsta = &pubsta->deflink;
+ }
+ }
ath12k_dbg(dp->ab, ATH12K_DBG_DATA,
"rx skb %p len %u peer %pM %d %s sn %u %s%s%s%s%s%s%s%s%s%s rate_idx %u vht_nss %u freq %u band %u flag 0x%x fcs-err %i mic-err %i amsdu-more %i\n",
@@ -1422,7 +1433,8 @@ void ath12k_dp_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev, struct napi_struc
/* TODO: trace rx packet */
- ieee80211_rx_napi(ath12k_pdev_dp_to_hw(dp_pdev), pubsta, msdu, napi);
+ ieee80211_rx_napi(ath12k_pdev_dp_to_hw(dp_pdev), link_pubsta, msdu,
+ napi);
}
EXPORT_SYMBOL(ath12k_dp_rx_deliver_msdu);
diff --git a/drivers/net/wireless/ath/ath12k/hw.h b/drivers/net/wireless/ath/ath12k/hw.h
index 3ed38f8f2b48a..7a2345f883d70 100644
--- a/drivers/net/wireless/ath/ath12k/hw.h
+++ b/drivers/net/wireless/ath/ath12k/hw.h
@@ -247,9 +247,9 @@ struct ath12k_hw_ops {
bool (*dp_srng_is_tx_comp_ring)(int ring_num);
bool (*is_frame_link_agnostic)(struct ath12k_link_vif *arvif,
struct ieee80211_mgmt *mgmt);
- void (*set_rx_link_id)(struct ath12k_dp_peer *dp_peer,
- struct ath12k_skb_rxcb *rxcb,
- struct ieee80211_rx_status *status);
+ int (*get_rx_link_id)(struct ath12k_dp_peer *dp_peer,
+ struct ath12k_skb_rxcb *rxcb,
+ struct ieee80211_rx_status *status);
};
static inline
@@ -280,13 +280,15 @@ static inline int ath12k_hw_mac_id_to_srng_id(const struct ath12k_hw_params *hw,
return 0;
}
-static inline void ath12k_hw_set_rx_link_id(const struct ath12k_hw_params *hw,
- struct ath12k_dp_peer *dp_peer,
- struct ath12k_skb_rxcb *rxcb,
- struct ieee80211_rx_status *status)
+static inline int ath12k_hw_get_rx_link_id(const struct ath12k_hw_params *hw,
+ struct ath12k_dp_peer *dp_peer,
+ struct ath12k_skb_rxcb *rxcb,
+ struct ieee80211_rx_status *status)
{
- if (hw->hw_ops->set_rx_link_id)
- hw->hw_ops->set_rx_link_id(dp_peer, rxcb, status);
+ if (hw->hw_ops->get_rx_link_id)
+ return hw->hw_ops->get_rx_link_id(dp_peer, rxcb, status);
+
+ return -1;
}
struct ath12k_fw_ie {
diff --git a/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.c b/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.c
index 95d87dd67872f..8b722ee0ad8fa 100644
--- a/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.c
+++ b/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.c
@@ -2317,17 +2317,16 @@ ath12k_wifi7_dp_rxdesc_mpdu_valid(struct ath12k_base *ab,
return tlv_tag == HAL_RX_MPDU_START;
}
-void
-ath12k_wifi7_dp_rx_set_link_id_qcn9274(struct ath12k_dp_peer *dp_peer,
+int
+ath12k_wifi7_dp_rx_get_link_id_qcn9274(struct ath12k_dp_peer *dp_peer,
struct ath12k_skb_rxcb *rxcb,
struct ieee80211_rx_status *status)
{
- status->link_valid = 1;
- status->link_id = dp_peer->hw_links[rxcb->hw_link_id];
+ return dp_peer->hw_links[rxcb->hw_link_id];
}
-void
-ath12k_wifi7_dp_rx_set_link_id_wcn7850(struct ath12k_dp_peer *dp_peer,
+int
+ath12k_wifi7_dp_rx_get_link_id_wcn7850(struct ath12k_dp_peer *dp_peer,
struct ath12k_skb_rxcb *rxcb,
struct ieee80211_rx_status *status)
{
@@ -2341,10 +2340,9 @@ ath12k_wifi7_dp_rx_set_link_id_wcn7850(struct ath12k_dp_peer *dp_peer,
links_map = READ_ONCE(dp_peer->link_peers_map);
for_each_set_bit(i, &links_map, ATH12K_NUM_MAX_LINKS) {
link_peer = rcu_dereference(dp_peer->link_peers[i]);
- if (link_peer && link_peer->peer_id == rxcb->peer_id) {
- status->link_valid = 1;
- status->link_id = link_peer->link_id;
- return;
- }
+ if (link_peer && link_peer->peer_id == rxcb->peer_id)
+ return link_peer->link_id;
}
+
+ return -1;
}
diff --git a/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.h b/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.h
index 1d3a4788a2dd9..d68489ff7b037 100644
--- a/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.h
+++ b/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.h
@@ -57,10 +57,10 @@ ath12k_wifi7_dp_rxdesc_mpdu_valid(struct ath12k_base *ab,
struct hal_rx_desc *rx_desc);
int ath12k_wifi7_dp_rx_tid_delete_handler(struct ath12k_base *ab,
struct ath12k_dp_rx_tid_rxq *rx_tid);
-void ath12k_wifi7_dp_rx_set_link_id_qcn9274(struct ath12k_dp_peer *dp_peer,
- struct ath12k_skb_rxcb *rxcb,
- struct ieee80211_rx_status *status);
-void ath12k_wifi7_dp_rx_set_link_id_wcn7850(struct ath12k_dp_peer *dp_peer,
- struct ath12k_skb_rxcb *rxcb,
- struct ieee80211_rx_status *status);
+int ath12k_wifi7_dp_rx_get_link_id_qcn9274(struct ath12k_dp_peer *dp_peer,
+ struct ath12k_skb_rxcb *rxcb,
+ struct ieee80211_rx_status *status);
+int ath12k_wifi7_dp_rx_get_link_id_wcn7850(struct ath12k_dp_peer *dp_peer,
+ struct ath12k_skb_rxcb *rxcb,
+ struct ieee80211_rx_status *status);
#endif
diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hw.c b/drivers/net/wireless/ath/ath12k/wifi7/hw.c
index d890801d58223..a6ac4a40f7cc2 100644
--- a/drivers/net/wireless/ath/ath12k/wifi7/hw.c
+++ b/drivers/net/wireless/ath/ath12k/wifi7/hw.c
@@ -158,7 +158,7 @@ static const struct ath12k_hw_ops qcn9274_ops = {
.get_ring_selector = ath12k_wifi7_hw_get_ring_selector_qcn9274,
.dp_srng_is_tx_comp_ring = ath12k_wifi7_dp_srng_is_comp_ring_qcn9274,
.is_frame_link_agnostic = ath12k_wifi7_is_frame_link_agnostic_qcn9274,
- .set_rx_link_id = ath12k_wifi7_dp_rx_set_link_id_qcn9274,
+ .get_rx_link_id = ath12k_wifi7_dp_rx_get_link_id_qcn9274,
};
static const struct ath12k_hw_ops wcn7850_ops = {
@@ -169,7 +169,7 @@ static const struct ath12k_hw_ops wcn7850_ops = {
.get_ring_selector = ath12k_wifi7_hw_get_ring_selector_wcn7850,
.dp_srng_is_tx_comp_ring = ath12k_wifi7_dp_srng_is_comp_ring_wcn7850,
.is_frame_link_agnostic = ath12k_wifi7_is_frame_link_agnostic_wcn7850,
- .set_rx_link_id = ath12k_wifi7_dp_rx_set_link_id_wcn7850,
+ .get_rx_link_id = ath12k_wifi7_dp_rx_get_link_id_wcn7850,
};
static const struct ath12k_hw_ops qcc2072_ops = {
@@ -180,7 +180,7 @@ static const struct ath12k_hw_ops qcc2072_ops = {
.get_ring_selector = ath12k_wifi7_hw_get_ring_selector_wcn7850,
.dp_srng_is_tx_comp_ring = ath12k_wifi7_dp_srng_is_comp_ring_wcn7850,
.is_frame_link_agnostic = ath12k_wifi7_is_frame_link_agnostic_wcn7850,
- .set_rx_link_id = ath12k_wifi7_dp_rx_set_link_id_wcn7850,
+ .get_rx_link_id = ath12k_wifi7_dp_rx_get_link_id_wcn7850,
};
#define ATH12K_TX_RING_MASK_0 0x1
diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c
index d5160af60e00d..7e3655966f02d 100644
--- a/drivers/net/wireless/ath/ath12k/wmi.c
+++ b/drivers/net/wireless/ath/ath12k/wmi.c
@@ -7271,7 +7271,6 @@ static void ath12k_mgmt_rx_event(struct ath12k_base *ab, struct sk_buff *skb)
struct ath12k_wmi_mgmt_rx_arg rx_ev = {};
struct ath12k *ar;
struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
- struct ieee80211_sta *pubsta = NULL;
struct ath12k_dp_link_peer *peer;
struct ieee80211_hdr *hdr;
bool is_4addr_null_pkt;
@@ -7373,11 +7372,6 @@ static void ath12k_mgmt_rx_event(struct ath12k_base *ab, struct sk_buff *skb)
dev_kfree_skb(skb);
goto exit;
}
- pubsta = peer->sta;
- if (pubsta && pubsta->valid_links) {
- status->link_valid = 1;
- status->link_id = peer->link_id;
- }
spin_unlock_bh(&dp->dp_lock);
goto send_rx;
}
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/agg.c b/drivers/net/wireless/intel/iwlwifi/mld/agg.c
index 96102d2af2cf9..c45c47337509e 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/agg.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/agg.c
@@ -7,7 +7,8 @@
#include "hcmd.h"
static void
-iwl_mld_reorder_release_frames(struct iwl_mld *mld, struct ieee80211_sta *sta,
+iwl_mld_reorder_release_frames(struct iwl_mld *mld,
+ struct ieee80211_link_sta *link_sta,
struct napi_struct *napi,
struct iwl_mld_baid_data *baid_data,
struct iwl_mld_reorder_buffer *reorder_buf,
@@ -32,7 +33,7 @@ iwl_mld_reorder_release_frames(struct iwl_mld *mld, struct ieee80211_sta *sta,
while ((skb = __skb_dequeue(skb_list))) {
iwl_mld_pass_packet_to_mac80211(mld, napi, skb,
reorder_buf->queue,
- sta);
+ link_sta);
reorder_buf->num_stored--;
}
}
@@ -74,7 +75,7 @@ static void iwl_mld_release_frames_from_notif(struct iwl_mld *mld,
reorder_buf = &ba_data->reorder_buf[queue];
- iwl_mld_reorder_release_frames(mld, link_sta->sta, napi, ba_data,
+ iwl_mld_reorder_release_frames(mld, link_sta, napi, ba_data,
reorder_buf, nssn);
out_unlock:
rcu_read_unlock();
@@ -180,7 +181,7 @@ void iwl_mld_del_ba(struct iwl_mld *mld, int queue,
reorder_buf = &ba_data->reorder_buf[queue];
/* release all frames that are in the reorder buffer to the stack */
- iwl_mld_reorder_release_frames(mld, link_sta->sta, NULL,
+ iwl_mld_reorder_release_frames(mld, link_sta, NULL,
ba_data, reorder_buf,
ieee80211_sn_add(reorder_buf->head_sn,
ba_data->buf_size));
@@ -193,7 +194,7 @@ void iwl_mld_del_ba(struct iwl_mld *mld, int queue,
*/
enum iwl_mld_reorder_result
iwl_mld_reorder(struct iwl_mld *mld, struct napi_struct *napi,
- int queue, struct ieee80211_sta *sta,
+ int queue, struct ieee80211_link_sta *link_sta,
struct sk_buff *skb, struct iwl_rx_mpdu_desc *desc)
{
struct ieee80211_hdr *hdr = (void *)skb_mac_header(skb);
@@ -228,12 +229,12 @@ iwl_mld_reorder(struct iwl_mld *mld, struct napi_struct *napi,
return IWL_MLD_PASS_SKB;
/* no sta yet */
- if (IWL_FW_CHECK(mld, !sta,
+ if (IWL_FW_CHECK(mld, !link_sta,
"Got valid BAID without a valid station assigned - %d\n",
baid))
return IWL_MLD_PASS_SKB;
- mld_sta = iwl_mld_sta_from_mac80211(sta);
+ mld_sta = iwl_mld_sta_from_mac80211(link_sta->sta);
/* not a data packet */
if (!ieee80211_is_data_qos(hdr->frame_control) ||
@@ -324,7 +325,7 @@ iwl_mld_reorder(struct iwl_mld *mld, struct napi_struct *napi,
* will be released when the frame release notification arrives.
*/
if (!amsdu || last_subframe)
- iwl_mld_reorder_release_frames(mld, sta, napi, baid_data,
+ iwl_mld_reorder_release_frames(mld, link_sta, napi, baid_data,
buffer, nssn);
else if (buffer->num_stored == 1)
buffer->head_sn = nssn;
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/agg.h b/drivers/net/wireless/intel/iwlwifi/mld/agg.h
index 651c80d1c7cda..c6cd5fa219bed 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/agg.h
+++ b/drivers/net/wireless/intel/iwlwifi/mld/agg.h
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
/*
- * Copyright (C) 2024 Intel Corporation
+ * Copyright (C) 2024, 2026 Intel Corporation
*/
#ifndef __iwl_agg_h__
#define __iwl_agg_h__
@@ -106,7 +106,7 @@ int iwl_mld_ampdu_rx_stop(struct iwl_mld *mld, struct ieee80211_sta *sta,
enum iwl_mld_reorder_result
iwl_mld_reorder(struct iwl_mld *mld, struct napi_struct *napi,
- int queue, struct ieee80211_sta *sta,
+ int queue, struct ieee80211_link_sta *link_sta,
struct sk_buff *skb, struct iwl_rx_mpdu_desc *desc);
void iwl_mld_handle_frame_release_notif(struct iwl_mld *mld,
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/rx.c b/drivers/net/wireless/intel/iwlwifi/mld/rx.c
index 269439d789f46..02bdf6ccbcdc9 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/rx.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/rx.c
@@ -47,7 +47,8 @@ iwl_mld_fill_phy_data_from_mpdu(struct iwl_mld *mld,
}
static inline int iwl_mld_check_pn(struct iwl_mld *mld, struct sk_buff *skb,
- int queue, struct ieee80211_sta *sta)
+ int queue,
+ struct ieee80211_link_sta *link_sta)
{
struct ieee80211_hdr *hdr = (void *)skb_mac_header(skb);
struct ieee80211_rx_status *stats = IEEE80211_SKB_RXCB(skb);
@@ -71,13 +72,13 @@ static inline int iwl_mld_check_pn(struct iwl_mld *mld, struct sk_buff *skb,
return 0;
/* if we are here - this for sure is either CCMP or GCMP */
- if (!sta) {
+ if (!link_sta) {
IWL_DEBUG_DROP(mld,
"expected hw-decrypted unicast frame for station\n");
return -1;
}
- mld_sta = iwl_mld_sta_from_mac80211(sta);
+ mld_sta = iwl_mld_sta_from_mac80211(link_sta->sta);
extiv = (u8 *)hdr + ieee80211_hdrlen(hdr->frame_control);
keyidx = extiv[3] >> 6;
@@ -119,17 +120,17 @@ static inline int iwl_mld_check_pn(struct iwl_mld *mld, struct sk_buff *skb,
void iwl_mld_pass_packet_to_mac80211(struct iwl_mld *mld,
struct napi_struct *napi,
struct sk_buff *skb, int queue,
- struct ieee80211_sta *sta)
+ struct ieee80211_link_sta *link_sta)
{
KUNIT_STATIC_STUB_REDIRECT(iwl_mld_pass_packet_to_mac80211,
- mld, napi, skb, queue, sta);
+ mld, napi, skb, queue, link_sta);
- if (unlikely(iwl_mld_check_pn(mld, skb, queue, sta))) {
+ if (unlikely(iwl_mld_check_pn(mld, skb, queue, link_sta))) {
kfree_skb(skb);
return;
}
- ieee80211_rx_napi(mld->hw, sta, skb, napi);
+ ieee80211_rx_napi(mld->hw, link_sta, skb, napi);
}
EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(iwl_mld_pass_packet_to_mac80211);
@@ -1728,7 +1729,7 @@ static void iwl_mld_update_last_rx_timestamp(struct iwl_mld *mld, u8 baid)
* Sets *drop to true if the packet should be dropped.
* Returns the station if found, or NULL otherwise.
*/
-static struct ieee80211_sta *
+static struct ieee80211_link_sta *
iwl_mld_rx_with_sta(struct iwl_mld *mld, struct ieee80211_hdr *hdr,
struct sk_buff *skb,
const struct iwl_rx_mpdu_desc *mpdu_desc,
@@ -1764,11 +1765,6 @@ iwl_mld_rx_with_sta(struct iwl_mld *mld, struct ieee80211_hdr *hdr,
rx_status = IEEE80211_SKB_RXCB(skb);
- if (link_sta && sta->valid_links) {
- rx_status->link_valid = true;
- rx_status->link_id = link_sta->link_id;
- }
-
/* fill checksum */
if (ieee80211_is_data(hdr->frame_control) &&
pkt->len_n_flags & cpu_to_le32(FH_RSCSR_RPA_EN)) {
@@ -1803,10 +1799,10 @@ iwl_mld_rx_with_sta(struct iwl_mld *mld, struct ieee80211_hdr *hdr,
queue);
}
- return sta;
+ return link_sta;
}
-static int iwl_mld_rx_mgmt_prot(struct ieee80211_sta *sta,
+static int iwl_mld_rx_mgmt_prot(struct ieee80211_link_sta *link_sta,
struct ieee80211_hdr *hdr,
struct ieee80211_rx_status *rx_status,
u32 mpdu_status,
@@ -1820,7 +1816,6 @@ static int iwl_mld_rx_mgmt_prot(struct ieee80211_sta *sta,
struct ieee80211_key_conf *key;
const u8 *frame = (void *)hdr;
const u8 *mmie;
- u8 link_id;
if ((mpdu_status & IWL_RX_MPDU_STATUS_SEC_MASK) ==
IWL_RX_MPDU_STATUS_SEC_NONE)
@@ -1836,10 +1831,10 @@ static int iwl_mld_rx_mgmt_prot(struct ieee80211_sta *sta,
if (!ieee80211_is_beacon(hdr->frame_control))
return 0;
- if (!sta)
+ if (!link_sta)
return -1;
- mld_sta = iwl_mld_sta_from_mac80211(sta);
+ mld_sta = iwl_mld_sta_from_mac80211(link_sta->sta);
mld_vif = iwl_mld_vif_from_mac80211(mld_sta->vif);
/* key mismatch - will also report !MIC_OK but we shouldn't count it */
@@ -1853,8 +1848,7 @@ static int iwl_mld_rx_mgmt_prot(struct ieee80211_sta *sta,
return 0;
}
- link_id = rx_status->link_valid ? rx_status->link_id : 0;
- link = rcu_dereference(mld_vif->link[link_id]);
+ link = rcu_dereference(mld_vif->link[link_sta->link_id]);
if (WARN_ON_ONCE(!link))
return -1;
@@ -1905,7 +1899,7 @@ static int iwl_mld_rx_mgmt_prot(struct ieee80211_sta *sta,
}
static int iwl_mld_rx_crypto(struct iwl_mld *mld,
- struct ieee80211_sta *sta,
+ struct ieee80211_link_sta *link_sta,
struct ieee80211_hdr *hdr,
struct ieee80211_rx_status *rx_status,
struct iwl_rx_mpdu_desc *desc, int queue,
@@ -1915,7 +1909,7 @@ static int iwl_mld_rx_crypto(struct iwl_mld *mld,
if (unlikely(ieee80211_is_mgmt(hdr->frame_control) &&
!ieee80211_has_protected(hdr->frame_control)))
- return iwl_mld_rx_mgmt_prot(sta, hdr, rx_status, status,
+ return iwl_mld_rx_mgmt_prot(link_sta, hdr, rx_status, status,
le16_to_cpu(desc->mpdu_len));
if (!ieee80211_has_protected(hdr->frame_control) ||
@@ -2023,7 +2017,7 @@ void iwl_mld_rx_mpdu(struct iwl_mld *mld, struct napi_struct *napi,
struct iwl_rx_packet *pkt = rxb_addr(rxb);
struct iwl_mld_rx_phy_data phy_data = {};
struct iwl_rx_mpdu_desc *mpdu_desc = (void *)pkt->data;
- struct ieee80211_sta *sta;
+ struct ieee80211_link_sta *link_sta;
struct ieee80211_hdr *hdr;
struct sk_buff *skb;
size_t mpdu_desc_size = sizeof(*mpdu_desc);
@@ -2086,7 +2080,8 @@ void iwl_mld_rx_mpdu(struct iwl_mld *mld, struct napi_struct *napi,
rcu_read_lock();
- sta = iwl_mld_rx_with_sta(mld, hdr, skb, mpdu_desc, pkt, queue, &drop);
+ link_sta = iwl_mld_rx_with_sta(mld, hdr, skb, mpdu_desc, pkt, queue,
+ &drop);
if (drop)
goto drop;
@@ -2127,7 +2122,7 @@ void iwl_mld_rx_mpdu(struct iwl_mld *mld, struct napi_struct *napi,
iwl_mld_rx_fill_status(mld, link_id, hdr, skb, &phy_data);
- if (iwl_mld_rx_crypto(mld, sta, hdr, rx_status, mpdu_desc, queue,
+ if (iwl_mld_rx_crypto(mld, link_sta, hdr, rx_status, mpdu_desc, queue,
le32_to_cpu(pkt->len_n_flags), &crypto_len))
goto drop;
@@ -2140,7 +2135,8 @@ void iwl_mld_rx_mpdu(struct iwl_mld *mld, struct napi_struct *napi,
if (iwl_mld_time_sync_frame(mld, skb, hdr->addr2))
goto out;
- reorder_res = iwl_mld_reorder(mld, napi, queue, sta, skb, mpdu_desc);
+ reorder_res = iwl_mld_reorder(mld, napi, queue, link_sta, skb,
+ mpdu_desc);
switch (reorder_res) {
case IWL_MLD_PASS_SKB:
break;
@@ -2153,7 +2149,7 @@ void iwl_mld_rx_mpdu(struct iwl_mld *mld, struct napi_struct *napi,
goto drop;
}
- iwl_mld_pass_packet_to_mac80211(mld, napi, skb, queue, sta);
+ iwl_mld_pass_packet_to_mac80211(mld, napi, skb, queue, link_sta);
goto out;
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/rx.h b/drivers/net/wireless/intel/iwlwifi/mld/rx.h
index 573b89c3c9c61..b08c5ade3db8f 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/rx.h
+++ b/drivers/net/wireless/intel/iwlwifi/mld/rx.h
@@ -69,7 +69,7 @@ void iwl_mld_handle_rsc_notif(struct iwl_mld *mld,
void iwl_mld_pass_packet_to_mac80211(struct iwl_mld *mld,
struct napi_struct *napi,
struct sk_buff *skb, int queue,
- struct ieee80211_sta *sta);
+ struct ieee80211_link_sta *link_sta);
void iwl_mld_handle_phy_air_sniffer_notif(struct iwl_mld *mld,
struct napi_struct *napi,
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/tests/agg.c b/drivers/net/wireless/intel/iwlwifi/mld/tests/agg.c
index 29b0248cec3df..e9efe2996f071 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/tests/agg.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/tests/agg.c
@@ -2,7 +2,7 @@
/*
* KUnit tests for channel helper functions
*
- * Copyright (C) 2024-2025 Intel Corporation
+ * Copyright (C) 2024-2026 Intel Corporation
*/
#include <kunit/test.h>
#include <kunit/static_stub.h>
@@ -441,7 +441,7 @@ static void
fake_iwl_mld_pass_packet_to_mac80211(struct iwl_mld *mld,
struct napi_struct *napi,
struct sk_buff *skb, int queue,
- struct ieee80211_sta *sta)
+ struct ieee80211_link_sta *link_sta)
{
__skb_queue_tail(&g_released_skbs, skb);
g_num_released_skbs++;
@@ -630,7 +630,8 @@ static void test_reorder_buffer(struct kunit *test)
mpdu_desc = setup_mpdu_desc();
rcu_read_lock();
- reorder_res = iwl_mld_reorder(mld, NULL, QUEUE, sta, skb, mpdu_desc);
+ reorder_res = iwl_mld_reorder(mld, NULL, QUEUE, &sta->deflink, skb,
+ mpdu_desc);
rcu_read_unlock();
KUNIT_ASSERT_EQ(test, reorder_res, param->expected.reorder_res);
diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/rx.c b/drivers/net/wireless/intel/iwlwifi/mvm/rx.c
index ab1eb2eb0c3cd..8c197a090c2b1 100644
--- a/drivers/net/wireless/intel/iwlwifi/mvm/rx.c
+++ b/drivers/net/wireless/intel/iwlwifi/mvm/rx.c
@@ -90,7 +90,7 @@ static void iwl_mvm_pass_packet_to_mac80211(struct iwl_mvm *mvm,
fraglen, rxb->truesize);
}
- ieee80211_rx_napi(mvm->hw, sta, skb, napi);
+ ieee80211_rx_napi(mvm->hw, sta ? &sta->deflink : NULL, skb, napi);
}
/*
diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/rxmq.c b/drivers/net/wireless/intel/iwlwifi/mvm/rxmq.c
index 7f0b4f5daa214..8c4009d3a00ca 100644
--- a/drivers/net/wireless/intel/iwlwifi/mvm/rxmq.c
+++ b/drivers/net/wireless/intel/iwlwifi/mvm/rxmq.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
- * Copyright (C) 2012-2014, 2018-2025 Intel Corporation
+ * Copyright (C) 2012-2014, 2018-2026 Intel Corporation
* Copyright (C) 2013-2015 Intel Mobile Communications GmbH
* Copyright (C) 2015-2017 Intel Deutschland GmbH
*/
@@ -243,7 +243,7 @@ static void iwl_mvm_pass_packet_to_mac80211(struct iwl_mvm *mvm,
return;
}
- ieee80211_rx_napi(mvm->hw, sta, skb, napi);
+ ieee80211_rx_napi(mvm->hw, sta ? &sta->deflink : NULL, skb, napi);
}
static bool iwl_mvm_used_average_energy(struct iwl_mvm *mvm,
@@ -2528,7 +2528,7 @@ void iwl_mvm_rx_monitor_no_data(struct iwl_mvm *mvm, struct napi_struct *napi,
}
rcu_read_lock();
- ieee80211_rx_napi(mvm->hw, sta, skb, napi);
+ ieee80211_rx_napi(mvm->hw, sta ? &sta->deflink : NULL, skb, napi);
rcu_read_unlock();
}
diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c
index abbe65cbcd89e..3da06a55913ef 100644
--- a/drivers/net/wireless/mediatek/mt76/mac80211.c
+++ b/drivers/net/wireless/mediatek/mt76/mac80211.c
@@ -1255,7 +1255,7 @@ EXPORT_SYMBOL(mt76_rx_signal);
static void
mt76_rx_convert(struct mt76_dev *dev, struct sk_buff *skb,
struct ieee80211_hw **hw,
- struct ieee80211_sta **sta)
+ struct ieee80211_link_sta **link_sta)
{
struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
struct ieee80211_hdr *hdr = mt76_skb_get_hdr(skb);
@@ -1302,11 +1302,15 @@ mt76_rx_convert(struct mt76_dev *dev, struct sk_buff *skb,
sizeof(mstat.chain_signal));
if (mstat.wcid) {
- status->link_valid = mstat.wcid->link_valid;
- status->link_id = mstat.wcid->link_id;
+ struct ieee80211_sta *sta = wcid_to_sta(mstat.wcid);
+
+ if (mstat.wcid->link_valid)
+ *link_sta =
+ rcu_dereference(sta->link[mstat.wcid->link_id]);
+ else
+ *link_sta = &sta->deflink;
}
- *sta = wcid_to_sta(mstat.wcid);
*hw = mt76_phy_hw(dev, mstat.phy_idx);
}
@@ -1530,7 +1534,7 @@ mt76_check_sta(struct mt76_dev *dev, struct sk_buff *skb)
void mt76_rx_complete(struct mt76_dev *dev, struct sk_buff_head *frames,
struct napi_struct *napi)
{
- struct ieee80211_sta *sta;
+ struct ieee80211_link_sta *link_sta;
struct ieee80211_hw *hw;
struct sk_buff *skb, *tmp;
LIST_HEAD(list);
@@ -1541,8 +1545,8 @@ void mt76_rx_complete(struct mt76_dev *dev, struct sk_buff_head *frames,
mt76_check_ccmp_pn(skb);
skb_shinfo(skb)->frag_list = NULL;
- mt76_rx_convert(dev, skb, &hw, &sta);
- ieee80211_rx_list(hw, sta, skb, &list);
+ mt76_rx_convert(dev, skb, &hw, &link_sta);
+ ieee80211_rx_list(hw, link_sta, skb, &list);
/* subsequent amsdu frames */
while (nskb) {
@@ -1550,8 +1554,8 @@ void mt76_rx_complete(struct mt76_dev *dev, struct sk_buff_head *frames,
nskb = nskb->next;
skb->next = NULL;
- mt76_rx_convert(dev, skb, &hw, &sta);
- ieee80211_rx_list(hw, sta, skb, &list);
+ mt76_rx_convert(dev, skb, &hw, &link_sta);
+ ieee80211_rx_list(hw, link_sta, skb, &list);
}
}
spin_unlock(&dev->rx_lock);
diff --git a/drivers/net/wireless/realtek/rtw89/core.c b/drivers/net/wireless/realtek/rtw89/core.c
index 397ebbfcac09a..454d876aeb886 100644
--- a/drivers/net/wireless/realtek/rtw89/core.c
+++ b/drivers/net/wireless/realtek/rtw89/core.c
@@ -3073,10 +3073,8 @@ static void rtw89_vif_rx_stats_iter(void *data, u8 *mac,
struct rtw89_vif *rtwvif = vif_to_rtwvif(vif);
struct rtw89_rx_desc_info *desc_info = iter_data->desc_info;
struct sk_buff *skb = iter_data->skb;
- struct ieee80211_rx_status *rx_status = IEEE80211_SKB_RXCB(skb);
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
struct rtw89_rx_phy_ppdu *phy_ppdu = iter_data->phy_ppdu;
- bool is_mld = ieee80211_vif_is_mld(vif);
struct ieee80211_bss_conf *bss_conf;
struct rtw89_vif_link *rtwvif_link;
const u8 *bssid = iter_data->bssid;
@@ -3110,11 +3108,6 @@ static void rtw89_vif_rx_stats_iter(void *data, u8 *mac,
if (!ether_addr_equal(target_bssid, bssid))
goto out;
- if (is_mld) {
- rx_status->link_valid = true;
- rx_status->link_id = rtwvif_link->link_id;
- }
-
bb = rtw89_get_bb_ctx(rtwdev, rtwvif_link->phy_idx);
pkt_stat = &bb->cur_pkt_stat;
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index 02b6d81cccd11..a0d519cd9c4fa 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -1880,9 +1880,6 @@ static void mac80211_hwsim_rx(struct mac80211_hwsim_data *data,
sp->active_links_rx &= ~BIT(link_id);
else
sp->active_links_rx |= BIT(link_id);
-
- rx_status->link_valid = true;
- rx_status->link_id = link_id;
}
rcu_read_unlock();
}
diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index 97c16d4ff1271..436478daf5596 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -4009,6 +4009,7 @@ struct cfg80211_update_ft_ies_params {
* @link_id: for MLO, the link ID to transmit on, -1 if not given; note
* that the link ID isn't validated (much), it's in range but the
* link might not exist (or be used by the receiver STA)
+ * @no_sta: set if the frame should not be transmitted using an existing STA
*/
struct cfg80211_mgmt_tx_params {
struct ieee80211_channel *chan;
@@ -4021,6 +4022,7 @@ struct cfg80211_mgmt_tx_params {
int n_csa_offsets;
const u16 *csa_offsets;
int link_id;
+ bool no_sta;
};
/**
@@ -9501,6 +9503,7 @@ void cfg80211_conn_failed(struct net_device *dev, const u8 *mac_addr,
* @flags: flags, as defined in &enum nl80211_rxmgmt_flags
* @rx_tstamp: Hardware timestamp of frame RX in nanoseconds
* @ack_tstamp: Hardware timestamp of ack TX in nanoseconds
+ * @no_sta: set if no station is known for the frame (relevant for MLD)
*/
struct cfg80211_rx_info {
int freq;
@@ -9512,6 +9515,7 @@ struct cfg80211_rx_info {
u32 flags;
u64 rx_tstamp;
u64 ack_tstamp;
+ bool no_sta;
};
/**
diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 9d1fac6e80829..7aae728e96d84 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -1770,10 +1770,9 @@ enum mac80211_rx_encoding {
* @ampdu_reference: A-MPDU reference number, must be a different value for
* each A-MPDU but the same for each subframe within one A-MPDU
* @zero_length_psdu_type: radiotap type of the 0-length PSDU
- * @link_valid: if the link which is identified by @link_id is valid. This flag
- * is set only when connection is MLO.
- * @link_id: id of the link used to receive the packet. This is used along with
- * @link_valid.
+ * @link_id: id of the link used to receive the packet. Set and used by
+ * mac80211 internally, it uses @freq set by the driver to identify the
+ * correct link per vif.
*/
struct ieee80211_rx_status {
u64 mactime;
@@ -1813,7 +1812,7 @@ struct ieee80211_rx_status {
u8 chains;
s8 chain_signal[IEEE80211_MAX_CHAINS];
u8 zero_length_psdu_type;
- u8 link_valid:1, link_id:4;
+ u8 link_id:4;
};
static_assert(sizeof(struct ieee80211_rx_status) <= sizeof_field(struct sk_buff, cb));
@@ -5389,14 +5388,18 @@ void ieee80211_restart_hw(struct ieee80211_hw *hw);
* mixed for a single hardware. Must not run concurrently with
* ieee80211_tx_status_skb() or ieee80211_tx_status_ni().
*
+ * For data frames, when hardware has done address translation, a link station
+ * has to be provided and the frequency information may be skipped.
+ *
* This function must be called with BHs disabled and RCU read lock
*
* @hw: the hardware this frame came in on
- * @sta: the station the frame was received from, or %NULL
+ * @link_sta: the link station the data frame was received from, or %NULL
* @skb: the buffer to receive, owned by mac80211 after this call
* @list: the destination list
*/
-void ieee80211_rx_list(struct ieee80211_hw *hw, struct ieee80211_sta *sta,
+void ieee80211_rx_list(struct ieee80211_hw *hw,
+ struct ieee80211_link_sta *link_sta,
struct sk_buff *skb, struct list_head *list);
/**
@@ -5414,14 +5417,18 @@ void ieee80211_rx_list(struct ieee80211_hw *hw, struct ieee80211_sta *sta,
* mixed for a single hardware. Must not run concurrently with
* ieee80211_tx_status_skb() or ieee80211_tx_status_ni().
*
+ * For data frames, when hardware has done address translation, a link station
+ * has to be provided and the frequency information may be skipped.
+ *
* This function must be called with BHs disabled.
*
* @hw: the hardware this frame came in on
- * @sta: the station the frame was received from, or %NULL
+ * @link_sta: the link station the data frame was received from, or %NULL
* @skb: the buffer to receive, owned by mac80211 after this call
* @napi: the NAPI context
*/
-void ieee80211_rx_napi(struct ieee80211_hw *hw, struct ieee80211_sta *sta,
+void ieee80211_rx_napi(struct ieee80211_hw *hw,
+ struct ieee80211_link_sta *link_sta,
struct sk_buff *skb, struct napi_struct *napi);
/**
diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h
index 020387d764122..4653f8f26ee70 100644
--- a/include/uapi/linux/nl80211.h
+++ b/include/uapi/linux/nl80211.h
@@ -3185,6 +3185,11 @@ enum nl80211_commands {
* The aggregated message always precedes the per-link messages for the
* same station within a dump sequence.
*
+ * @NL80211_ATTR_FRAME_CMD_NO_STA: Valid for NL80211_CMD_FRAME to denote that
+ * the kernel had no station for a received frame or should not use a
+ * known station to transmit a frame. This is relevant to know whether
+ * MLD address translation happened or to disable it when sending a frame.
+ *
* @NUM_NL80211_ATTR: total number of nl80211_attrs available
* @NL80211_ATTR_MAX: highest attribute number currently defined
* @__NL80211_ATTR_AFTER_LAST: internal use
@@ -3785,6 +3790,8 @@ enum nl80211_attrs {
NL80211_ATTR_STA_DUMP_LINK_STATS,
+ NL80211_ATTR_FRAME_CMD_NO_STA,
+
/* add attributes here, update the policy in nl80211.c */
__NL80211_ATTR_AFTER_LAST,
diff --git a/net/mac80211/agg-tx.c b/net/mac80211/agg-tx.c
index 4833b46770b62..dd8a11ef6e52b 100644
--- a/net/mac80211/agg-tx.c
+++ b/net/mac80211/agg-tx.c
@@ -98,7 +98,7 @@ static void ieee80211_send_addba_request(struct sta_info *sta, u16 tid,
if (sta->sta.deflink.he_cap.has_he)
ieee80211_add_addbaext(skb, 0, agg_size);
- ieee80211_tx_skb_tid(sdata, skb, tid, -1);
+ ieee80211_tx_skb_tid(sdata, skb, NULL, tid, -1);
}
void ieee80211_send_bar(struct ieee80211_vif *vif, u8 *ra, u16 tid, u16 ssn)
@@ -127,7 +127,7 @@ void ieee80211_send_bar(struct ieee80211_vif *vif, u8 *ra, u16 tid, u16 ssn)
IEEE80211_SKB_CB(skb)->flags |= IEEE80211_TX_INTFL_DONT_ENCRYPT |
IEEE80211_TX_CTL_REQ_TX_STATUS;
- ieee80211_tx_skb_tid(sdata, skb, tid, -1);
+ ieee80211_tx_skb_tid(sdata, skb, NULL, tid, -1);
}
EXPORT_SYMBOL(ieee80211_send_bar);
diff --git a/net/mac80211/ap.c b/net/mac80211/ap.c
index e7ac99c5a22eb..fe255891c35f0 100644
--- a/net/mac80211/ap.c
+++ b/net/mac80211/ap.c
@@ -82,9 +82,6 @@ ieee80211_rx_eml_op_mode_notif(struct ieee80211_sub_if_data *sdata,
if (!ift_ext_capa)
return;
- if (!status->link_valid)
- return;
-
sta = sta_info_get_bss(sdata, mgmt->sa);
if (!sta)
return;
diff --git a/net/mac80211/ht.c b/net/mac80211/ht.c
index e1e1b7f82f3fe..c5c2113fa4f79 100644
--- a/net/mac80211/ht.c
+++ b/net/mac80211/ht.c
@@ -545,7 +545,7 @@ int ieee80211_send_smps_action(struct ieee80211_sub_if_data *sdata,
info->status_data = IEEE80211_STATUS_TYPE_SMPS |
u16_encode_bits(status_link_id << 2 | smps,
IEEE80211_STATUS_SUBDATA_MASK);
- ieee80211_tx_skb_tid(sdata, skb, 7, link_id);
+ ieee80211_tx_skb_tid(sdata, skb, NULL, 7, link_id);
return 0;
}
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 5761e96214914..0d8eb042887a9 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -2463,7 +2463,8 @@ void ieee80211_xmit(struct ieee80211_sub_if_data *sdata,
struct sta_info *sta, struct sk_buff *skb);
void __ieee80211_tx_skb_tid_band(struct ieee80211_sub_if_data *sdata,
- struct sk_buff *skb, int tid, int link_id,
+ struct sk_buff *skb, struct sta_info *sta,
+ int tid, int link_id,
enum nl80211_band band);
static inline bool ieee80211_require_encrypted_assoc(__le16 fc,
@@ -2483,18 +2484,20 @@ ieee80211_tx_skb_tid_band(struct ieee80211_sub_if_data *sdata,
enum nl80211_band band)
{
rcu_read_lock();
- __ieee80211_tx_skb_tid_band(sdata, skb, tid, -1, band);
+ __ieee80211_tx_skb_tid_band(sdata, skb, ERR_PTR(-ENOENT),
+ tid, -1, band);
rcu_read_unlock();
}
void ieee80211_tx_skb_tid(struct ieee80211_sub_if_data *sdata,
- struct sk_buff *skb, int tid, int link_id);
+ struct sk_buff *skb, struct sta_info *sta,
+ int tid, int link_id);
static inline void ieee80211_tx_skb(struct ieee80211_sub_if_data *sdata,
struct sk_buff *skb)
{
/* Send all internal mgmt frames on VO. Accordingly set TID to 7. */
- ieee80211_tx_skb_tid(sdata, skb, 7, -1);
+ ieee80211_tx_skb_tid(sdata, skb, NULL, 7, -1);
}
/**
diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c
index 43460a705a6bd..a0b214dbb8af8 100644
--- a/net/mac80211/iface.c
+++ b/net/mac80211/iface.c
@@ -1673,11 +1673,8 @@ static void ieee80211_iface_process_skb(struct ieee80211_local *local,
break;
status = IEEE80211_SKB_RXCB(skb);
- if (!status->link_valid)
- link_sta = &sta->deflink;
- else
- link_sta = rcu_dereference_protected(sta->link[status->link_id],
- lockdep_is_held(&local->hw.wiphy->mtx));
+ link_sta = wiphy_dereference(local->hw.wiphy,
+ sta->link[status->link_id]);
if (link_sta)
ieee80211_ht_handle_chanwidth_notif(local, sdata, sta,
link_sta, chanwidth,
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index f51167f0fc46d..361775fb931ec 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -11831,12 +11831,10 @@ void ieee80211_sta_rx_queued_frame(struct ieee80211_sub_if_data *sdata,
rx_status = (struct ieee80211_rx_status *) skb->cb;
fc = le16_to_cpu(mgmt->frame_control);
- if (rx_status->link_valid) {
- link = sdata_dereference(sdata->link[rx_status->link_id],
- sdata);
- if (!link)
- return;
- }
+ link = sdata_dereference(sdata->link[rx_status->link_id],
+ sdata);
+ if (!link)
+ return;
switch (fc & IEEE80211_FCTL_STYPE) {
case IEEE80211_STYPE_BEACON:
diff --git a/net/mac80211/offchannel.c b/net/mac80211/offchannel.c
index 7acef80d5f1fb..ddd7a96a2f817 100644
--- a/net/mac80211/offchannel.c
+++ b/net/mac80211/offchannel.c
@@ -854,8 +854,10 @@ int ieee80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev,
need_offchan = true;
rcu_read_lock();
- sta = sta_info_get_bss(sdata, mgmt->da);
- mlo_sta = sta && sta->sta.mlo;
+ if (!params->no_sta) {
+ sta = sta_info_get_bss(sdata, mgmt->da);
+ mlo_sta = sta && sta->sta.mlo;
+ }
if (!ieee80211_is_action(mgmt->frame_control) ||
mgmt->u.action.category == WLAN_CATEGORY_PUBLIC ||
@@ -884,7 +886,8 @@ int ieee80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev,
local->ops->remain_on_channel &&
memcmp(sdata->vif.cfg.ap_addr, mgmt->bssid, ETH_ALEN))) {
need_offchan = true;
- } else if (sdata->u.mgd.associated &&
+ } else if (!params->no_sta &&
+ sdata->u.mgd.associated &&
ether_addr_equal(sdata->vif.cfg.ap_addr, mgmt->da)) {
sta = sta_info_get_bss(sdata, mgmt->da);
mlo_sta = sta && sta->sta.mlo;
@@ -1022,7 +1025,9 @@ int ieee80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev,
}
if (!need_offchan) {
- ieee80211_tx_skb_tid(sdata, skb, 7, link_id);
+ ieee80211_tx_skb_tid(sdata, skb,
+ params->no_sta ? ERR_PTR(-ENOENT) : sta,
+ 7, link_id);
ret = 0;
goto out_unlock;
}
diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index 5e26be8e27d8f..7896e6eee130b 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -222,43 +222,21 @@ ieee80211_rx_radiotap_hdrlen(struct ieee80211_local *local,
}
static void __ieee80211_queue_skb_to_iface(struct ieee80211_sub_if_data *sdata,
- int link_id,
- struct sta_info *sta,
+ struct link_sta_info *link_sta,
struct sk_buff *skb)
{
- struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
-
- if (link_id >= 0) {
- status->link_valid = 1;
- status->link_id = link_id;
- } else {
- status->link_valid = 0;
- }
-
skb_queue_tail(&sdata->skb_queue, skb);
wiphy_work_queue(sdata->local->hw.wiphy, &sdata->work);
- if (sta) {
- struct link_sta_info *link_sta_info;
-
- if (link_id >= 0) {
- link_sta_info = rcu_dereference(sta->link[link_id]);
- if (!link_sta_info)
- return;
- } else {
- link_sta_info = &sta->deflink;
- }
-
- link_sta_info->rx_stats.packets++;
- }
+ if (link_sta)
+ link_sta->rx_stats.packets++;
}
static void ieee80211_queue_skb_to_iface(struct ieee80211_sub_if_data *sdata,
- int link_id,
- struct sta_info *sta,
+ struct link_sta_info *link_sta,
struct sk_buff *skb)
{
skb->protocol = 0;
- __ieee80211_queue_skb_to_iface(sdata, link_id, sta, skb);
+ __ieee80211_queue_skb_to_iface(sdata, link_sta, skb);
}
static void ieee80211_handle_mu_mimo_mon(struct ieee80211_sub_if_data *sdata,
@@ -301,7 +279,7 @@ static void ieee80211_handle_mu_mimo_mon(struct ieee80211_sub_if_data *sdata,
if (!skb)
return;
- ieee80211_queue_skb_to_iface(sdata, -1, NULL, skb);
+ ieee80211_queue_skb_to_iface(sdata, NULL, skb);
}
/*
@@ -1498,7 +1476,7 @@ static void ieee80211_rx_reorder_ampdu(struct ieee80211_rx_data *rx,
/* if this mpdu is fragmented - terminate rx aggregation session */
sc = le16_to_cpu(hdr->seq_ctrl);
if (sc & IEEE80211_SCTL_FRAG) {
- ieee80211_queue_skb_to_iface(rx->sdata, rx->link_id, NULL, skb);
+ ieee80211_queue_skb_to_iface(rx->sdata, NULL, skb);
return;
}
@@ -2524,7 +2502,7 @@ ieee80211_rx_h_defragment(struct ieee80211_rx_data *rx)
out:
ieee80211_led_rx(rx->local);
- if (rx->sta)
+ if (rx->link_sta)
rx->link_sta->rx_stats.packets++;
return RX_CONTINUE;
}
@@ -3335,8 +3313,8 @@ ieee80211_rx_h_data(struct ieee80211_rx_data *rx)
(tf->action_code == WLAN_TDLS_CHANNEL_SWITCH_REQUEST ||
tf->action_code == WLAN_TDLS_CHANNEL_SWITCH_RESPONSE)) {
rx->skb->protocol = cpu_to_be16(ETH_P_TDLS);
- __ieee80211_queue_skb_to_iface(sdata, rx->link_id,
- rx->sta, rx->skb);
+ __ieee80211_queue_skb_to_iface(sdata, rx->link_sta,
+ rx->skb);
return RX_QUEUED;
}
}
@@ -3991,7 +3969,7 @@ ieee80211_rx_h_action(struct ieee80211_rx_data *rx)
return RX_QUEUED;
queue:
- ieee80211_queue_skb_to_iface(sdata, rx->link_id, rx->sta, rx->skb);
+ ieee80211_queue_skb_to_iface(sdata, rx->link_sta, rx->skb);
return RX_QUEUED;
}
@@ -4005,6 +3983,7 @@ ieee80211_rx_h_userspace_mgmt(struct ieee80211_rx_data *rx)
.len = rx->skb->len,
.link_id = rx->link_id,
.have_link_id = rx->link_id >= 0,
+ .no_sta = !rx->sta,
};
/* skip known-bad action frames and return them in the next handler */
@@ -4127,7 +4106,7 @@ ieee80211_rx_h_action_return(struct ieee80211_rx_data *rx)
local->hw.offchannel_tx_hw_queue;
}
- __ieee80211_tx_skb_tid_band(rx->sdata, nskb, 7, -1,
+ __ieee80211_tx_skb_tid_band(rx->sdata, nskb, rx->sta, 7, -1,
status->band);
}
@@ -4147,7 +4126,7 @@ ieee80211_rx_h_ext(struct ieee80211_rx_data *rx)
return RX_DROP_U_UNEXPECTED_EXT_FRAME;
/* for now only beacons are ext, so queue them */
- ieee80211_queue_skb_to_iface(sdata, rx->link_id, rx->sta, rx->skb);
+ ieee80211_queue_skb_to_iface(sdata, rx->link_sta, rx->skb);
return RX_QUEUED;
}
@@ -4204,7 +4183,7 @@ ieee80211_rx_h_mgmt(struct ieee80211_rx_data *rx)
return RX_DROP_U_UNHANDLED_MGMT_STYPE;
}
- ieee80211_queue_skb_to_iface(sdata, rx->link_id, rx->sta, rx->skb);
+ ieee80211_queue_skb_to_iface(sdata, rx->link_sta, rx->skb);
return RX_QUEUED;
}
@@ -4248,12 +4227,28 @@ static void ieee80211_rx_handlers(struct ieee80211_rx_data *rx,
spin_lock_bh(&rx->local->rx_path_lock);
while ((skb = __skb_dequeue(frames))) {
+ struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
+
/*
- * all the other fields are valid across frames
- * that belong to an aMPDU since they are on the
- * same TID from the same station
+ * Most fields are valid across frames. However,
+ * in the case of reordering frames may have arrived
+ * on different links, so adjust the link_id, link
+ * and link_sta accordingly.
*/
rx->skb = skb;
+ if (ieee80211_vif_is_mld(&rx->sdata->vif) &&
+ unlikely(rx->link_id != status->link_id)) {
+ if (rx->sta) {
+ rx->link_sta =
+ rcu_dereference(rx->sta->link[rx->link_id]);
+
+ /* Link got disabled, just use deflink */
+ if (!rx->link_sta)
+ rx->link_sta = &rx->sta->deflink;
+ }
+ rx->link_id = status->link_id;
+ rx->link = rcu_dereference(rx->sdata->link[status->link_id]);
+ }
if (WARN_ON_ONCE(!rx->link)) {
res = RX_DROP_U_NO_LINK;
@@ -4323,7 +4318,34 @@ static void ieee80211_invoke_rx_handlers(struct ieee80211_rx_data *rx)
static bool
ieee80211_rx_is_valid_sta_link_id(struct ieee80211_sta *sta, u8 link_id)
{
- return !!(sta->valid_links & BIT(link_id));
+ if (sta->mlo)
+ return sta->valid_links & BIT(link_id);
+
+ return sta->deflink.link_id == link_id;
+}
+
+static bool ieee80211_rx_data_set_link_sta(struct ieee80211_rx_data *rx,
+ struct link_sta_info *link_sta)
+{
+ struct sta_info *sta;
+
+ if (WARN_ON_ONCE(!link_sta) ||
+ !ieee80211_rx_is_valid_sta_link_id(&link_sta->sta->sta,
+ link_sta->link_id))
+ return false;
+
+ sta = link_sta->sta;
+
+ rx->sta = sta;
+ rx->local = sta->sdata->local;
+ rx->link = rcu_dereference(sta->sdata->link[link_sta->link_id]);
+ if (!rx->link)
+ return false;
+
+ rx->link_id = rx->link->link_id;
+ rx->link_sta = link_sta;
+
+ return true;
}
static bool ieee80211_rx_data_set_link(struct ieee80211_rx_data *rx,
@@ -4360,11 +4382,13 @@ static bool ieee80211_rx_data_set_sta(struct ieee80211_rx_data *rx,
if (link_id < 0) {
if (ieee80211_vif_is_mld(&rx->sdata->vif) &&
- sta && !sta->sta.valid_links)
+ sta && !sta->sta.valid_links) {
rx->link =
rcu_dereference(rx->sdata->link[sta->deflink.link_id]);
- else
+ rx->link_sta = &sta->deflink;
+ } else {
rx->link = &rx->sdata->deflink;
+ }
} else if (!ieee80211_rx_data_set_link(rx, link_id)) {
return false;
}
@@ -4387,7 +4411,7 @@ void ieee80211_release_reorder_timeout(struct sta_info *sta, int tid)
struct tid_ampdu_rx *tid_agg_rx;
int link_id = -1;
- /* FIXME: statistics won't be right with this */
+ /* NOTE: the correct link STA for the frame is set later */
if (sta->sta.valid_links)
link_id = ffs(sta->sta.valid_links) - 1;
@@ -4914,20 +4938,14 @@ static void ieee80211_rx_8023(struct ieee80211_rx_data *rx,
{
struct ieee80211_sta_rx_stats *stats;
struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(rx->skb);
- struct sta_info *sta = rx->sta;
- struct link_sta_info *link_sta;
+ struct link_sta_info *link_sta = rx->link_sta;
struct sk_buff *skb = rx->skb;
void *sa = skb->data + ETH_ALEN;
void *da = skb->data;
- if (rx->link_id >= 0) {
- link_sta = rcu_dereference(sta->link[rx->link_id]);
- if (WARN_ON_ONCE(!link_sta)) {
- dev_kfree_skb(rx->skb);
- return;
- }
- } else {
- link_sta = &sta->deflink;
+ if (WARN_ON_ONCE(!link_sta)) {
+ dev_kfree_skb(rx->skb);
+ return;
}
stats = &link_sta->rx_stats;
@@ -5154,6 +5172,9 @@ static bool ieee80211_invoke_fast_rx(struct ieee80211_rx_data *rx,
goto drop;
}
+ status = IEEE80211_SKB_RXCB(skb);
+ status->link_id = rx->link_id < 0 ? 0 : rx->link_id;
+
ieee80211_rx_8023(rx, fast_rx, orig_len);
return true;
@@ -5178,6 +5199,7 @@ static bool ieee80211_prepare_and_rx_handle(struct ieee80211_rx_data *rx,
struct ieee80211_hdr *hdr = (void *)skb->data;
struct link_sta_info *link_sta = rx->link_sta;
struct ieee80211_link_data *link = rx->link;
+ struct ieee80211_rx_status *status;
rx->skb = skb;
@@ -5221,6 +5243,9 @@ static bool ieee80211_prepare_and_rx_handle(struct ieee80211_rx_data *rx,
hdr = (struct ieee80211_hdr *)rx->skb->data;
}
+ status = IEEE80211_SKB_RXCB(rx->skb);
+ status->link_id = rx->link_id < 0 ? 0 : rx->link_id;
+
if (ieee80211_is_s1g_beacon(hdr->frame_control)) {
ieee80211_invoke_rx_handlers(rx);
return true;
@@ -5256,22 +5281,19 @@ static bool ieee80211_prepare_and_rx_handle(struct ieee80211_rx_data *rx,
}
static void __ieee80211_rx_handle_8023(struct ieee80211_hw *hw,
- struct ieee80211_sta *pubsta,
+ struct ieee80211_link_sta *link_pubsta,
struct sk_buff *skb,
struct list_head *list)
{
struct ieee80211_local *local = hw_to_local(hw);
- struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
struct ieee80211_fast_rx *fast_rx;
struct ieee80211_rx_data rx;
struct sta_info *sta;
- int link_id = -1;
memset(&rx, 0, sizeof(rx));
rx.skb = skb;
rx.local = local;
rx.list = list;
- rx.link_id = -1;
I802_DEBUG_INC(local->dot11ReceivedFragmentCount);
@@ -5279,23 +5301,15 @@ static void __ieee80211_rx_handle_8023(struct ieee80211_hw *hw,
if (skb->len < sizeof(struct ethhdr))
goto drop;
- if (!pubsta)
+ if (!link_pubsta)
goto drop;
- if (status->link_valid)
- link_id = status->link_id;
-
- /*
- * TODO: Should the frame be dropped if the right link_id is not
- * available? Or may be it is fine in the current form to proceed with
- * the frame processing because with frame being in 802.3 format,
- * link_id is used only for stats purpose and updating the stats on
- * the deflink is fine?
- */
- sta = container_of(pubsta, struct sta_info, sta);
- if (!ieee80211_rx_data_set_sta(&rx, sta, link_id))
+ sta = container_of(link_pubsta->sta, struct sta_info, sta);
+ if (!ieee80211_rx_data_set_sta(&rx, sta, link_pubsta->link_id))
goto drop;
+ rx.link_id = link_pubsta->link_id;
+
fast_rx = rcu_dereference(rx.sta->fast_rx);
if (!fast_rx)
goto drop;
@@ -5307,59 +5321,19 @@ static void __ieee80211_rx_handle_8023(struct ieee80211_hw *hw,
dev_kfree_skb(skb);
}
-static bool ieee80211_rx_for_interface(struct ieee80211_rx_data *rx,
- struct sk_buff *skb, bool consume)
+static bool ieee80211_rx_valid_freq(int freq, struct ieee80211_link_data *link)
{
- struct link_sta_info *link_sta;
- struct ieee80211_hdr *hdr = (void *)skb->data;
- struct sta_info *sta;
- int link_id = -1;
-
- if (ieee80211_is_s1g_beacon(hdr->frame_control)) {
- if (!ieee80211_rx_data_set_sta(rx, NULL, -1))
- return false;
-
- return ieee80211_prepare_and_rx_handle(rx, skb, consume);
- }
+ struct ieee80211_chanctx_conf *conf;
- /*
- * Look up link station first, in case there's a
- * chance that they might have a link address that
- * is identical to the MLD address, that way we'll
- * have the link information if needed.
- */
- link_sta = link_sta_info_get_bss(rx->sdata, hdr->addr2);
- if (link_sta) {
- sta = link_sta->sta;
- link_id = link_sta->link_id;
- } else {
- struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
-
- sta = sta_info_get_bss(rx->sdata, hdr->addr2);
- if (status->link_valid) {
- link_id = status->link_id;
- } else if (ieee80211_vif_is_mld(&rx->sdata->vif) &&
- status->freq) {
- struct ieee80211_link_data *link;
- struct ieee80211_chanctx_conf *conf;
-
- for_each_link_data_rcu(rx->sdata, link) {
- conf = rcu_dereference(link->conf->chanctx_conf);
- if (!conf || !conf->def.chan)
- continue;
-
- if (status->freq == conf->def.chan->center_freq) {
- link_id = link->link_id;
- break;
- }
- }
- }
- }
+ if (!freq || link->sdata->vif.type == NL80211_IFTYPE_NAN ||
+ link->sdata->vif.type == NL80211_IFTYPE_NAN_DATA)
+ return true;
- if (!ieee80211_rx_data_set_sta(rx, sta, link_id))
+ conf = rcu_dereference(link->conf->chanctx_conf);
+ if (!conf || !conf->def.chan)
return false;
- return ieee80211_prepare_and_rx_handle(rx, skb, consume);
+ return freq == conf->def.chan->center_freq;
}
/*
@@ -5367,18 +5341,20 @@ static bool ieee80211_rx_for_interface(struct ieee80211_rx_data *rx,
* be called with rcu_read_lock protection.
*/
static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw,
- struct ieee80211_sta *pubsta,
+ struct ieee80211_link_sta *link_pubsta,
struct sk_buff *skb,
struct list_head *list)
{
struct ieee80211_local *local = hw_to_local(hw);
- struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
struct ieee80211_sub_if_data *sdata;
+ struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
struct ieee80211_hdr *hdr;
+ struct link_sta_info *link_sta;
+ struct sta_info *sta;
__le16 fc;
struct ieee80211_rx_data rx;
- struct ieee80211_sub_if_data *prev;
struct rhlist_head *tmp;
+ bool rx_data_pending;
int err = 0;
fc = ((struct ieee80211_hdr *)skb->data)->frame_control;
@@ -5386,7 +5362,6 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw,
rx.skb = skb;
rx.local = local;
rx.list = list;
- rx.link_id = -1;
if (ieee80211_is_data(fc) || ieee80211_is_mgmt(fc))
I802_DEBUG_INC(local->dot11ReceivedFragmentCount);
@@ -5426,97 +5401,102 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw,
ieee80211_is_s1g_beacon(hdr->frame_control)))
ieee80211_scan_rx(local, skb);
+ /*
+ * RX of a data frame should only happen to existing stations.
+ * It is therefore more efficient to use the one provided by the driver
+ * or search based on the station's address.
+ *
+ * Note we will fall through and handle a spurious data frame in the
+ * same way as a management frame.
+ */
if (ieee80211_is_data(fc)) {
- struct sta_info *sta, *prev_sta;
- int link_id = -1;
-
- if (status->link_valid)
- link_id = status->link_id;
-
- if (pubsta) {
- sta = container_of(pubsta, struct sta_info, sta);
- if (!ieee80211_rx_data_set_sta(&rx, sta, link_id))
- goto out;
-
- /*
- * In MLO connection, fetch the link_id using addr2
- * when the driver does not pass link_id in status.
- * When the address translation is already performed by
- * driver/hw, the valid link_id must be passed in
- * status.
- */
-
- if (!status->link_valid && pubsta->mlo) {
- struct link_sta_info *link_sta;
-
- link_sta = link_sta_info_get_bss(rx.sdata,
- hdr->addr2);
- if (!link_sta)
- goto out;
-
- if (!ieee80211_rx_data_set_link(&rx,
- link_sta->link_id))
- goto out;
- }
-
- if (ieee80211_prepare_and_rx_handle(&rx, skb, true))
+ if (link_pubsta) {
+ sta = container_of(link_pubsta->sta, struct sta_info,
+ sta);
+ link_sta = rcu_dereference(sta->link[link_pubsta->link_id]);
+
+ rx.sdata = sta->sdata;
+ if (ieee80211_rx_data_set_link_sta(&rx, link_sta) &&
+ ieee80211_prepare_and_rx_handle(&rx, skb, true))
return;
+
goto out;
}
- prev_sta = NULL;
+ rx_data_pending = false;
+ /* Search for stations on non-MLD interfaces */
for_each_sta_info(local, hdr->addr2, sta, tmp) {
- if (!prev_sta) {
- prev_sta = sta;
- continue;
- }
+ struct ieee80211_link_data *link;
- rx.sdata = prev_sta->sdata;
- if (!status->link_valid && prev_sta->sta.mlo) {
- struct link_sta_info *link_sta;
+ if (ieee80211_vif_is_mld(&sta->sdata->vif))
+ continue;
- link_sta = link_sta_info_get_bss(rx.sdata,
- hdr->addr2);
- if (!link_sta)
- continue;
+ link = &sta->sdata->deflink;
+ if (!ieee80211_rx_valid_freq(status->freq, link))
+ continue;
- link_id = link_sta->link_id;
+ if (rx_data_pending) {
+ ieee80211_prepare_and_rx_handle(&rx, skb,
+ false);
+ rx_data_pending = false;
}
- if (!ieee80211_rx_data_set_sta(&rx, prev_sta, link_id))
- goto out;
-
- ieee80211_prepare_and_rx_handle(&rx, skb, false);
+ rx.sdata = sta->sdata;
+ if (!ieee80211_rx_data_set_link_sta(&rx, &sta->deflink))
+ continue;
- prev_sta = sta;
+ rx_data_pending = true;
}
- if (prev_sta) {
- rx.sdata = prev_sta->sdata;
- if (!status->link_valid && prev_sta->sta.mlo) {
- struct link_sta_info *link_sta;
+ /* And search stations on MLD interfaces */
+ for_each_link_sta_info(local, hdr->addr2, link_sta, tmp) {
+ struct ieee80211_link_data *link;
- link_sta = link_sta_info_get_bss(rx.sdata,
- hdr->addr2);
- if (!link_sta)
- goto out;
+ sta = link_sta->sta;
+ sdata = sta->sdata;
+ link = rcu_dereference(sdata->link[link_sta->link_id]);
- link_id = link_sta->link_id;
+ if (!ieee80211_rx_valid_freq(status->freq, link))
+ continue;
+
+ if (rx_data_pending) {
+ ieee80211_prepare_and_rx_handle(&rx, skb,
+ false);
+ rx_data_pending = false;
}
- if (!ieee80211_rx_data_set_sta(&rx, prev_sta, link_id))
- goto out;
+ rx.sdata = sta->sdata;
+ if (!ieee80211_rx_data_set_link_sta(&rx, link_sta))
+ continue;
+
+ rx_data_pending = true;
+ }
+ if (rx_data_pending) {
if (ieee80211_prepare_and_rx_handle(&rx, skb, true))
return;
+
goto out;
}
+
+ /* fall through, e.g. for spurious frame notification */
}
- prev = NULL;
+ /*
+ * This is a management frame (or a data frame without a station) and
+ * it will be delivered independent of whether a station exists,
+ * so iterate the interfaces.
+ */
+ rx_data_pending = false;
+
+ /* We expect the driver to provide frequency information */
+ if (WARN_ON_ONCE(!local->emulate_chanctx && !status->freq))
+ goto out;
list_for_each_entry_rcu(sdata, &local->interfaces, list) {
+ struct ieee80211_link_data *link = NULL;
+
if (!ieee80211_sdata_running(sdata))
continue;
@@ -5524,30 +5504,85 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw,
sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
continue;
+ link_sta = NULL;
+ link = NULL;
+
+ /* Try to resolve the station */
+ if (!ieee80211_vif_is_mld(&sdata->vif)) {
+ sta = sta_info_get_bss(sdata, hdr->addr2);
+
+ if (sta) {
+ link_sta = &sta->deflink;
+ link = &sdata->deflink;
+ }
+ } else {
+ link_sta = link_sta_info_get_bss(sdata, hdr->addr2);
+
+ if (link_sta)
+ link = rcu_dereference(sdata->link[link_sta->link_id]);
+ }
+
/*
- * frame is destined for this interface, but if it's
- * not also for the previous one we handle that after
- * the loop to avoid copying the SKB once too much
+ * If we found a STA and it has a valid link information, then
+ * RX using the station.
*/
+ if (link_sta && link &&
+ ieee80211_rx_valid_freq(status->freq, link)) {
+ if (rx_data_pending) {
+ ieee80211_prepare_and_rx_handle(&rx, skb, false);
+ rx_data_pending = false;
+ }
+
+ /* No valid_links check as we need to RX beacons */
+
+ rx.sdata = sdata;
+ if (ieee80211_rx_data_set_link_sta(&rx, link_sta))
+ rx_data_pending = true;
- if (!prev) {
- prev = sdata;
continue;
}
- rx.sdata = prev;
- ieee80211_rx_for_interface(&rx, skb, false);
+ /* No station, try to resolve the link and RX */
+ if (ieee80211_vif_is_mld(&sdata->vif)) {
+ struct ieee80211_chanctx_conf *conf;
+ bool found = false;
- prev = sdata;
- }
+ for_each_link_data_rcu(sdata, link) {
+ conf = rcu_dereference(link->conf->chanctx_conf);
+ if (!conf || !conf->def.chan)
+ continue;
- if (prev) {
- rx.sdata = prev;
+ if (status->freq == conf->def.chan->center_freq) {
+ found = true;
+ break;
+ }
+ }
- if (ieee80211_rx_for_interface(&rx, skb, true))
- return;
+ if (!found)
+ link = &sdata->deflink;
+ } else {
+ link = &sdata->deflink;
+ }
+
+ if (rx_data_pending) {
+ ieee80211_prepare_and_rx_handle(&rx, skb, false);
+ rx_data_pending = false;
+ }
+
+ rx.sdata = sdata;
+ rx.local = sdata->local;
+ rx.link = link;
+ rx.link_id = link->link_id;
+ rx.sta = NULL;
+ rx.link_sta = NULL;
+
+ rx_data_pending = true;
}
+ if (rx_data_pending &&
+ ieee80211_prepare_and_rx_handle(&rx, skb, true))
+ return;
+
out:
dev_kfree_skb(skb);
}
@@ -5556,7 +5591,8 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw,
* This is the receive path handler. It is called by a low level driver when an
* 802.11 MPDU is received from the hardware.
*/
-void ieee80211_rx_list(struct ieee80211_hw *hw, struct ieee80211_sta *pubsta,
+void ieee80211_rx_list(struct ieee80211_hw *hw,
+ struct ieee80211_link_sta *link_pubsta,
struct sk_buff *skb, struct list_head *list)
{
struct ieee80211_local *local = hw_to_local(hw);
@@ -5694,9 +5730,6 @@ void ieee80211_rx_list(struct ieee80211_hw *hw, struct ieee80211_sta *pubsta,
}
}
- if (WARN_ON_ONCE(status->link_id >= IEEE80211_LINK_UNSPECIFIED))
- goto drop;
-
status->rx_flags = 0;
kcov_remote_start_common(skb_get_kcov_handle(skb));
@@ -5715,9 +5748,10 @@ void ieee80211_rx_list(struct ieee80211_hw *hw, struct ieee80211_sta *pubsta,
ieee80211_tpt_led_trig_rx(local, skb->len);
if (status->flag & RX_FLAG_8023)
- __ieee80211_rx_handle_8023(hw, pubsta, skb, list);
+ __ieee80211_rx_handle_8023(hw, link_pubsta, skb, list);
else
- __ieee80211_rx_handle_packet(hw, pubsta, skb, list);
+ __ieee80211_rx_handle_packet(hw, link_pubsta, skb,
+ list);
}
kcov_remote_stop();
@@ -5727,7 +5761,8 @@ void ieee80211_rx_list(struct ieee80211_hw *hw, struct ieee80211_sta *pubsta,
}
EXPORT_SYMBOL(ieee80211_rx_list);
-void ieee80211_rx_napi(struct ieee80211_hw *hw, struct ieee80211_sta *pubsta,
+void ieee80211_rx_napi(struct ieee80211_hw *hw,
+ struct ieee80211_link_sta *link_pubsta,
struct sk_buff *skb, struct napi_struct *napi)
{
struct sk_buff *tmp;
@@ -5740,7 +5775,7 @@ void ieee80211_rx_napi(struct ieee80211_hw *hw, struct ieee80211_sta *pubsta,
* receive processing
*/
rcu_read_lock();
- ieee80211_rx_list(hw, pubsta, skb, &list);
+ ieee80211_rx_list(hw, link_pubsta, skb, &list);
rcu_read_unlock();
if (!napi) {
diff --git a/net/mac80211/scan.c b/net/mac80211/scan.c
index eeff230bd909f..324c2b6d7ce1b 100644
--- a/net/mac80211/scan.c
+++ b/net/mac80211/scan.c
@@ -9,7 +9,7 @@
* Copyright 2007, Michael Wu <flamingice@sourmilk.net>
* Copyright 2013-2015 Intel Mobile Communications GmbH
* Copyright 2016-2017 Intel Deutschland GmbH
- * Copyright (C) 2018-2025 Intel Corporation
+ * Copyright (C) 2018-2026 Intel Corporation
*/
#include <linux/if_arp.h>
@@ -204,12 +204,10 @@ ieee80211_bss_info_update(struct ieee80211_local *local,
* an indication on which of the links the frame was received
*/
if (ieee80211_vif_is_mld(&scan_sdata->vif)) {
- if (rx_status->link_valid) {
- s8 link_id = rx_status->link_id;
+ s8 link_id = rx_status->link_id;
- link_conf =
- rcu_dereference(scan_sdata->vif.link_conf[link_id]);
- }
+ link_conf =
+ rcu_dereference(scan_sdata->vif.link_conf[link_id]);
} else {
link_conf = &scan_sdata->vif.bss_conf;
}
diff --git a/net/mac80211/tdls.c b/net/mac80211/tdls.c
index dc2f662fe4c46..e17d492be5902 100644
--- a/net/mac80211/tdls.c
+++ b/net/mac80211/tdls.c
@@ -1066,7 +1066,7 @@ ieee80211_tdls_prep_mgmt_packet(struct wiphy *wiphy, struct net_device *dev,
}
if (action_code == WLAN_PUB_ACTION_TDLS_DISCOVER_RES) {
- ieee80211_tx_skb_tid(sdata, skb, 7, link_id);
+ ieee80211_tx_skb_tid(sdata, skb, sta, 7, link_id);
return 0;
}
diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c
index 3a1e2c9e1565e..6f52e88402371 100644
--- a/net/mac80211/tx.c
+++ b/net/mac80211/tx.c
@@ -6436,7 +6436,8 @@ void ieee80211_unreserve_tid(struct ieee80211_sta *pubsta, u8 tid)
EXPORT_SYMBOL(ieee80211_unreserve_tid);
void __ieee80211_tx_skb_tid_band(struct ieee80211_sub_if_data *sdata,
- struct sk_buff *skb, int tid, int link_id,
+ struct sk_buff *skb, struct sta_info *sta,
+ int tid, int link_id,
enum nl80211_band band)
{
const struct ieee80211_hdr *hdr = (void *)skb->data;
@@ -6488,12 +6489,13 @@ void __ieee80211_tx_skb_tid_band(struct ieee80211_sub_if_data *sdata,
*/
local_bh_disable();
IEEE80211_SKB_CB(skb)->band = band;
- ieee80211_xmit(sdata, NULL, skb);
+ ieee80211_xmit(sdata, sta, skb);
local_bh_enable();
}
void ieee80211_tx_skb_tid(struct ieee80211_sub_if_data *sdata,
- struct sk_buff *skb, int tid, int link_id)
+ struct sk_buff *skb, struct sta_info *sta,
+ int tid, int link_id)
{
struct ieee80211_chanctx_conf *chanctx_conf;
enum nl80211_band band;
@@ -6519,7 +6521,7 @@ void ieee80211_tx_skb_tid(struct ieee80211_sub_if_data *sdata,
band = 0;
}
- __ieee80211_tx_skb_tid_band(sdata, skb, tid, link_id, band);
+ __ieee80211_tx_skb_tid_band(sdata, skb, sta, tid, link_id, band);
rcu_read_unlock();
}
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index 44f2bad086705..437bebcc02748 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -1096,6 +1096,7 @@ static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = {
[NL80211_ATTR_NPCA_PUNCT_BITMAP] =
NLA_POLICY_FULL_RANGE(NLA_U32, &nl80211_punct_bitmap_range),
[NL80211_ATTR_STA_DUMP_LINK_STATS] = { .type = NLA_FLAG },
+ [NL80211_ATTR_FRAME_CMD_NO_STA] = { .type = NLA_FLAG },
};
/* policy for the key attributes */
@@ -14860,6 +14861,9 @@ static int nl80211_tx_mgmt(struct sk_buff *skb, struct genl_info *info)
!(wdev->valid_links & BIT(params.link_id)))
return -EINVAL;
+ params.no_sta =
+ nla_get_flag(info->attrs[NL80211_ATTR_FRAME_CMD_NO_STA]);
+
params.buf = nla_data(info->attrs[NL80211_ATTR_FRAME]);
params.len = nla_len(info->attrs[NL80211_ATTR_FRAME]);
@@ -22024,7 +22028,9 @@ int nl80211_send_mgmt(struct cfg80211_registered_device *rdev,
(info->ack_tstamp && nla_put_u64_64bit(msg,
NL80211_ATTR_TX_HW_TIMESTAMP,
info->ack_tstamp,
- NL80211_ATTR_PAD)))
+ NL80211_ATTR_PAD)) ||
+ (info->no_sta &&
+ nla_put_flag(msg, NL80211_ATTR_FRAME_CMD_NO_STA)))
goto nla_put_failure;
genlmsg_end(msg, hdr);
|
| 3/2 | 2026/08/24 20:41 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 33122 output: 286 thoughts: 1250 Thoughts: **Fuzzing Candidate Assessment: Wireless Subsystem Patch** Okay, here's what I'm thinking as I review this patch series. It's a pretty substantial change, touching a significant number of files across the wireless subsystem. My initial assessment is that this absolutely warrants fuzzing. We're looking at modifications to `mac80211`, `cfg80211`, and several key wireless drivers – `ath11k`, `ath12k`, `iwlwifi`, `mt76`, `rtw89`, and `mac80211_hwsim`. That's a wide reach, indicating the potential for broad impact. The core of this patch seems to revolve around changes to the signatures of `ieee80211_rx_napi` and `ieee80211_rx_list`. The fact that we're moving from `struct ieee80211_sta *` to `struct ieee80211_link_sta *` is a signal of a likely functional change. The removal of the `link_valid` flag in `struct ieee80211_rx_status` and the different handling of `link_id` are also significant changes that need careful scrutiny. This looks like a core mac80211 modification, with ripples that can cause very subtle and possibly hard-to-find bugs if the edge cases aren't covered, and the new data structures are not treated as well-tested. The fact that this includes changes to the virtual driver `mac80211_hwsim` is a red flag, as this functionality is accessible in virtualized environments. That opens the door for fuzzing in a controlled setting, and there's a strong likelihood of triggering exploitable behaviour within that. My next step is to identify the crucial configurations and symbols we need to target. I'll need to focus on: `MAC80211`, `MAC80211_HWSIM`, and `CFG80211` as the key configurations. Now for the symbols, the obvious ones are: `ieee80211_rx_napi`, `ieee80211_rx_list`. Then, I'll need to expand the search to all related functions: `__ieee80211_rx_handle_packet`, `__ieee80211_rx_handle_8023`, `ieee80211_rx_for_interface`, `ieee80211_rx_h_data`, `ieee80211_rx_h_mgmt`, `ieee80211_rx_h_action`, `ieee80211_rx_h_action_return`, `ieee80211_rx_h_userspace_mgmt`, `ieee80211_rx_h_ext`, `ieee80211_rx_h_defragment`, `ieee80211_rx_reorder_ampdu`, `ieee80211_rx_8023`, `ieee80211_prepare_and_rx_handle`, `ieee80211_invoke_fast_rx`, `ieee80211_rx_handlers`, `ieee80211_invoke_rx_handlers`. Also, since we're dealing with wireless, let's not forget to consider `nl80211_tx_mgmt` and `nl80211_send_mgmt`, as those functions are always a potential attack surface. Now, considering the base configurations `MAC80211` and `CFG80211`, which are pretty standard, I can be satisfied that those alone are a sufficient starting point. So, let's get the `set-results` tool fired up and get this fuzzing plan in motion. |
| 4/2 | 2026/08/24 20:41 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"MAC80211",
"MAC80211_HWSIM",
"CFG80211"
],
"FocusSymbols": [
"ieee80211_rx_napi",
"ieee80211_rx_list",
"__ieee80211_rx_handle_packet",
"__ieee80211_rx_handle_8023",
"ieee80211_prepare_and_rx_handle",
"ieee80211_rx_handlers",
"nl80211_tx_mgmt",
"nl80211_send_mgmt"
],
"Reasoning": "The patch modifies the core mac80211 RX path, changing the signature of ieee80211_rx_napi and ieee80211_rx_list to take a link_sta instead of a sta. It also modifies how link_id is handled in RX status. These changes affect the core 802.11 stack and virtual drivers like mac80211_hwsim, making them reachable and worth fuzzing.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"MAC80211",
"MAC80211_HWSIM",
"CFG80211"
],
"FocusSymbols": [
"ieee80211_rx_napi",
"ieee80211_rx_list",
"__ieee80211_rx_handle_packet",
"__ieee80211_rx_handle_8023",
"ieee80211_prepare_and_rx_handle",
"ieee80211_rx_handlers",
"nl80211_tx_mgmt",
"nl80211_send_mgmt"
],
"Reasoning": "The patch modifies the core mac80211 RX path, changing the signature of ieee80211_rx_napi and ieee80211_rx_list to take a link_sta instead of a sta. It also modifies how link_id is handled in RX status. These changes affect the core 802.11 stack and virtual drivers like mac80211_hwsim, making them reachable and worth fuzzing.",
"WorthFuzzing": true
} |
| 5/1 | 2026/08/24 20:41 | action | If |
0mArgs:{
"WorthFuzzing": true
}Results: null |
| 6/2 | 2026/08/24 20:41 | agent | kmsan-evaluator |
0mModel:best-expensive Args: null Results: {
"KMSANReasoning": "The patch refactors how `link_id` and `link_valid` are passed from wireless drivers to mac80211. Instead of setting `link_valid` and `link_id` in `struct ieee80211_rx_status` (which is stored in `skb-\u003ecb`), drivers now resolve the `link_sta` and pass it directly to `ieee80211_rx_napi()` and `ieee80211_rx_list()`. The patch also adds a `no_sta` boolean to `struct cfg80211_mgmt_tx_params` and `struct cfg80211_rx_info`, which is exposed to userspace via a new netlink flag attribute `NL80211_ATTR_FRAME_CMD_NO_STA`.\n\nSince `NL80211_ATTR_FRAME_CMD_NO_STA` is an `NLA_FLAG`, it carries no payload and does not leak any uninitialized memory to userspace. The changes to `struct ieee80211_rx_status` only affect internal kernel bitfields and do not expose uninitialized memory. Any potential bugs introduced by this patch would be logic errors, NULL pointer dereferences, or out-of-bounds accesses (e.g., incorrect link ID indexing), which are well within the capabilities of standard KASAN and other bug detectors. There is no risk of uninitialized memory usage or info-leaks that would require a dedicated KMSAN fuzzing session.",
"NeedsKMSAN": false
}Instruction: You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.
CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
a comprehensive suite of debugging tools and sanitizers, including KASAN
(out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
(locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
or page allocations) and kernel-to-user memory info-leaks.
Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.
Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.
Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
attributes, ioctl output arguments, socket options, or BPF buffers) where fields
or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
uninitialized bytes of existing buffers.
Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
dereferences, locking deadlocks, or use-after-free bugs (these are already caught
by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.
Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt: For your convenience, here is the diff of the changes:
commit d20e704263f8e8096339e5e49f4820aff11946db
Author: syz-cluster <triage@syzkaller.com>
Date: Mon Aug 24 20:41:33 2026 +0000
syz-cluster: applied patch under review
diff --git a/drivers/net/wireless/ath/ath11k/dp_rx.c b/drivers/net/wireless/ath/ath11k/dp_rx.c
index 33425707c0842..fbb3d18f55ddd 100644
--- a/drivers/net/wireless/ath/ath11k/dp_rx.c
+++ b/drivers/net/wireless/ath/ath11k/dp_rx.c
@@ -2500,7 +2500,7 @@ static void ath11k_dp_rx_deliver_msdu(struct ath11k *ar, struct napi_struct *nap
!(is_mcbc && rx_status->flag & RX_FLAG_DECRYPTED))
rx_status->flag |= RX_FLAG_8023;
- ieee80211_rx_napi(ar->hw, pubsta, msdu, napi);
+ ieee80211_rx_napi(ar->hw, pubsta ? &pubsta->deflink : NULL, msdu, napi);
}
static bool ath11k_dp_rx_check_nwifi_hdr_len_valid(struct ath11k_base *ab,
diff --git a/drivers/net/wireless/ath/ath12k/dp_mon.c b/drivers/net/wireless/ath/ath12k/dp_mon.c
index 7d5be77b081f8..1faf1896d0ab1 100644
--- a/drivers/net/wireless/ath/ath12k/dp_mon.c
+++ b/drivers/net/wireless/ath/ath12k/dp_mon.c
@@ -503,8 +503,6 @@ void ath12k_dp_mon_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev,
struct ieee80211_rx_status *rx_status;
struct ieee80211_radiotap_he *he = NULL;
- status->link_valid = 0;
-
if ((status->encoding == RX_ENC_HE) && !(status->flag & RX_FLAG_RADIOTAP_HE) &&
!(status->flag & RX_FLAG_SKIP_MONITOR)) {
he = skb_push(msdu, sizeof(known));
diff --git a/drivers/net/wireless/ath/ath12k/dp_rx.c b/drivers/net/wireless/ath/ath12k/dp_rx.c
index 8fa0e90b45316..41ac0a4072aeb 100644
--- a/drivers/net/wireless/ath/ath12k/dp_rx.c
+++ b/drivers/net/wireless/ath/ath12k/dp_rx.c
@@ -1375,6 +1375,7 @@ void ath12k_dp_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev, struct napi_struc
struct ath12k_dp *dp = dp_pdev->dp;
struct ieee80211_rx_status *rx_status;
struct ieee80211_sta *pubsta;
+ struct ieee80211_link_sta *link_pubsta = NULL;
struct ath12k_dp_peer *peer;
struct ath12k_skb_rxcb *rxcb = ATH12K_SKB_RXCB(msdu);
struct ieee80211_rx_status *status = rx_info->rx_status;
@@ -1384,9 +1385,19 @@ void ath12k_dp_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev, struct napi_struc
pubsta = peer ? peer->sta : NULL;
- status->link_valid = 0;
- if (pubsta && pubsta->valid_links)
- ath12k_hw_set_rx_link_id(dp->hw_params, peer, rxcb, status);
+ if (pubsta) {
+ if (pubsta->valid_links) {
+ int link_id =
+ ath12k_hw_get_rx_link_id(dp->hw_params, peer,
+ rxcb, status);
+
+ if (link_id >= 0)
+ link_pubsta =
+ rcu_dereference(pubsta->link[link_id]);
+ } else {
+ link_pubsta = &pubsta->deflink;
+ }
+ }
ath12k_dbg(dp->ab, ATH12K_DBG_DATA,
"rx skb %p len %u peer %pM %d %s sn %u %s%s%s%s%s%s%s%s%s%s rate_idx %u vht_nss %u freq %u band %u flag 0x%x fcs-err %i mic-err %i amsdu-more %i\n",
@@ -1422,7 +1433,8 @@ void ath12k_dp_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev, struct napi_struc
/* TODO: trace rx packet */
- ieee80211_rx_napi(ath12k_pdev_dp_to_hw(dp_pdev), pubsta, msdu, napi);
+ ieee80211_rx_napi(ath12k_pdev_dp_to_hw(dp_pdev), link_pubsta, msdu,
+ napi);
}
EXPORT_SYMBOL(ath12k_dp_rx_deliver_msdu);
diff --git a/drivers/net/wireless/ath/ath12k/hw.h b/drivers/net/wireless/ath/ath12k/hw.h
index 3ed38f8f2b48a..7a2345f883d70 100644
--- a/drivers/net/wireless/ath/ath12k/hw.h
+++ b/drivers/net/wireless/ath/ath12k/hw.h
@@ -247,9 +247,9 @@ struct ath12k_hw_ops {
bool (*dp_srng_is_tx_comp_ring)(int ring_num);
bool (*is_frame_link_agnostic)(struct ath12k_link_vif *arvif,
struct ieee80211_mgmt *mgmt);
- void (*set_rx_link_id)(struct ath12k_dp_peer *dp_peer,
- struct ath12k_skb_rxcb *rxcb,
- struct ieee80211_rx_status *status);
+ int (*get_rx_link_id)(struct ath12k_dp_peer *dp_peer,
+ struct ath12k_skb_rxcb *rxcb,
+ struct ieee80211_rx_status *status);
};
static inline
@@ -280,13 +280,15 @@ static inline int ath12k_hw_mac_id_to_srng_id(const struct ath12k_hw_params *hw,
return 0;
}
-static inline void ath12k_hw_set_rx_link_id(const struct ath12k_hw_params *hw,
- struct ath12k_dp_peer *dp_peer,
- struct ath12k_skb_rxcb *rxcb,
- struct ieee80211_rx_status *status)
+static inline int ath12k_hw_get_rx_link_id(const struct ath12k_hw_params *hw,
+ struct ath12k_dp_peer *dp_peer,
+ struct ath12k_skb_rxcb *rxcb,
+ struct ieee80211_rx_status *status)
{
- if (hw->hw_ops->set_rx_link_id)
- hw->hw_ops->set_rx_link_id(dp_peer, rxcb, status);
+ if (hw->hw_ops->get_rx_link_id)
+ return hw->hw_ops->get_rx_link_id(dp_peer, rxcb, status);
+
+ return -1;
}
struct ath12k_fw_ie {
diff --git a/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.c b/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.c
index 95d87dd67872f..8b722ee0ad8fa 100644
--- a/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.c
+++ b/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.c
@@ -2317,17 +2317,16 @@ ath12k_wifi7_dp_rxdesc_mpdu_valid(struct ath12k_base *ab,
return tlv_tag == HAL_RX_MPDU_START;
}
-void
-ath12k_wifi7_dp_rx_set_link_id_qcn9274(struct ath12k_dp_peer *dp_peer,
+int
+ath12k_wifi7_dp_rx_get_link_id_qcn9274(struct ath12k_dp_peer *dp_peer,
struct ath12k_skb_rxcb *rxcb,
struct ieee80211_rx_status *status)
{
- status->link_valid = 1;
- status->link_id = dp_peer->hw_links[rxcb->hw_link_id];
+ return dp_peer->hw_links[rxcb->hw_link_id];
}
-void
-ath12k_wifi7_dp_rx_set_link_id_wcn7850(struct ath12k_dp_peer *dp_peer,
+int
+ath12k_wifi7_dp_rx_get_link_id_wcn7850(struct ath12k_dp_peer *dp_peer,
struct ath12k_skb_rxcb *rxcb,
struct ieee80211_rx_status *status)
{
@@ -2341,10 +2340,9 @@ ath12k_wifi7_dp_rx_set_link_id_wcn7850(struct ath12k_dp_peer *dp_peer,
links_map = READ_ONCE(dp_peer->link_peers_map);
for_each_set_bit(i, &links_map, ATH12K_NUM_MAX_LINKS) {
link_peer = rcu_dereference(dp_peer->link_peers[i]);
- if (link_peer && link_peer->peer_id == rxcb->peer_id) {
- status->link_valid = 1;
- status->link_id = link_peer->link_id;
- return;
- }
+ if (link_peer && link_peer->peer_id == rxcb->peer_id)
+ return link_peer->link_id;
}
+
+ return -1;
}
diff --git a/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.h b/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.h
index 1d3a4788a2dd9..d68489ff7b037 100644
--- a/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.h
+++ b/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.h
@@ -57,10 +57,10 @@ ath12k_wifi7_dp_rxdesc_mpdu_valid(struct ath12k_base *ab,
struct hal_rx_desc *rx_desc);
int ath12k_wifi7_dp_rx_tid_delete_handler(struct ath12k_base *ab,
struct ath12k_dp_rx_tid_rxq *rx_tid);
-void ath12k_wifi7_dp_rx_set_link_id_qcn9274(struct ath12k_dp_peer *dp_peer,
- struct ath12k_skb_rxcb *rxcb,
- struct ieee80211_rx_status *status);
-void ath12k_wifi7_dp_rx_set_link_id_wcn7850(struct ath12k_dp_peer *dp_peer,
- struct ath12k_skb_rxcb *rxcb,
- struct ieee80211_rx_status *status);
+int ath12k_wifi7_dp_rx_get_link_id_qcn9274(struct ath12k_dp_peer *dp_peer,
+ struct ath12k_skb_rxcb *rxcb,
+ struct ieee80211_rx_status *status);
+int ath12k_wifi7_dp_rx_get_link_id_wcn7850(struct ath12k_dp_peer *dp_peer,
+ struct ath12k_skb_rxcb *rxcb,
+ struct ieee80211_rx_status *status);
#endif
diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hw.c b/drivers/net/wireless/ath/ath12k/wifi7/hw.c
index d890801d58223..a6ac4a40f7cc2 100644
--- a/drivers/net/wireless/ath/ath12k/wifi7/hw.c
+++ b/drivers/net/wireless/ath/ath12k/wifi7/hw.c
@@ -158,7 +158,7 @@ static const struct ath12k_hw_ops qcn9274_ops = {
.get_ring_selector = ath12k_wifi7_hw_get_ring_selector_qcn9274,
.dp_srng_is_tx_comp_ring = ath12k_wifi7_dp_srng_is_comp_ring_qcn9274,
.is_frame_link_agnostic = ath12k_wifi7_is_frame_link_agnostic_qcn9274,
- .set_rx_link_id = ath12k_wifi7_dp_rx_set_link_id_qcn9274,
+ .get_rx_link_id = ath12k_wifi7_dp_rx_get_link_id_qcn9274,
};
static const struct ath12k_hw_ops wcn7850_ops = {
@@ -169,7 +169,7 @@ static const struct ath12k_hw_ops wcn7850_ops = {
.get_ring_selector = ath12k_wifi7_hw_get_ring_selector_wcn7850,
.dp_srng_is_tx_comp_ring = ath12k_wifi7_dp_srng_is_comp_ring_wcn7850,
.is_frame_link_agnostic = ath12k_wifi7_is_frame_link_agnostic_wcn7850,
- .set_rx_link_id = ath12k_wifi7_dp_rx_set_link_id_wcn7850,
+ .get_rx_link_id = ath12k_wifi7_dp_rx_get_link_id_wcn7850,
};
static const struct ath12k_hw_ops qcc2072_ops = {
@@ -180,7 +180,7 @@ static const struct ath12k_hw_ops qcc2072_ops = {
.get_ring_selector = ath12k_wifi7_hw_get_ring_selector_wcn7850,
.dp_srng_is_tx_comp_ring = ath12k_wifi7_dp_srng_is_comp_ring_wcn7850,
.is_frame_link_agnostic = ath12k_wifi7_is_frame_link_agnostic_wcn7850,
- .set_rx_link_id = ath12k_wifi7_dp_rx_set_link_id_wcn7850,
+ .get_rx_link_id = ath12k_wifi7_dp_rx_get_link_id_wcn7850,
};
#define ATH12K_TX_RING_MASK_0 0x1
diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c
index d5160af60e00d..7e3655966f02d 100644
--- a/drivers/net/wireless/ath/ath12k/wmi.c
+++ b/drivers/net/wireless/ath/ath12k/wmi.c
@@ -7271,7 +7271,6 @@ static void ath12k_mgmt_rx_event(struct ath12k_base *ab, struct sk_buff *skb)
struct ath12k_wmi_mgmt_rx_arg rx_ev = {};
struct ath12k *ar;
struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
- struct ieee80211_sta *pubsta = NULL;
struct ath12k_dp_link_peer *peer;
struct ieee80211_hdr *hdr;
bool is_4addr_null_pkt;
@@ -7373,11 +7372,6 @@ static void ath12k_mgmt_rx_event(struct ath12k_base *ab, struct sk_buff *skb)
dev_kfree_skb(skb);
goto exit;
}
- pubsta = peer->sta;
- if (pubsta && pubsta->valid_links) {
- status->link_valid = 1;
- status->link_id = peer->link_id;
- }
spin_unlock_bh(&dp->dp_lock);
goto send_rx;
}
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/agg.c b/drivers/net/wireless/intel/iwlwifi/mld/agg.c
index 96102d2af2cf9..c45c47337509e 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/agg.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/agg.c
@@ -7,7 +7,8 @@
#include "hcmd.h"
static void
-iwl_mld_reorder_release_frames(struct iwl_mld *mld, struct ieee80211_sta *sta,
+iwl_mld_reorder_release_frames(struct iwl_mld *mld,
+ struct ieee80211_link_sta *link_sta,
struct napi_struct *napi,
struct iwl_mld_baid_data *baid_data,
struct iwl_mld_reorder_buffer *reorder_buf,
@@ -32,7 +33,7 @@ iwl_mld_reorder_release_frames(struct iwl_mld *mld, struct ieee80211_sta *sta,
while ((skb = __skb_dequeue(skb_list))) {
iwl_mld_pass_packet_to_mac80211(mld, napi, skb,
reorder_buf->queue,
- sta);
+ link_sta);
reorder_buf->num_stored--;
}
}
@@ -74,7 +75,7 @@ static void iwl_mld_release_frames_from_notif(struct iwl_mld *mld,
reorder_buf = &ba_data->reorder_buf[queue];
- iwl_mld_reorder_release_frames(mld, link_sta->sta, napi, ba_data,
+ iwl_mld_reorder_release_frames(mld, link_sta, napi, ba_data,
reorder_buf, nssn);
out_unlock:
rcu_read_unlock();
@@ -180,7 +181,7 @@ void iwl_mld_del_ba(struct iwl_mld *mld, int queue,
reorder_buf = &ba_data->reorder_buf[queue];
/* release all frames that are in the reorder buffer to the stack */
- iwl_mld_reorder_release_frames(mld, link_sta->sta, NULL,
+ iwl_mld_reorder_release_frames(mld, link_sta, NULL,
ba_data, reorder_buf,
ieee80211_sn_add(reorder_buf->head_sn,
ba_data->buf_size));
@@ -193,7 +194,7 @@ void iwl_mld_del_ba(struct iwl_mld *mld, int queue,
*/
enum iwl_mld_reorder_result
iwl_mld_reorder(struct iwl_mld *mld, struct napi_struct *napi,
- int queue, struct ieee80211_sta *sta,
+ int queue, struct ieee80211_link_sta *link_sta,
struct sk_buff *skb, struct iwl_rx_mpdu_desc *desc)
{
struct ieee80211_hdr *hdr = (void *)skb_mac_header(skb);
@@ -228,12 +229,12 @@ iwl_mld_reorder(struct iwl_mld *mld, struct napi_struct *napi,
return IWL_MLD_PASS_SKB;
/* no sta yet */
- if (IWL_FW_CHECK(mld, !sta,
+ if (IWL_FW_CHECK(mld, !link_sta,
"Got valid BAID without a valid station assigned - %d\n",
baid))
return IWL_MLD_PASS_SKB;
- mld_sta = iwl_mld_sta_from_mac80211(sta);
+ mld_sta = iwl_mld_sta_from_mac80211(link_sta->sta);
/* not a data packet */
if (!ieee80211_is_data_qos(hdr->frame_control) ||
@@ -324,7 +325,7 @@ iwl_mld_reorder(struct iwl_mld *mld, struct napi_struct *napi,
* will be released when the frame release notification arrives.
*/
if (!amsdu || last_subframe)
- iwl_mld_reorder_release_frames(mld, sta, napi, baid_data,
+ iwl_mld_reorder_release_frames(mld, link_sta, napi, baid_data,
buffer, nssn);
else if (buffer->num_stored == 1)
buffer->head_sn = nssn;
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/agg.h b/drivers/net/wireless/intel/iwlwifi/mld/agg.h
index 651c80d1c7cda..c6cd5fa219bed 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/agg.h
+++ b/drivers/net/wireless/intel/iwlwifi/mld/agg.h
@@ -1,6 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */
/*
- * Copyright (C) 2024 Intel Corporation
+ * Copyright (C) 2024, 2026 Intel Corporation
*/
#ifndef __iwl_agg_h__
#define __iwl_agg_h__
@@ -106,7 +106,7 @@ int iwl_mld_ampdu_rx_stop(struct iwl_mld *mld, struct ieee80211_sta *sta,
enum iwl_mld_reorder_result
iwl_mld_reorder(struct iwl_mld *mld, struct napi_struct *napi,
- int queue, struct ieee80211_sta *sta,
+ int queue, struct ieee80211_link_sta *link_sta,
struct sk_buff *skb, struct iwl_rx_mpdu_desc *desc);
void iwl_mld_handle_frame_release_notif(struct iwl_mld *mld,
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/rx.c b/drivers/net/wireless/intel/iwlwifi/mld/rx.c
index 269439d789f46..02bdf6ccbcdc9 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/rx.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/rx.c
@@ -47,7 +47,8 @@ iwl_mld_fill_phy_data_from_mpdu(struct iwl_mld *mld,
}
static inline int iwl_mld_check_pn(struct iwl_mld *mld, struct sk_buff *skb,
- int queue, struct ieee80211_sta *sta)
+ int queue,
+ struct ieee80211_link_sta *link_sta)
{
struct ieee80211_hdr *hdr = (void *)skb_mac_header(skb);
struct ieee80211_rx_status *stats = IEEE80211_SKB_RXCB(skb);
@@ -71,13 +72,13 @@ static inline int iwl_mld_check_pn(struct iwl_mld *mld, struct sk_buff *skb,
return 0;
/* if we are here - this for sure is either CCMP or GCMP */
- if (!sta) {
+ if (!link_sta) {
IWL_DEBUG_DROP(mld,
"expected hw-decrypted unicast frame for station\n");
return -1;
}
- mld_sta = iwl_mld_sta_from_mac80211(sta);
+ mld_sta = iwl_mld_sta_from_mac80211(link_sta->sta);
extiv = (u8 *)hdr + ieee80211_hdrlen(hdr->frame_control);
keyidx = extiv[3] >> 6;
@@ -119,17 +120,17 @@ static inline int iwl_mld_check_pn(struct iwl_mld *mld, struct sk_buff *skb,
void iwl_mld_pass_packet_to_mac80211(struct iwl_mld *mld,
struct napi_struct *napi,
struct sk_buff *skb, int queue,
- struct ieee80211_sta *sta)
+ struct ieee80211_link_sta *link_sta)
{
KUNIT_STATIC_STUB_REDIRECT(iwl_mld_pass_packet_to_mac80211,
- mld, napi, skb, queue, sta);
+ mld, napi, skb, queue, link_sta);
- if (unlikely(iwl_mld_check_pn(mld, skb, queue, sta))) {
+ if (unlikely(iwl_mld_check_pn(mld, skb, queue, link_sta))) {
kfree_skb(skb);
return;
}
- ieee80211_rx_napi(mld->hw, sta, skb, napi);
+ ieee80211_rx_napi(mld->hw, link_sta, skb, napi);
}
EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(iwl_mld_pass_packet_to_mac80211);
@@ -1728,7 +1729,7 @@ static void iwl_mld_update_last_rx_timestamp(struct iwl_mld *mld, u8 baid)
* Sets *drop to true if the packet should be dropped.
* Returns the station if found, or NULL otherwise.
*/
-static struct ieee80211_sta *
+static struct ieee80211_link_sta *
iwl_mld_rx_with_sta(struct iwl_mld *mld, struct ieee80211_hdr *hdr,
struct sk_buff *skb,
const struct iwl_rx_mpdu_desc *mpdu_desc,
@@ -1764,11 +1765,6 @@ iwl_mld_rx_with_sta(struct iwl_mld *mld, struct ieee80211_hdr *hdr,
rx_status = IEEE80211_SKB_RXCB(skb);
- if (link_sta && sta->valid_links) {
- rx_status->link_valid = true;
- rx_status->link_id = link_sta->link_id;
- }
-
/* fill checksum */
if (ieee80211_is_data(hdr->frame_control) &&
pkt->len_n_flags & cpu_to_le32(FH_RSCSR_RPA_EN)) {
@@ -1803,10 +1799,10 @@ iwl_mld_rx_with_sta(struct iwl_mld *mld, struct ieee80211_hdr *hdr,
queue);
}
- return sta;
+ return link_sta;
}
-static int iwl_mld_rx_mgmt_prot(struct ieee80211_sta *sta,
+static int iwl_mld_rx_mgmt_prot(struct ieee80211_link_sta *link_sta,
struct ieee80211_hdr *hdr,
struct ieee80211_rx_status *rx_status,
u32 mpdu_status,
@@ -1820,7 +1816,6 @@ static int iwl_mld_rx_mgmt_prot(struct ieee80211_sta *sta,
struct ieee80211_key_conf *key;
const u8 *frame = (void *)hdr;
const u8 *mmie;
- u8 link_id;
if ((mpdu_status & IWL_RX_MPDU_STATUS_SEC_MASK) ==
IWL_RX_MPDU_STATUS_SEC_NONE)
@@ -1836,10 +1831,10 @@ static int iwl_mld_rx_mgmt_prot(struct ieee80211_sta *sta,
if (!ieee80211_is_beacon(hdr->frame_control))
return 0;
- if (!sta)
+ if (!link_sta)
return -1;
- mld_sta = iwl_mld_sta_from_mac80211(sta);
+ mld_sta = iwl_mld_sta_from_mac80211(link_sta->sta);
mld_vif = iwl_mld_vif_from_mac80211(mld_sta->vif);
/* key mismatch - will also report !MIC_OK but we shouldn't count it */
@@ -1853,8 +1848,7 @@ static int iwl_mld_rx_mgmt_prot(struct ieee80211_sta *sta,
return 0;
}
- link_id = rx_status->link_valid ? rx_status->link_id : 0;
- link = rcu_dereference(mld_vif->link[link_id]);
+ link = rcu_dereference(mld_vif->link[link_sta->link_id]);
if (WARN_ON_ONCE(!link))
return -1;
@@ -1905,7 +1899,7 @@ static int iwl_mld_rx_mgmt_prot(struct ieee80211_sta *sta,
}
static int iwl_mld_rx_crypto(struct iwl_mld *mld,
- struct ieee80211_sta *sta,
+ struct ieee80211_link_sta *link_sta,
struct ieee80211_hdr *hdr,
struct ieee80211_rx_status *rx_status,
struct iwl_rx_mpdu_desc *desc, int queue,
@@ -1915,7 +1909,7 @@ static int iwl_mld_rx_crypto(struct iwl_mld *mld,
if (unlikely(ieee80211_is_mgmt(hdr->frame_control) &&
!ieee80211_has_protected(hdr->frame_control)))
- return iwl_mld_rx_mgmt_prot(sta, hdr, rx_status, status,
+ return iwl_mld_rx_mgmt_prot(link_sta, hdr, rx_status, status,
le16_to_cpu(desc->mpdu_len));
if (!ieee80211_has_protected(hdr->frame_control) ||
@@ -2023,7 +2017,7 @@ void iwl_mld_rx_mpdu(struct iwl_mld *mld, struct napi_struct *napi,
struct iwl_rx_packet *pkt = rxb_addr(rxb);
struct iwl_mld_rx_phy_data phy_data = {};
struct iwl_rx_mpdu_desc *mpdu_desc = (void *)pkt->data;
- struct ieee80211_sta *sta;
+ struct ieee80211_link_sta *link_sta;
struct ieee80211_hdr *hdr;
struct sk_buff *skb;
size_t mpdu_desc_size = sizeof(*mpdu_desc);
@@ -2086,7 +2080,8 @@ void iwl_mld_rx_mpdu(struct iwl_mld *mld, struct napi_struct *napi,
rcu_read_lock();
- sta = iwl_mld_rx_with_sta(mld, hdr, skb, mpdu_desc, pkt, queue, &drop);
+ link_sta = iwl_mld_rx_with_sta(mld, hdr, skb, mpdu_desc, pkt, queue,
+ &drop);
if (drop)
goto drop;
@@ -2127,7 +2122,7 @@ void iwl_mld_rx_mpdu(struct iwl_mld *mld, struct napi_struct *napi,
iwl_mld_rx_fill_status(mld, link_id, hdr, skb, &phy_data);
- if (iwl_mld_rx_crypto(mld, sta, hdr, rx_status, mpdu_desc, queue,
+ if (iwl_mld_rx_crypto(mld, link_sta, hdr, rx_status, mpdu_desc, queue,
le32_to_cpu(pkt->len_n_flags), &crypto_len))
goto drop;
@@ -2140,7 +2135,8 @@ void iwl_mld_rx_mpdu(struct iwl_mld *mld, struct napi_struct *napi,
if (iwl_mld_time_sync_frame(mld, skb, hdr->addr2))
goto out;
- reorder_res = iwl_mld_reorder(mld, napi, queue, sta, skb, mpdu_desc);
+ reorder_res = iwl_mld_reorder(mld, napi, queue, link_sta, skb,
+ mpdu_desc);
switch (reorder_res) {
case IWL_MLD_PASS_SKB:
break;
@@ -2153,7 +2149,7 @@ void iwl_mld_rx_mpdu(struct iwl_mld *mld, struct napi_struct *napi,
goto drop;
}
- iwl_mld_pass_packet_to_mac80211(mld, napi, skb, queue, sta);
+ iwl_mld_pass_packet_to_mac80211(mld, napi, skb, queue, link_sta);
goto out;
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/rx.h b/drivers/net/wireless/intel/iwlwifi/mld/rx.h
index 573b89c3c9c61..b08c5ade3db8f 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/rx.h
+++ b/drivers/net/wireless/intel/iwlwifi/mld/rx.h
@@ -69,7 +69,7 @@ void iwl_mld_handle_rsc_notif(struct iwl_mld *mld,
void iwl_mld_pass_packet_to_mac80211(struct iwl_mld *mld,
struct napi_struct *napi,
struct sk_buff *skb, int queue,
- struct ieee80211_sta *sta);
+ struct ieee80211_link_sta *link_sta);
void iwl_mld_handle_phy_air_sniffer_notif(struct iwl_mld *mld,
struct napi_struct *napi,
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/tests/agg.c b/drivers/net/wireless/intel/iwlwifi/mld/tests/agg.c
index 29b0248cec3df..e9efe2996f071 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/tests/agg.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/tests/agg.c
@@ -2,7 +2,7 @@
/*
* KUnit tests for channel helper functions
*
- * Copyright (C) 2024-2025 Intel Corporation
+ * Copyright (C) 2024-2026 Intel Corporation
*/
#include <kunit/test.h>
#include <kunit/static_stub.h>
@@ -441,7 +441,7 @@ static void
fake_iwl_mld_pass_packet_to_mac80211(struct iwl_mld *mld,
struct napi_struct *napi,
struct sk_buff *skb, int queue,
- struct ieee80211_sta *sta)
+ struct ieee80211_link_sta *link_sta)
{
__skb_queue_tail(&g_released_skbs, skb);
g_num_released_skbs++;
@@ -630,7 +630,8 @@ static void test_reorder_buffer(struct kunit *test)
mpdu_desc = setup_mpdu_desc();
rcu_read_lock();
- reorder_res = iwl_mld_reorder(mld, NULL, QUEUE, sta, skb, mpdu_desc);
+ reorder_res = iwl_mld_reorder(mld, NULL, QUEUE, &sta->deflink, skb,
+ mpdu_desc);
rcu_read_unlock();
KUNIT_ASSERT_EQ(test, reorder_res, param->expected.reorder_res);
diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/rx.c b/drivers/net/wireless/intel/iwlwifi/mvm/rx.c
index ab1eb2eb0c3cd..8c197a090c2b1 100644
--- a/drivers/net/wireless/intel/iwlwifi/mvm/rx.c
+++ b/drivers/net/wireless/intel/iwlwifi/mvm/rx.c
@@ -90,7 +90,7 @@ static void iwl_mvm_pass_packet_to_mac80211(struct iwl_mvm *mvm,
fraglen, rxb->truesize);
}
- ieee80211_rx_napi(mvm->hw, sta, skb, napi);
+ ieee80211_rx_napi(mvm->hw, sta ? &sta->deflink : NULL, skb, napi);
}
/*
diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/rxmq.c b/drivers/net/wireless/intel/iwlwifi/mvm/rxmq.c
index 7f0b4f5daa214..8c4009d3a00ca 100644
--- a/drivers/net/wireless/intel/iwlwifi/mvm/rxmq.c
+++ b/drivers/net/wireless/intel/iwlwifi/mvm/rxmq.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
- * Copyright (C) 2012-2014, 2018-2025 Intel Corporation
+ * Copyright (C) 2012-2014, 2018-2026 Intel Corporation
* Copyright (C) 2013-2015 Intel Mobile Communications GmbH
* Copyright (C) 2015-2017 Intel Deutschland GmbH
*/
@@ -243,7 +243,7 @@ static void iwl_mvm_pass_packet_to_mac80211(struct iwl_mvm *mvm,
return;
}
- ieee80211_rx_napi(mvm->hw, sta, skb, napi);
+ ieee80211_rx_napi(mvm->hw, sta ? &sta->deflink : NULL, skb, napi);
}
static bool iwl_mvm_used_average_energy(struct iwl_mvm *mvm,
@@ -2528,7 +2528,7 @@ void iwl_mvm_rx_monitor_no_data(struct iwl_mvm *mvm, struct napi_struct *napi,
}
rcu_read_lock();
- ieee80211_rx_napi(mvm->hw, sta, skb, napi);
+ ieee80211_rx_napi(mvm->hw, sta ? &sta->deflink : NULL, skb, napi);
rcu_read_unlock();
}
diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c
index abbe65cbcd89e..3da06a55913ef 100644
--- a/drivers/net/wireless/mediatek/mt76/mac80211.c
+++ b/drivers/net/wireless/mediatek/mt76/mac80211.c
@@ -1255,7 +1255,7 @@ EXPORT_SYMBOL(mt76_rx_signal);
static void
mt76_rx_convert(struct mt76_dev *dev, struct sk_buff *skb,
struct ieee80211_hw **hw,
- struct ieee80211_sta **sta)
+ struct ieee80211_link_sta **link_sta)
{
struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
struct ieee80211_hdr *hdr = mt76_skb_get_hdr(skb);
@@ -1302,11 +1302,15 @@ mt76_rx_convert(struct mt76_dev *dev, struct sk_buff *skb,
sizeof(mstat.chain_signal));
if (mstat.wcid) {
- status->link_valid = mstat.wcid->link_valid;
- status->link_id = mstat.wcid->link_id;
+ struct ieee80211_sta *sta = wcid_to_sta(mstat.wcid);
+
+ if (mstat.wcid->link_valid)
+ *link_sta =
+ rcu_dereference(sta->link[mstat.wcid->link_id]);
+ else
+ *link_sta = &sta->deflink;
}
- *sta = wcid_to_sta(mstat.wcid);
*hw = mt76_phy_hw(dev, mstat.phy_idx);
}
@@ -1530,7 +1534,7 @@ mt76_check_sta(struct mt76_dev *dev, struct sk_buff *skb)
void mt76_rx_complete(struct mt76_dev *dev, struct sk_buff_head *frames,
struct napi_struct *napi)
{
- struct ieee80211_sta *sta;
+ struct ieee80211_link_sta *link_sta;
struct ieee80211_hw *hw;
struct sk_buff *skb, *tmp;
LIST_HEAD(list);
@@ -1541,8 +1545,8 @@ void mt76_rx_complete(struct mt76_dev *dev, struct sk_buff_head *frames,
mt76_check_ccmp_pn(skb);
skb_shinfo(skb)->frag_list = NULL;
- mt76_rx_convert(dev, skb, &hw, &sta);
- ieee80211_rx_list(hw, sta, skb, &list);
+ mt76_rx_convert(dev, skb, &hw, &link_sta);
+ ieee80211_rx_list(hw, link_sta, skb, &list);
/* subsequent amsdu frames */
while (nskb) {
@@ -1550,8 +1554,8 @@ void mt76_rx_complete(struct mt76_dev *dev, struct sk_buff_head *frames,
nskb = nskb->next;
skb->next = NULL;
- mt76_rx_convert(dev, skb, &hw, &sta);
- ieee80211_rx_list(hw, sta, skb, &list);
+ mt76_rx_convert(dev, skb, &hw, &link_sta);
+ ieee80211_rx_list(hw, link_sta, skb, &list);
}
}
spin_unlock(&dev->rx_lock);
diff --git a/drivers/net/wireless/realtek/rtw89/core.c b/drivers/net/wireless/realtek/rtw89/core.c
index 397ebbfcac09a..454d876aeb886 100644
--- a/drivers/net/wireless/realtek/rtw89/core.c
+++ b/drivers/net/wireless/realtek/rtw89/core.c
@@ -3073,10 +3073,8 @@ static void rtw89_vif_rx_stats_iter(void *data, u8 *mac,
struct rtw89_vif *rtwvif = vif_to_rtwvif(vif);
struct rtw89_rx_desc_info *desc_info = iter_data->desc_info;
struct sk_buff *skb = iter_data->skb;
- struct ieee80211_rx_status *rx_status = IEEE80211_SKB_RXCB(skb);
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
struct rtw89_rx_phy_ppdu *phy_ppdu = iter_data->phy_ppdu;
- bool is_mld = ieee80211_vif_is_mld(vif);
struct ieee80211_bss_conf *bss_conf;
struct rtw89_vif_link *rtwvif_link;
const u8 *bssid = iter_data->bssid;
@@ -3110,11 +3108,6 @@ static void rtw89_vif_rx_stats_iter(void *data, u8 *mac,
if (!ether_addr_equal(target_bssid, bssid))
goto out;
- if (is_mld) {
- rx_status->link_valid = true;
- rx_status->link_id = rtwvif_link->link_id;
- }
-
bb = rtw89_get_bb_ctx(rtwdev, rtwvif_link->phy_idx);
pkt_stat = &bb->cur_pkt_stat;
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index 02b6d81cccd11..a0d519cd9c4fa 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -1880,9 +1880,6 @@ static void mac80211_hwsim_rx(struct mac80211_hwsim_data *data,
sp->active_links_rx &= ~BIT(link_id);
else
sp->active_links_rx |= BIT(link_id);
-
- rx_status->link_valid = true;
- rx_status->link_id = link_id;
}
rcu_read_unlock();
}
diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index 97c16d4ff1271..436478daf5596 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -4009,6 +4009,7 @@ struct cfg80211_update_ft_ies_params {
* @link_id: for MLO, the link ID to transmit on, -1 if not given; note
* that the link ID isn't validated (much), it's in range but the
* link might not exist (or be used by the receiver STA)
+ * @no_sta: set if the frame should not be transmitted using an existing STA
*/
struct cfg80211_mgmt_tx_params {
struct ieee80211_channel *chan;
@@ -4021,6 +4022,7 @@ struct cfg80211_mgmt_tx_params {
int n_csa_offsets;
const u16 *csa_offsets;
int link_id;
+ bool no_sta;
};
/**
@@ -9501,6 +9503,7 @@ void cfg80211_conn_failed(struct net_device *dev, const u8 *mac_addr,
* @flags: flags, as defined in &enum nl80211_rxmgmt_flags
* @rx_tstamp: Hardware timestamp of frame RX in nanoseconds
* @ack_tstamp: Hardware timestamp of ack TX in nanoseconds
+ * @no_sta: set if no station is known for the frame (relevant for MLD)
*/
struct cfg80211_rx_info {
int freq;
@@ -9512,6 +9515,7 @@ struct cfg80211_rx_info {
u32 flags;
u64 rx_tstamp;
u64 ack_tstamp;
+ bool no_sta;
};
/**
diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 9d1fac6e80829..7aae728e96d84 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -1770,10 +1770,9 @@ enum mac80211_rx_encoding {
* @ampdu_reference: A-MPDU reference number, must be a different value for
* each A-MPDU but the same for each subframe within one A-MPDU
* @zero_length_psdu_type: radiotap type of the 0-length PSDU
- * @link_valid: if the link which is identified by @link_id is valid. This flag
- * is set only when connection is MLO.
- * @link_id: id of the link used to receive the packet. This is used along with
- * @link_valid.
+ * @link_id: id of the link used to receive the packet. Set and used by
+ * mac80211 internally, it uses @freq set by the driver to identify the
+ * correct link per vif.
*/
struct ieee80211_rx_status {
u64 mactime;
@@ -1813,7 +1812,7 @@ struct ieee80211_rx_status {
u8 chains;
s8 chain_signal[IEEE80211_MAX_CHAINS];
u8 zero_length_psdu_type;
- u8 link_valid:1, link_id:4;
+ u8 link_id:4;
};
static_assert(sizeof(struct ieee80211_rx_status) <= sizeof_field(struct sk_buff, cb));
@@ -5389,14 +5388,18 @@ void ieee80211_restart_hw(struct ieee80211_hw *hw);
* mixed for a single hardware. Must not run concurrently with
* ieee80211_tx_status_skb() or ieee80211_tx_status_ni().
*
+ * For data frames, when hardware has done address translation, a link station
+ * has to be provided and the frequency information may be skipped.
+ *
* This function must be called with BHs disabled and RCU read lock
*
* @hw: the hardware this frame came in on
- * @sta: the station the frame was received from, or %NULL
+ * @link_sta: the link station the data frame was received from, or %NULL
* @skb: the buffer to receive, owned by mac80211 after this call
* @list: the destination list
*/
-void ieee80211_rx_list(struct ieee80211_hw *hw, struct ieee80211_sta *sta,
+void ieee80211_rx_list(struct ieee80211_hw *hw,
+ struct ieee80211_link_sta *link_sta,
struct sk_buff *skb, struct list_head *list);
/**
@@ -5414,14 +5417,18 @@ void ieee80211_rx_list(struct ieee80211_hw *hw, struct ieee80211_sta *sta,
* mixed for a single hardware. Must not run concurrently with
* ieee80211_tx_status_skb() or ieee80211_tx_status_ni().
*
+ * For data frames, when hardware has done address translation, a link station
+ * has to be provided and the frequency information may be skipped.
+ *
* This function must be called with BHs disabled.
*
* @hw: the hardware this frame came in on
- * @sta: the station the frame was received from, or %NULL
+ * @link_sta: the link station the data frame was received from, or %NULL
* @skb: the buffer to receive, owned by mac80211 after this call
* @napi: the NAPI context
*/
-void ieee80211_rx_napi(struct ieee80211_hw *hw, struct ieee80211_sta *sta,
+void ieee80211_rx_napi(struct ieee80211_hw *hw,
+ struct ieee80211_link_sta *link_sta,
struct sk_buff *skb, struct napi_struct *napi);
/**
diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h
index 020387d764122..4653f8f26ee70 100644
--- a/include/uapi/linux/nl80211.h
+++ b/include/uapi/linux/nl80211.h
@@ -3185,6 +3185,11 @@ enum nl80211_commands {
* The aggregated message always precedes the per-link messages for the
* same station within a dump sequence.
*
+ * @NL80211_ATTR_FRAME_CMD_NO_STA: Valid for NL80211_CMD_FRAME to denote that
+ * the kernel had no station for a received frame or should not use a
+ * known station to transmit a frame. This is relevant to know whether
+ * MLD address translation happened or to disable it when sending a frame.
+ *
* @NUM_NL80211_ATTR: total number of nl80211_attrs available
* @NL80211_ATTR_MAX: highest attribute number currently defined
* @__NL80211_ATTR_AFTER_LAST: internal use
@@ -3785,6 +3790,8 @@ enum nl80211_attrs {
NL80211_ATTR_STA_DUMP_LINK_STATS,
+ NL80211_ATTR_FRAME_CMD_NO_STA,
+
/* add attributes here, update the policy in nl80211.c */
__NL80211_ATTR_AFTER_LAST,
diff --git a/net/mac80211/agg-tx.c b/net/mac80211/agg-tx.c
index 4833b46770b62..dd8a11ef6e52b 100644
--- a/net/mac80211/agg-tx.c
+++ b/net/mac80211/agg-tx.c
@@ -98,7 +98,7 @@ static void ieee80211_send_addba_request(struct sta_info *sta, u16 tid,
if (sta->sta.deflink.he_cap.has_he)
ieee80211_add_addbaext(skb, 0, agg_size);
- ieee80211_tx_skb_tid(sdata, skb, tid, -1);
+ ieee80211_tx_skb_tid(sdata, skb, NULL, tid, -1);
}
void ieee80211_send_bar(struct ieee80211_vif *vif, u8 *ra, u16 tid, u16 ssn)
@@ -127,7 +127,7 @@ void ieee80211_send_bar(struct ieee80211_vif *vif, u8 *ra, u16 tid, u16 ssn)
IEEE80211_SKB_CB(skb)->flags |= IEEE80211_TX_INTFL_DONT_ENCRYPT |
IEEE80211_TX_CTL_REQ_TX_STATUS;
- ieee80211_tx_skb_tid(sdata, skb, tid, -1);
+ ieee80211_tx_skb_tid(sdata, skb, NULL, tid, -1);
}
EXPORT_SYMBOL(ieee80211_send_bar);
diff --git a/net/mac80211/ap.c b/net/mac80211/ap.c
index e7ac99c5a22eb..fe255891c35f0 100644
--- a/net/mac80211/ap.c
+++ b/net/mac80211/ap.c
@@ -82,9 +82,6 @@ ieee80211_rx_eml_op_mode_notif(struct ieee80211_sub_if_data *sdata,
if (!ift_ext_capa)
return;
- if (!status->link_valid)
- return;
-
sta = sta_info_get_bss(sdata, mgmt->sa);
if (!sta)
return;
diff --git a/net/mac80211/ht.c b/net/mac80211/ht.c
index e1e1b7f82f3fe..c5c2113fa4f79 100644
--- a/net/mac80211/ht.c
+++ b/net/mac80211/ht.c
@@ -545,7 +545,7 @@ int ieee80211_send_smps_action(struct ieee80211_sub_if_data *sdata,
info->status_data = IEEE80211_STATUS_TYPE_SMPS |
u16_encode_bits(status_link_id << 2 | smps,
IEEE80211_STATUS_SUBDATA_MASK);
- ieee80211_tx_skb_tid(sdata, skb, 7, link_id);
+ ieee80211_tx_skb_tid(sdata, skb, NULL, 7, link_id);
return 0;
}
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 5761e96214914..0d8eb042887a9 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -2463,7 +2463,8 @@ void ieee80211_xmit(struct ieee80211_sub_if_data *sdata,
struct sta_info *sta, struct sk_buff *skb);
void __ieee80211_tx_skb_tid_band(struct ieee80211_sub_if_data *sdata,
- struct sk_buff *skb, int tid, int link_id,
+ struct sk_buff *skb, struct sta_info *sta,
+ int tid, int link_id,
enum nl80211_band band);
static inline bool ieee80211_require_encrypted_assoc(__le16 fc,
@@ -2483,18 +2484,20 @@ ieee80211_tx_skb_tid_band(struct ieee80211_sub_if_data *sdata,
enum nl80211_band band)
{
rcu_read_lock();
- __ieee80211_tx_skb_tid_band(sdata, skb, tid, -1, band);
+ __ieee80211_tx_skb_tid_band(sdata, skb, ERR_PTR(-ENOENT),
+ tid, -1, band);
rcu_read_unlock();
}
void ieee80211_tx_skb_tid(struct ieee80211_sub_if_data *sdata,
- struct sk_buff *skb, int tid, int link_id);
+ struct sk_buff *skb, struct sta_info *sta,
+ int tid, int link_id);
static inline void ieee80211_tx_skb(struct ieee80211_sub_if_data *sdata,
struct sk_buff *skb)
{
/* Send all internal mgmt frames on VO. Accordingly set TID to 7. */
- ieee80211_tx_skb_tid(sdata, skb, 7, -1);
+ ieee80211_tx_skb_tid(sdata, skb, NULL, 7, -1);
}
/**
diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c
index 43460a705a6bd..a0b214dbb8af8 100644
--- a/net/mac80211/iface.c
+++ b/net/mac80211/iface.c
@@ -1673,11 +1673,8 @@ static void ieee80211_iface_process_skb(struct ieee80211_local *local,
break;
status = IEEE80211_SKB_RXCB(skb);
- if (!status->link_valid)
- link_sta = &sta->deflink;
- else
- link_sta = rcu_dereference_protected(sta->link[status->link_id],
- lockdep_is_held(&local->hw.wiphy->mtx));
+ link_sta = wiphy_dereference(local->hw.wiphy,
+ sta->link[status->link_id]);
if (link_sta)
ieee80211_ht_handle_chanwidth_notif(local, sdata, sta,
link_sta, chanwidth,
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index f51167f0fc46d..361775fb931ec 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -11831,12 +11831,10 @@ void ieee80211_sta_rx_queued_frame(struct ieee80211_sub_if_data *sdata,
rx_status = (struct ieee80211_rx_status *) skb->cb;
fc = le16_to_cpu(mgmt->frame_control);
- if (rx_status->link_valid) {
- link = sdata_dereference(sdata->link[rx_status->link_id],
- sdata);
- if (!link)
- return;
- }
+ link = sdata_dereference(sdata->link[rx_status->link_id],
+ sdata);
+ if (!link)
+ return;
switch (fc & IEEE80211_FCTL_STYPE) {
case IEEE80211_STYPE_BEACON:
diff --git a/net/mac80211/offchannel.c b/net/mac80211/offchannel.c
index 7acef80d5f1fb..ddd7a96a2f817 100644
--- a/net/mac80211/offchannel.c
+++ b/net/mac80211/offchannel.c
@@ -854,8 +854,10 @@ int ieee80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev,
need_offchan = true;
rcu_read_lock();
- sta = sta_info_get_bss(sdata, mgmt->da);
- mlo_sta = sta && sta->sta.mlo;
+ if (!params->no_sta) {
+ sta = sta_info_get_bss(sdata, mgmt->da);
+ mlo_sta = sta && sta->sta.mlo;
+ }
if (!ieee80211_is_action(mgmt->frame_control) ||
mgmt->u.action.category == WLAN_CATEGORY_PUBLIC ||
@@ -884,7 +886,8 @@ int ieee80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev,
local->ops->remain_on_channel &&
memcmp(sdata->vif.cfg.ap_addr, mgmt->bssid, ETH_ALEN))) {
need_offchan = true;
- } else if (sdata->u.mgd.associated &&
+ } else if (!params->no_sta &&
+ sdata->u.mgd.associated &&
ether_addr_equal(sdata->vif.cfg.ap_addr, mgmt->da)) {
sta = sta_info_get_bss(sdata, mgmt->da);
mlo_sta = sta && sta->sta.mlo;
@@ -1022,7 +1025,9 @@ int ieee80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev,
}
if (!need_offchan) {
- ieee80211_tx_skb_tid(sdata, skb, 7, link_id);
+ ieee80211_tx_skb_tid(sdata, skb,
+ params->no_sta ? ERR_PTR(-ENOENT) : sta,
+ 7, link_id);
ret = 0;
goto out_unlock;
}
diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index 5e26be8e27d8f..7896e6eee130b 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -222,43 +222,21 @@ ieee80211_rx_radiotap_hdrlen(struct ieee80211_local *local,
}
static void __ieee80211_queue_skb_to_iface(struct ieee80211_sub_if_data *sdata,
- int link_id,
- struct sta_info *sta,
+ struct link_sta_info *link_sta,
struct sk_buff *skb)
{
- struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
-
- if (link_id >= 0) {
- status->link_valid = 1;
- status->link_id = link_id;
- } else {
- status->link_valid = 0;
- }
-
skb_queue_tail(&sdata->skb_queue, skb);
wiphy_work_queue(sdata->local->hw.wiphy, &sdata->work);
- if (sta) {
- struct link_sta_info *link_sta_info;
-
- if (link_id >= 0) {
- link_sta_info = rcu_dereference(sta->link[link_id]);
- if (!link_sta_info)
- return;
- } else {
- link_sta_info = &sta->deflink;
- }
-
- link_sta_info->rx_stats.packets++;
- }
+ if (link_sta)
+ link_sta->rx_stats.packets++;
}
static void ieee80211_queue_skb_to_iface(struct ieee80211_sub_if_data *sdata,
- int link_id,
- struct sta_info *sta,
+ struct link_sta_info *link_sta,
struct sk_buff *skb)
{
skb->protocol = 0;
- __ieee80211_queue_skb_to_iface(sdata, link_id, sta, skb);
+ __ieee80211_queue_skb_to_iface(sdata, link_sta, skb);
}
static void ieee80211_handle_mu_mimo_mon(struct ieee80211_sub_if_data *sdata,
@@ -301,7 +279,7 @@ static void ieee80211_handle_mu_mimo_mon(struct ieee80211_sub_if_data *sdata,
if (!skb)
return;
- ieee80211_queue_skb_to_iface(sdata, -1, NULL, skb);
+ ieee80211_queue_skb_to_iface(sdata, NULL, skb);
}
/*
@@ -1498,7 +1476,7 @@ static void ieee80211_rx_reorder_ampdu(struct ieee80211_rx_data *rx,
/* if this mpdu is fragmented - terminate rx aggregation session */
sc = le16_to_cpu(hdr->seq_ctrl);
if (sc & IEEE80211_SCTL_FRAG) {
- ieee80211_queue_skb_to_iface(rx->sdata, rx->link_id, NULL, skb);
+ ieee80211_queue_skb_to_iface(rx->sdata, NULL, skb);
return;
}
@@ -2524,7 +2502,7 @@ ieee80211_rx_h_defragment(struct ieee80211_rx_data *rx)
out:
ieee80211_led_rx(rx->local);
- if (rx->sta)
+ if (rx->link_sta)
rx->link_sta->rx_stats.packets++;
return RX_CONTINUE;
}
@@ -3335,8 +3313,8 @@ ieee80211_rx_h_data(struct ieee80211_rx_data *rx)
(tf->action_code == WLAN_TDLS_CHANNEL_SWITCH_REQUEST ||
tf->action_code == WLAN_TDLS_CHANNEL_SWITCH_RESPONSE)) {
rx->skb->protocol = cpu_to_be16(ETH_P_TDLS);
- __ieee80211_queue_skb_to_iface(sdata, rx->link_id,
- rx->sta, rx->skb);
+ __ieee80211_queue_skb_to_iface(sdata, rx->link_sta,
+ rx->skb);
return RX_QUEUED;
}
}
@@ -3991,7 +3969,7 @@ ieee80211_rx_h_action(struct ieee80211_rx_data *rx)
return RX_QUEUED;
queue:
- ieee80211_queue_skb_to_iface(sdata, rx->link_id, rx->sta, rx->skb);
+ ieee80211_queue_skb_to_iface(sdata, rx->link_sta, rx->skb);
return RX_QUEUED;
}
@@ -4005,6 +3983,7 @@ ieee80211_rx_h_userspace_mgmt(struct ieee80211_rx_data *rx)
.len = rx->skb->len,
.link_id = rx->link_id,
.have_link_id = rx->link_id >= 0,
+ .no_sta = !rx->sta,
};
/* skip known-bad action frames and return them in the next handler */
@@ -4127,7 +4106,7 @@ ieee80211_rx_h_action_return(struct ieee80211_rx_data *rx)
local->hw.offchannel_tx_hw_queue;
}
- __ieee80211_tx_skb_tid_band(rx->sdata, nskb, 7, -1,
+ __ieee80211_tx_skb_tid_band(rx->sdata, nskb, rx->sta, 7, -1,
status->band);
}
@@ -4147,7 +4126,7 @@ ieee80211_rx_h_ext(struct ieee80211_rx_data *rx)
return RX_DROP_U_UNEXPECTED_EXT_FRAME;
/* for now only beacons are ext, so queue them */
- ieee80211_queue_skb_to_iface(sdata, rx->link_id, rx->sta, rx->skb);
+ ieee80211_queue_skb_to_iface(sdata, rx->link_sta, rx->skb);
return RX_QUEUED;
}
@@ -4204,7 +4183,7 @@ ieee80211_rx_h_mgmt(struct ieee80211_rx_data *rx)
return RX_DROP_U_UNHANDLED_MGMT_STYPE;
}
- ieee80211_queue_skb_to_iface(sdata, rx->link_id, rx->sta, rx->skb);
+ ieee80211_queue_skb_to_iface(sdata, rx->link_sta, rx->skb);
return RX_QUEUED;
}
@@ -4248,12 +4227,28 @@ static void ieee80211_rx_handlers(struct ieee80211_rx_data *rx,
spin_lock_bh(&rx->local->rx_path_lock);
while ((skb = __skb_dequeue(frames))) {
+ struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
+
/*
- * all the other fields are valid across frames
- * that belong to an aMPDU since they are on the
- * same TID from the same station
+ * Most fields are valid across frames. However,
+ * in the case of reordering frames may have arrived
+ * on different links, so adjust the link_id, link
+ * and link_sta accordingly.
*/
rx->skb = skb;
+ if (ieee80211_vif_is_mld(&rx->sdata->vif) &&
+ unlikely(rx->link_id != status->link_id)) {
+ if (rx->sta) {
+ rx->link_sta =
+ rcu_dereference(rx->sta->link[rx->link_id]);
+
+ /* Link got disabled, just use deflink */
+ if (!rx->link_sta)
+ rx->link_sta = &rx->sta->deflink;
+ }
+ rx->link_id = status->link_id;
+ rx->link = rcu_dereference(rx->sdata->link[status->link_id]);
+ }
if (WARN_ON_ONCE(!rx->link)) {
res = RX_DROP_U_NO_LINK;
@@ -4323,7 +4318,34 @@ static void ieee80211_invoke_rx_handlers(struct ieee80211_rx_data *rx)
static bool
ieee80211_rx_is_valid_sta_link_id(struct ieee80211_sta *sta, u8 link_id)
{
- return !!(sta->valid_links & BIT(link_id));
+ if (sta->mlo)
+ return sta->valid_links & BIT(link_id);
+
+ return sta->deflink.link_id == link_id;
+}
+
+static bool ieee80211_rx_data_set_link_sta(struct ieee80211_rx_data *rx,
+ struct link_sta_info *link_sta)
+{
+ struct sta_info *sta;
+
+ if (WARN_ON_ONCE(!link_sta) ||
+ !ieee80211_rx_is_valid_sta_link_id(&link_sta->sta->sta,
+ link_sta->link_id))
+ return false;
+
+ sta = link_sta->sta;
+
+ rx->sta = sta;
+ rx->local = sta->sdata->local;
+ rx->link = rcu_dereference(sta->sdata->link[link_sta->link_id]);
+ if (!rx->link)
+ return false;
+
+ rx->link_id = rx->link->link_id;
+ rx->link_sta = link_sta;
+
+ return true;
}
static bool ieee80211_rx_data_set_link(struct ieee80211_rx_data *rx,
@@ -4360,11 +4382,13 @@ static bool ieee80211_rx_data_set_sta(struct ieee80211_rx_data *rx,
if (link_id < 0) {
if (ieee80211_vif_is_mld(&rx->sdata->vif) &&
- sta && !sta->sta.valid_links)
+ sta && !sta->sta.valid_links) {
rx->link =
rcu_dereference(rx->sdata->link[sta->deflink.link_id]);
- else
+ rx->link_sta = &sta->deflink;
+ } else {
rx->link = &rx->sdata->deflink;
+ }
} else if (!ieee80211_rx_data_set_link(rx, link_id)) {
return false;
}
@@ -4387,7 +4411,7 @@ void ieee80211_release_reorder_timeout(struct sta_info *sta, int tid)
struct tid_ampdu_rx *tid_agg_rx;
int link_id = -1;
- /* FIXME: statistics won't be right with this */
+ /* NOTE: the correct link STA for the frame is set later */
if (sta->sta.valid_links)
link_id = ffs(sta->sta.valid_links) - 1;
@@ -4914,20 +4938,14 @@ static void ieee80211_rx_8023(struct ieee80211_rx_data *rx,
{
struct ieee80211_sta_rx_stats *stats;
struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(rx->skb);
- struct sta_info *sta = rx->sta;
- struct link_sta_info *link_sta;
+ struct link_sta_info *link_sta = rx->link_sta;
struct sk_buff *skb = rx->skb;
void *sa = skb->data + ETH_ALEN;
void *da = skb->data;
- if (rx->link_id >= 0) {
- link_sta = rcu_dereference(sta->link[rx->link_id]);
- if (WARN_ON_ONCE(!link_sta)) {
- dev_kfree_skb(rx->skb);
- return;
- }
- } else {
- link_sta = &sta->deflink;
+ if (WARN_ON_ONCE(!link_sta)) {
+ dev_kfree_skb(rx->skb);
+ return;
}
stats = &link_sta->rx_stats;
@@ -5154,6 +5172,9 @@ static bool ieee80211_invoke_fast_rx(struct ieee80211_rx_data *rx,
goto drop;
}
+ status = IEEE80211_SKB_RXCB(skb);
+ status->link_id = rx->link_id < 0 ? 0 : rx->link_id;
+
ieee80211_rx_8023(rx, fast_rx, orig_len);
return true;
@@ -5178,6 +5199,7 @@ static bool ieee80211_prepare_and_rx_handle(struct ieee80211_rx_data *rx,
struct ieee80211_hdr *hdr = (void *)skb->data;
struct link_sta_info *link_sta = rx->link_sta;
struct ieee80211_link_data *link = rx->link;
+ struct ieee80211_rx_status *status;
rx->skb = skb;
@@ -5221,6 +5243,9 @@ static bool ieee80211_prepare_and_rx_handle(struct ieee80211_rx_data *rx,
hdr = (struct ieee80211_hdr *)rx->skb->data;
}
+ status = IEEE80211_SKB_RXCB(rx->skb);
+ status->link_id = rx->link_id < 0 ? 0 : rx->link_id;
+
if (ieee80211_is_s1g_beacon(hdr->frame_control)) {
ieee80211_invoke_rx_handlers(rx);
return true;
@@ -5256,22 +5281,19 @@ static bool ieee80211_prepare_and_rx_handle(struct ieee80211_rx_data *rx,
}
static void __ieee80211_rx_handle_8023(struct ieee80211_hw *hw,
- struct ieee80211_sta *pubsta,
+ struct ieee80211_link_sta *link_pubsta,
struct sk_buff *skb,
struct list_head *list)
{
struct ieee80211_local *local = hw_to_local(hw);
- struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
struct ieee80211_fast_rx *fast_rx;
struct ieee80211_rx_data rx;
struct sta_info *sta;
- int link_id = -1;
memset(&rx, 0, sizeof(rx));
rx.skb = skb;
rx.local = local;
rx.list = list;
- rx.link_id = -1;
I802_DEBUG_INC(local->dot11ReceivedFragmentCount);
@@ -5279,23 +5301,15 @@ static void __ieee80211_rx_handle_8023(struct ieee80211_hw *hw,
if (skb->len < sizeof(struct ethhdr))
goto drop;
- if (!pubsta)
+ if (!link_pubsta)
goto drop;
- if (status->link_valid)
- link_id = status->link_id;
-
- /*
- * TODO: Should the frame be dropped if the right link_id is not
- * available? Or may be it is fine in the current form to proceed with
- * the frame processing because with frame being in 802.3 format,
- * link_id is used only for stats purpose and updating the stats on
- * the deflink is fine?
- */
- sta = container_of(pubsta, struct sta_info, sta);
- if (!ieee80211_rx_data_set_sta(&rx, sta, link_id))
+ sta = container_of(link_pubsta->sta, struct sta_info, sta);
+ if (!ieee80211_rx_data_set_sta(&rx, sta, link_pubsta->link_id))
goto drop;
+ rx.link_id = link_pubsta->link_id;
+
fast_rx = rcu_dereference(rx.sta->fast_rx);
if (!fast_rx)
goto drop;
@@ -5307,59 +5321,19 @@ static void __ieee80211_rx_handle_8023(struct ieee80211_hw *hw,
dev_kfree_skb(skb);
}
-static bool ieee80211_rx_for_interface(struct ieee80211_rx_data *rx,
- struct sk_buff *skb, bool consume)
+static bool ieee80211_rx_valid_freq(int freq, struct ieee80211_link_data *link)
{
- struct link_sta_info *link_sta;
- struct ieee80211_hdr *hdr = (void *)skb->data;
- struct sta_info *sta;
- int link_id = -1;
-
- if (ieee80211_is_s1g_beacon(hdr->frame_control)) {
- if (!ieee80211_rx_data_set_sta(rx, NULL, -1))
- return false;
-
- return ieee80211_prepare_and_rx_handle(rx, skb, consume);
- }
+ struct ieee80211_chanctx_conf *conf;
- /*
- * Look up link station first, in case there's a
- * chance that they might have a link address that
- * is identical to the MLD address, that way we'll
- * have the link information if needed.
- */
- link_sta = link_sta_info_get_bss(rx->sdata, hdr->addr2);
- if (link_sta) {
- sta = link_sta->sta;
- link_id = link_sta->link_id;
- } else {
- struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
-
- sta = sta_info_get_bss(rx->sdata, hdr->addr2);
- if (status->link_valid) {
- link_id = status->link_id;
- } else if (ieee80211_vif_is_mld(&rx->sdata->vif) &&
- status->freq) {
- struct ieee80211_link_data *link;
- struct ieee80211_chanctx_conf *conf;
-
- for_each_link_data_rcu(rx->sdata, link) {
- conf = rcu_dereference(link->conf->chanctx_conf);
- if (!conf || !conf->def.chan)
- continue;
-
- if (status->freq == conf->def.chan->center_freq) {
- link_id = link->link_id;
- break;
- }
- }
- }
- }
+ if (!freq || link->sdata->vif.type == NL80211_IFTYPE_NAN ||
+ link->sdata->vif.type == NL80211_IFTYPE_NAN_DATA)
+ return true;
- if (!ieee80211_rx_data_set_sta(rx, sta, link_id))
+ conf = rcu_dereference(link->conf->chanctx_conf);
+ if (!conf || !conf->def.chan)
return false;
- return ieee80211_prepare_and_rx_handle(rx, skb, consume);
+ return freq == conf->def.chan->center_freq;
}
/*
@@ -5367,18 +5341,20 @@ static bool ieee80211_rx_for_interface(struct ieee80211_rx_data *rx,
* be called with rcu_read_lock protection.
*/
static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw,
- struct ieee80211_sta *pubsta,
+ struct ieee80211_link_sta *link_pubsta,
struct sk_buff *skb,
struct list_head *list)
{
struct ieee80211_local *local = hw_to_local(hw);
- struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
struct ieee80211_sub_if_data *sdata;
+ struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
struct ieee80211_hdr *hdr;
+ struct link_sta_info *link_sta;
+ struct sta_info *sta;
__le16 fc;
struct ieee80211_rx_data rx;
- struct ieee80211_sub_if_data *prev;
struct rhlist_head *tmp;
+ bool rx_data_pending;
int err = 0;
fc = ((struct ieee80211_hdr *)skb->data)->frame_control;
@@ -5386,7 +5362,6 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw,
rx.skb = skb;
rx.local = local;
rx.list = list;
- rx.link_id = -1;
if (ieee80211_is_data(fc) || ieee80211_is_mgmt(fc))
I802_DEBUG_INC(local->dot11ReceivedFragmentCount);
@@ -5426,97 +5401,102 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw,
ieee80211_is_s1g_beacon(hdr->frame_control)))
ieee80211_scan_rx(local, skb);
+ /*
+ * RX of a data frame should only happen to existing stations.
+ * It is therefore more efficient to use the one provided by the driver
+ * or search based on the station's address.
+ *
+ * Note we will fall through and handle a spurious data frame in the
+ * same way as a management frame.
+ */
if (ieee80211_is_data(fc)) {
- struct sta_info *sta, *prev_sta;
- int link_id = -1;
-
- if (status->link_valid)
- link_id = status->link_id;
-
- if (pubsta) {
- sta = container_of(pubsta, struct sta_info, sta);
- if (!ieee80211_rx_data_set_sta(&rx, sta, link_id))
- goto out;
-
- /*
- * In MLO connection, fetch the link_id using addr2
- * when the driver does not pass link_id in status.
- * When the address translation is already performed by
- * driver/hw, the valid link_id must be passed in
- * status.
- */
-
- if (!status->link_valid && pubsta->mlo) {
- struct link_sta_info *link_sta;
-
- link_sta = link_sta_info_get_bss(rx.sdata,
- hdr->addr2);
- if (!link_sta)
- goto out;
-
- if (!ieee80211_rx_data_set_link(&rx,
- link_sta->link_id))
- goto out;
- }
-
- if (ieee80211_prepare_and_rx_handle(&rx, skb, true))
+ if (link_pubsta) {
+ sta = container_of(link_pubsta->sta, struct sta_info,
+ sta);
+ link_sta = rcu_dereference(sta->link[link_pubsta->link_id]);
+
+ rx.sdata = sta->sdata;
+ if (ieee80211_rx_data_set_link_sta(&rx, link_sta) &&
+ ieee80211_prepare_and_rx_handle(&rx, skb, true))
return;
+
goto out;
}
- prev_sta = NULL;
+ rx_data_pending = false;
+ /* Search for stations on non-MLD interfaces */
for_each_sta_info(local, hdr->addr2, sta, tmp) {
- if (!prev_sta) {
- prev_sta = sta;
- continue;
- }
+ struct ieee80211_link_data *link;
- rx.sdata = prev_sta->sdata;
- if (!status->link_valid && prev_sta->sta.mlo) {
- struct link_sta_info *link_sta;
+ if (ieee80211_vif_is_mld(&sta->sdata->vif))
+ continue;
- link_sta = link_sta_info_get_bss(rx.sdata,
- hdr->addr2);
- if (!link_sta)
- continue;
+ link = &sta->sdata->deflink;
+ if (!ieee80211_rx_valid_freq(status->freq, link))
+ continue;
- link_id = link_sta->link_id;
+ if (rx_data_pending) {
+ ieee80211_prepare_and_rx_handle(&rx, skb,
+ false);
+ rx_data_pending = false;
}
- if (!ieee80211_rx_data_set_sta(&rx, prev_sta, link_id))
- goto out;
-
- ieee80211_prepare_and_rx_handle(&rx, skb, false);
+ rx.sdata = sta->sdata;
+ if (!ieee80211_rx_data_set_link_sta(&rx, &sta->deflink))
+ continue;
- prev_sta = sta;
+ rx_data_pending = true;
}
- if (prev_sta) {
- rx.sdata = prev_sta->sdata;
- if (!status->link_valid && prev_sta->sta.mlo) {
- struct link_sta_info *link_sta;
+ /* And search stations on MLD interfaces */
+ for_each_link_sta_info(local, hdr->addr2, link_sta, tmp) {
+ struct ieee80211_link_data *link;
- link_sta = link_sta_info_get_bss(rx.sdata,
- hdr->addr2);
- if (!link_sta)
- goto out;
+ sta = link_sta->sta;
+ sdata = sta->sdata;
+ link = rcu_dereference(sdata->link[link_sta->link_id]);
- link_id = link_sta->link_id;
+ if (!ieee80211_rx_valid_freq(status->freq, link))
+ continue;
+
+ if (rx_data_pending) {
+ ieee80211_prepare_and_rx_handle(&rx, skb,
+ false);
+ rx_data_pending = false;
}
- if (!ieee80211_rx_data_set_sta(&rx, prev_sta, link_id))
- goto out;
+ rx.sdata = sta->sdata;
+ if (!ieee80211_rx_data_set_link_sta(&rx, link_sta))
+ continue;
+
+ rx_data_pending = true;
+ }
+ if (rx_data_pending) {
if (ieee80211_prepare_and_rx_handle(&rx, skb, true))
return;
+
goto out;
}
+
+ /* fall through, e.g. for spurious frame notification */
}
- prev = NULL;
+ /*
+ * This is a management frame (or a data frame without a station) and
+ * it will be delivered independent of whether a station exists,
+ * so iterate the interfaces.
+ */
+ rx_data_pending = false;
+
+ /* We expect the driver to provide frequency information */
+ if (WARN_ON_ONCE(!local->emulate_chanctx && !status->freq))
+ goto out;
list_for_each_entry_rcu(sdata, &local->interfaces, list) {
+ struct ieee80211_link_data *link = NULL;
+
if (!ieee80211_sdata_running(sdata))
continue;
@@ -5524,30 +5504,85 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw,
sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
continue;
+ link_sta = NULL;
+ link = NULL;
+
+ /* Try to resolve the station */
+ if (!ieee80211_vif_is_mld(&sdata->vif)) {
+ sta = sta_info_get_bss(sdata, hdr->addr2);
+
+ if (sta) {
+ link_sta = &sta->deflink;
+ link = &sdata->deflink;
+ }
+ } else {
+ link_sta = link_sta_info_get_bss(sdata, hdr->addr2);
+
+ if (link_sta)
+ link = rcu_dereference(sdata->link[link_sta->link_id]);
+ }
+
/*
- * frame is destined for this interface, but if it's
- * not also for the previous one we handle that after
- * the loop to avoid copying the SKB once too much
+ * If we found a STA and it has a valid link information, then
+ * RX using the station.
*/
+ if (link_sta && link &&
+ ieee80211_rx_valid_freq(status->freq, link)) {
+ if (rx_data_pending) {
+ ieee80211_prepare_and_rx_handle(&rx, skb, false);
+ rx_data_pending = false;
+ }
+
+ /* No valid_links check as we need to RX beacons */
+
+ rx.sdata = sdata;
+ if (ieee80211_rx_data_set_link_sta(&rx, link_sta))
+ rx_data_pending = true;
- if (!prev) {
- prev = sdata;
continue;
}
- rx.sdata = prev;
- ieee80211_rx_for_interface(&rx, skb, false);
+ /* No station, try to resolve the link and RX */
+ if (ieee80211_vif_is_mld(&sdata->vif)) {
+ struct ieee80211_chanctx_conf *conf;
+ bool found = false;
- prev = sdata;
- }
+ for_each_link_data_rcu(sdata, link) {
+ conf = rcu_dereference(link->conf->chanctx_conf);
+ if (!conf || !conf->def.chan)
+ continue;
- if (prev) {
- rx.sdata = prev;
+ if (status->freq == conf->def.chan->center_freq) {
+ found = true;
+ break;
+ }
+ }
- if (ieee80211_rx_for_interface(&rx, skb, true))
- return;
+ if (!found)
+ link = &sdata->deflink;
+ } else {
+ link = &sdata->deflink;
+ }
+
+ if (rx_data_pending) {
+ ieee80211_prepare_and_rx_handle(&rx, skb, false);
+ rx_data_pending = false;
+ }
+
+ rx.sdata = sdata;
+ rx.local = sdata->local;
+ rx.link = link;
+ rx.link_id = link->link_id;
+ rx.sta = NULL;
+ rx.link_sta = NULL;
+
+ rx_data_pending = true;
}
+ if (rx_data_pending &&
+ ieee80211_prepare_and_rx_handle(&rx, skb, true))
+ return;
+
out:
dev_kfree_skb(skb);
}
@@ -5556,7 +5591,8 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw,
* This is the receive path handler. It is called by a low level driver when an
* 802.11 MPDU is received from the hardware.
*/
-void ieee80211_rx_list(struct ieee80211_hw *hw, struct ieee80211_sta *pubsta,
+void ieee80211_rx_list(struct ieee80211_hw *hw,
+ struct ieee80211_link_sta *link_pubsta,
struct sk_buff *skb, struct list_head *list)
{
struct ieee80211_local *local = hw_to_local(hw);
@@ -5694,9 +5730,6 @@ void ieee80211_rx_list(struct ieee80211_hw *hw, struct ieee80211_sta *pubsta,
}
}
- if (WARN_ON_ONCE(status->link_id >= IEEE80211_LINK_UNSPECIFIED))
- goto drop;
-
status->rx_flags = 0;
kcov_remote_start_common(skb_get_kcov_handle(skb));
@@ -5715,9 +5748,10 @@ void ieee80211_rx_list(struct ieee80211_hw *hw, struct ieee80211_sta *pubsta,
ieee80211_tpt_led_trig_rx(local, skb->len);
if (status->flag & RX_FLAG_8023)
- __ieee80211_rx_handle_8023(hw, pubsta, skb, list);
+ __ieee80211_rx_handle_8023(hw, link_pubsta, skb, list);
else
- __ieee80211_rx_handle_packet(hw, pubsta, skb, list);
+ __ieee80211_rx_handle_packet(hw, link_pubsta, skb,
+ list);
}
kcov_remote_stop();
@@ -5727,7 +5761,8 @@ void ieee80211_rx_list(struct ieee80211_hw *hw, struct ieee80211_sta *pubsta,
}
EXPORT_SYMBOL(ieee80211_rx_list);
-void ieee80211_rx_napi(struct ieee80211_hw *hw, struct ieee80211_sta *pubsta,
+void ieee80211_rx_napi(struct ieee80211_hw *hw,
+ struct ieee80211_link_sta *link_pubsta,
struct sk_buff *skb, struct napi_struct *napi)
{
struct sk_buff *tmp;
@@ -5740,7 +5775,7 @@ void ieee80211_rx_napi(struct ieee80211_hw *hw, struct ieee80211_sta *pubsta,
* receive processing
*/
rcu_read_lock();
- ieee80211_rx_list(hw, pubsta, skb, &list);
+ ieee80211_rx_list(hw, link_pubsta, skb, &list);
rcu_read_unlock();
if (!napi) {
diff --git a/net/mac80211/scan.c b/net/mac80211/scan.c
index eeff230bd909f..324c2b6d7ce1b 100644
--- a/net/mac80211/scan.c
+++ b/net/mac80211/scan.c
@@ -9,7 +9,7 @@
* Copyright 2007, Michael Wu <flamingice@sourmilk.net>
* Copyright 2013-2015 Intel Mobile Communications GmbH
* Copyright 2016-2017 Intel Deutschland GmbH
- * Copyright (C) 2018-2025 Intel Corporation
+ * Copyright (C) 2018-2026 Intel Corporation
*/
#include <linux/if_arp.h>
@@ -204,12 +204,10 @@ ieee80211_bss_info_update(struct ieee80211_local *local,
* an indication on which of the links the frame was received
*/
if (ieee80211_vif_is_mld(&scan_sdata->vif)) {
- if (rx_status->link_valid) {
- s8 link_id = rx_status->link_id;
+ s8 link_id = rx_status->link_id;
- link_conf =
- rcu_dereference(scan_sdata->vif.link_conf[link_id]);
- }
+ link_conf =
+ rcu_dereference(scan_sdata->vif.link_conf[link_id]);
} else {
link_conf = &scan_sdata->vif.bss_conf;
}
diff --git a/net/mac80211/tdls.c b/net/mac80211/tdls.c
index dc2f662fe4c46..e17d492be5902 100644
--- a/net/mac80211/tdls.c
+++ b/net/mac80211/tdls.c
@@ -1066,7 +1066,7 @@ ieee80211_tdls_prep_mgmt_packet(struct wiphy *wiphy, struct net_device *dev,
}
if (action_code == WLAN_PUB_ACTION_TDLS_DISCOVER_RES) {
- ieee80211_tx_skb_tid(sdata, skb, 7, link_id);
+ ieee80211_tx_skb_tid(sdata, skb, sta, 7, link_id);
return 0;
}
diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c
index 3a1e2c9e1565e..6f52e88402371 100644
--- a/net/mac80211/tx.c
+++ b/net/mac80211/tx.c
@@ -6436,7 +6436,8 @@ void ieee80211_unreserve_tid(struct ieee80211_sta *pubsta, u8 tid)
EXPORT_SYMBOL(ieee80211_unreserve_tid);
void __ieee80211_tx_skb_tid_band(struct ieee80211_sub_if_data *sdata,
- struct sk_buff *skb, int tid, int link_id,
+ struct sk_buff *skb, struct sta_info *sta,
+ int tid, int link_id,
enum nl80211_band band)
{
const struct ieee80211_hdr *hdr = (void *)skb->data;
@@ -6488,12 +6489,13 @@ void __ieee80211_tx_skb_tid_band(struct ieee80211_sub_if_data *sdata,
*/
local_bh_disable();
IEEE80211_SKB_CB(skb)->band = band;
- ieee80211_xmit(sdata, NULL, skb);
+ ieee80211_xmit(sdata, sta, skb);
local_bh_enable();
}
void ieee80211_tx_skb_tid(struct ieee80211_sub_if_data *sdata,
- struct sk_buff *skb, int tid, int link_id)
+ struct sk_buff *skb, struct sta_info *sta,
+ int tid, int link_id)
{
struct ieee80211_chanctx_conf *chanctx_conf;
enum nl80211_band band;
@@ -6519,7 +6521,7 @@ void ieee80211_tx_skb_tid(struct ieee80211_sub_if_data *sdata,
band = 0;
}
- __ieee80211_tx_skb_tid_band(sdata, skb, tid, link_id, band);
+ __ieee80211_tx_skb_tid_band(sdata, skb, sta, tid, link_id, band);
rcu_read_unlock();
}
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index 44f2bad086705..437bebcc02748 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -1096,6 +1096,7 @@ static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = {
[NL80211_ATTR_NPCA_PUNCT_BITMAP] =
NLA_POLICY_FULL_RANGE(NLA_U32, &nl80211_punct_bitmap_range),
[NL80211_ATTR_STA_DUMP_LINK_STATS] = { .type = NLA_FLAG },
+ [NL80211_ATTR_FRAME_CMD_NO_STA] = { .type = NLA_FLAG },
};
/* policy for the key attributes */
@@ -14860,6 +14861,9 @@ static int nl80211_tx_mgmt(struct sk_buff *skb, struct genl_info *info)
!(wdev->valid_links & BIT(params.link_id)))
return -EINVAL;
+ params.no_sta =
+ nla_get_flag(info->attrs[NL80211_ATTR_FRAME_CMD_NO_STA]);
+
params.buf = nla_data(info->attrs[NL80211_ATTR_FRAME]);
params.len = nla_len(info->attrs[NL80211_ATTR_FRAME]);
@@ -22024,7 +22028,9 @@ int nl80211_send_mgmt(struct cfg80211_registered_device *rdev,
(info->ack_tstamp && nla_put_u64_64bit(msg,
NL80211_ATTR_TX_HW_TIMESTAMP,
info->ack_tstamp,
- NL80211_ATTR_PAD)))
+ NL80211_ATTR_PAD)) ||
+ (info->no_sta &&
+ nla_put_flag(msg, NL80211_ATTR_FRAME_CMD_NO_STA)))
goto nla_put_failure;
genlmsg_end(msg, hdr);
|
| 7/3 | 2026/08/24 20:41 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 33119 output: 360 thoughts: 3024 Thoughts: **Analyzing the Link ID and Validity Changes in mac80211** Okay, so this patch is all about tidying up how `link_id` and `link_valid` are handled in mac80211 and some drivers. My main concern, as always, is potential uninitialized memory reads or leaks. So, let's break this down. First, removing `link_valid` from `ieee80211_rx_status` and using `link_sta` seems reasonable. We're moving away from bitfields and toward explicit passing of link information, which is generally cleaner. The changes to the `ieee80211_rx_list` and `ieee80211_rx_napi` functions taking `link_sta` instead of `sta` directly support this. The addition of `no_sta` to `cfg80211_mgmt_tx_params` and `cfg80211_rx_info`, along with the `NL80211_ATTR_FRAME_CMD_NO_STA` attribute, looks like a key change. However, since it's a flag attribute, it's just a presence check (using `nla_get_flag`), so there's no data being leaked. That's a relief. We are using `nla_put_flag` which puts an empty attribute if `no_sta` is true, so no uninitialized memory. Now, the `ieee80211_rx_status` change is internal; as long as the size doesn't change, we're okay. And, because this structure is only used inside the kernel in `skb->cb`, we do not leak uninitialized memory to userspace. The `cfg80211_rx_info` and `cfg80211_mgmt_tx_params` changes seem safe, as `no_sta` is explicitly initialized using `nla_get_flag`. Here's the critical bit: We're removing the `link_valid` field and relying on `link_sta` being passed down. Now, the `ath12k` driver is one of the places the original set `status->link_id`, but now uses `ath12k_hw_get_rx_link_id`. My question is, is `status->link_id` going to be uninitialized now? I see now. Inside `ath12k_dp_rx_deliver_msdu`, `status` is passed to `ieee80211_rx_napi`, but `status->link_id` is now set to whatever is returned by `ath12k_hw_get_rx_link_id`. And since the caller to `ieee80211_rx_napi` passes the `link_pubsta` directly to `__ieee80211_rx_handle_8023` and `__ieee80211_rx_handle_packet`, there should be no read from an uninitialized `status->link_id`, as both use `link_pubsta->link_id`. Ok wait, `link_pubsta` can be NULL! If we are searching for stations on non-MLD interfaces, we are not using the `link_pubsta` struct. We are using `status->freq`. In `ieee80211_rx_handlers`, in cases with MLD, we do read `status->link_id`. But `ieee80211_prepare_and_rx_handle` initializes it, by calling the `ieee80211_rx_work` and `ieee80211_queue_skb_to_iface` functions. Finally, in the `cfg80211_rx_info`, all the fields are initialized, including `no_sta`. So, in short, I'm confident that there are no uninitialized memory exposures to user-space. The patch is a pretty straightforward refactoring, simplifying the passing of link information using the link pointer instead of bitfields and, to my expert eye, the risk is minimal. While logic bugs are still a possibility (e.g., potential NULL pointer dereferences if `link_sta` is accidentally NULL or the incorrect `link_id`), those are the kinds of issues that KASAN should be able to catch quite effectively. I don't see any compelling need for KMSAN here. |
| 8/3 | 2026/08/24 20:42 | tool | set-results |
0mArgs:{
"KMSANReasoning": "The patch refactors how `link_id` and `link_valid` are passed from wireless drivers to mac80211. Instead of setting `link_valid` and `link_id` in `struct ieee80211_rx_status` (which is stored in `skb-\u003ecb`), drivers now resolve the `link_sta` and pass it directly to `ieee80211_rx_napi()` and `ieee80211_rx_list()`. The patch also adds a `no_sta` boolean to `struct cfg80211_mgmt_tx_params` and `struct cfg80211_rx_info`, which is exposed to userspace via a new netlink flag attribute `NL80211_ATTR_FRAME_CMD_NO_STA`.\n\nSince `NL80211_ATTR_FRAME_CMD_NO_STA` is an `NLA_FLAG`, it carries no payload and does not leak any uninitialized memory to userspace. The changes to `struct ieee80211_rx_status` only affect internal kernel bitfields and do not expose uninitialized memory. Any potential bugs introduced by this patch would be logic errors, NULL pointer dereferences, or out-of-bounds accesses (e.g., incorrect link ID indexing), which are well within the capabilities of standard KASAN and other bug detectors. There is no risk of uninitialized memory usage or info-leaks that would require a dedicated KMSAN fuzzing session.",
"NeedsKMSAN": false
}Results: {
"KMSANReasoning": "The patch refactors how `link_id` and `link_valid` are passed from wireless drivers to mac80211. Instead of setting `link_valid` and `link_id` in `struct ieee80211_rx_status` (which is stored in `skb-\u003ecb`), drivers now resolve the `link_sta` and pass it directly to `ieee80211_rx_napi()` and `ieee80211_rx_list()`. The patch also adds a `no_sta` boolean to `struct cfg80211_mgmt_tx_params` and `struct cfg80211_rx_info`, which is exposed to userspace via a new netlink flag attribute `NL80211_ATTR_FRAME_CMD_NO_STA`.\n\nSince `NL80211_ATTR_FRAME_CMD_NO_STA` is an `NLA_FLAG`, it carries no payload and does not leak any uninitialized memory to userspace. The changes to `struct ieee80211_rx_status` only affect internal kernel bitfields and do not expose uninitialized memory. Any potential bugs introduced by this patch would be logic errors, NULL pointer dereferences, or out-of-bounds accesses (e.g., incorrect link ID indexing), which are well within the capabilities of standard KASAN and other bug detectors. There is no risk of uninitialized memory usage or info-leaks that would require a dedicated KMSAN fuzzing session.",
"NeedsKMSAN": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|