From df6165d2566454ddd0a84df6996598fee4a9b59a Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Mon, 13 Jul 2026 15:33:16 +0800 Subject: [PATCH 1/8] FROMLIST: wifi: ath12k: fix out-of-bounds clear_bit in ath12k_mac_dp_peer_cleanup() ath12k_mac_dp_peer_cleanup() clears the ML peer ID slot on the free_ml_peer_id_map bitmap by indexing it with dp_peer->peer_id. That is wrong: dp_peer->peer_id for an MLO peer always carries the ATH12K_PEER_ML_ID_VALID bit (BIT(13)), so clear_bit() is invoked with index >= 0x2000, which is far outside the bitmap of ATH12K_MAX_MLO_PEERS (256) bits and corrupts memory adjacent to ah->free_ml_peer_id_map. The intended bitmap entry also never gets cleared, so subsequent ath12k_peer_ml_alloc() calls eventually run out of IDs. The ID without the VALID bit is what ath12k_peer_ml_alloc() returned and is stored in ahsta->ml_peer_id. Use that instead. While there, also reset ahsta->ml_peer_id to ATH12K_MLO_PEER_ID_INVALID so the bitmap and ahsta->ml_peer_id stay in sync; Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Fixes: ee16dcf573d5 ("wifi: ath12k: Define ath12k_dp_peer structure & APIs for create & delete") Link: https://lore.kernel.org/linux-wireless/20260713-ath12k-fw-allocated-ml-peer-id-v1-1-d0a2a1a519eb@oss.qualcomm.com/ Signed-off-by: Baochen Qiang Signed-off-by: Gaole Zhang --- drivers/net/wireless/ath/ath12k/mac.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 6a3a9be2e6600..4eed5289cc5fa 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -1276,8 +1276,11 @@ void ath12k_mac_dp_peer_cleanup(struct ath12k_hw *ah) spin_lock_bh(&dp_hw->peer_lock); list_for_each_entry_safe(dp_peer, tmp, &dp_hw->dp_peers_list, list) { if (dp_peer->is_mlo) { + struct ath12k_sta *ahsta = ath12k_sta_to_ahsta(dp_peer->sta); + rcu_assign_pointer(dp_hw->dp_peers[dp_peer->peer_id], NULL); - clear_bit(dp_peer->peer_id, ah->free_ml_peer_id_map); + clear_bit(ahsta->ml_peer_id, ah->free_ml_peer_id_map); + ahsta->ml_peer_id = ATH12K_MLO_PEER_ID_INVALID; } list_move(&dp_peer->list, &peers); From 7d9a7f6de40e83c5cece360e4b69295733a98f77 Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Mon, 13 Jul 2026 15:33:17 +0800 Subject: [PATCH 2/8] FROMLIST: wifi: ath12k: factor out peer assoc send-and-wait into a helper ath12k_bss_assoc(), ath12k_mac_station_assoc() and ath12k_sta_rc_update_wk() all open-code the same sequence: reinit the peer_assoc_done completion, send the peer assoc WMI command, then wait for the firmware confirmation event. The reinit_completion() was buried in ath12k_peer_assoc_prepare(), far from the wait_for_completion_timeout() that consumes it, making the reinit/send/wait sequence hard to follow, and the three open-coded copies are easy to get out of sync. Move the sequence into a new helper ath12k_mac_peer_assoc() and call it from all three sites. The reinit, send and wait now live together so the completion's lifecycle is easy to read. While at it, ath12k_sta_rc_update_wk() previously warned but still waited the full timeout when the peer assoc command failed to send. Now a send failure returns immediately and skips the pointless 1 second wait, matching the other two callers. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Link: https://lore.kernel.org/linux-wireless/20260713-ath12k-fw-allocated-ml-peer-id-v1-2-d0a2a1a519eb@oss.qualcomm.com/ Signed-off-by: Baochen Qiang Signed-off-by: Gaole Zhang --- drivers/net/wireless/ath/ath12k/mac.c | 59 +++++++++++++-------------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 4eed5289cc5fa..67dbbae29ebe2 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -3583,8 +3583,6 @@ static void ath12k_peer_assoc_prepare(struct ath12k *ar, memset(arg, 0, sizeof(*arg)); - reinit_completion(&ar->peer_assoc_done); - arg->peer_new_assoc = !reassoc; ath12k_peer_assoc_h_basic(ar, arvif, arsta, arg); ath12k_peer_assoc_h_crypto(ar, arvif, arsta, arg); @@ -3824,6 +3822,29 @@ static u32 ath12k_mac_ieee80211_sta_bw_to_wmi(struct ath12k *ar, return bw; } +static int ath12k_mac_peer_assoc(struct ath12k *ar, + struct ath12k_wmi_peer_assoc_arg *peer_arg) +{ + int ret; + + reinit_completion(&ar->peer_assoc_done); + + ret = ath12k_wmi_send_peer_assoc_cmd(ar, peer_arg); + if (ret) { + ath12k_warn(ar->ab, "failed to run peer assoc for %pM vdev %i: %d\n", + peer_arg->peer_mac, peer_arg->vdev_id, ret); + return ret; + } + + if (!wait_for_completion_timeout(&ar->peer_assoc_done, 1 * HZ)) { + ath12k_warn(ar->ab, "failed to get peer assoc conf event for %pM vdev %i\n", + peer_arg->peer_mac, peer_arg->vdev_id); + return -ETIMEDOUT; + } + + return 0; +} + static void ath12k_bss_assoc(struct ath12k *ar, struct ath12k_link_vif *arvif, struct ieee80211_bss_conf *bss_conf) @@ -3904,18 +3925,10 @@ static void ath12k_bss_assoc(struct ath12k *ar, } peer_arg->is_assoc = true; - ret = ath12k_wmi_send_peer_assoc_cmd(ar, peer_arg); - if (ret) { - ath12k_warn(ar->ab, "failed to run peer assoc for %pM vdev %i: %d\n", - bss_conf->bssid, arvif->vdev_id, ret); - return; - } - if (!wait_for_completion_timeout(&ar->peer_assoc_done, 1 * HZ)) { - ath12k_warn(ar->ab, "failed to get peer assoc conf event for %pM vdev %i\n", - bss_conf->bssid, arvif->vdev_id); + ret = ath12k_mac_peer_assoc(ar, peer_arg); + if (ret) return; - } ret = ath12k_setup_peer_smps(ar, arvif, bss_conf->bssid, &link_sta->ht_cap, &link_sta->he_6ghz_capa); @@ -6476,18 +6489,10 @@ static int ath12k_mac_station_assoc(struct ath12k *ar, } peer_arg->is_assoc = true; - ret = ath12k_wmi_send_peer_assoc_cmd(ar, peer_arg); - if (ret) { - ath12k_warn(ar->ab, "failed to run peer assoc for STA %pM vdev %i: %d\n", - arsta->addr, arvif->vdev_id, ret); - return ret; - } - if (!wait_for_completion_timeout(&ar->peer_assoc_done, 1 * HZ)) { - ath12k_warn(ar->ab, "failed to get peer assoc conf event for %pM vdev %i\n", - arsta->addr, arvif->vdev_id); - return -ETIMEDOUT; - } + ret = ath12k_mac_peer_assoc(ar, peer_arg); + if (ret) + return ret; num_vht_rates = ath12k_mac_bitrate_mask_num_vht_rates(ar, band, mask); num_he_rates = ath12k_mac_bitrate_mask_num_he_rates(ar, band, mask); @@ -6763,14 +6768,8 @@ static void ath12k_sta_rc_update_wk(struct wiphy *wiphy, struct wiphy_work *wk) peer_arg, true); peer_arg->is_assoc = false; - err = ath12k_wmi_send_peer_assoc_cmd(ar, peer_arg); - if (err) - ath12k_warn(ar->ab, "failed to run peer assoc for STA %pM vdev %i: %d\n", - arsta->addr, arvif->vdev_id, err); - if (!wait_for_completion_timeout(&ar->peer_assoc_done, 1 * HZ)) - ath12k_warn(ar->ab, "failed to get peer assoc conf event for %pM vdev %i\n", - arsta->addr, arvif->vdev_id); + ath12k_mac_peer_assoc(ar, peer_arg); } } } From d0ee0a378a351407c0d5a407c6e97750179c5733 Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Mon, 13 Jul 2026 15:33:18 +0800 Subject: [PATCH 3/8] FROMLIST: wifi: ath12k: keep ATH12K_PEER_ML_ID_VALID set in ath12k_sta::ml_peer_id Several pieces of host bookkeeping for MLD peer IDs encode the same fact in different ways: - ath12k_sta::ml_peer_id stores the raw ID in [0, ATH12K_MAX_MLO_PEERS); - ath12k_dp_peer::peer_id, ath12k_dp_link_peer::ml_id and the index used on ath12k_dp_hw::dp_peers[] always carry the ATH12K_PEER_ML_ID_VALID bit (BIT(13)) when the ID is real; - WMI_MLO_PEER_ASSOC_PARAMS::ml_peer_id sent down to firmware is raw, without the bookkeeping bit. The mismatch leaks into call sites that have to remember to OR the bit in (ath12k_peer_create(), ath12k_mac_op_sta_state()) or remember not to (ath12k_peer_assoc_h_mlo()). Make ath12k_sta::ml_peer_id carry the VALID bit when valid, the same way ath12k_dp_peer::peer_id and ath12k_dp_link_peer::ml_id do: - ath12k_peer_ml_alloc() OR-s the bit in once on the way out; the internal bitmap stays raw [0, ATH12K_MAX_MLO_PEERS); - ath12k_peer_create() and ath12k_mac_op_sta_state() drop the explicit OR; - ath12k_peer_assoc_h_mlo() masks the bit off when populating the WMI ml_peer_id; While there, introduce ath12k_peer_ml_free() to mirror ath12k_peer_ml_alloc(), which helps avoid code duplication. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Link: https://lore.kernel.org/linux-wireless/20260713-ath12k-fw-allocated-ml-peer-id-v1-3-d0a2a1a519eb@oss.qualcomm.com/ Signed-off-by: Baochen Qiang Signed-off-by: Gaole Zhang --- drivers/net/wireless/ath/ath12k/mac.c | 27 +++++++++++++------------- drivers/net/wireless/ath/ath12k/peer.c | 17 +++++++++++++--- drivers/net/wireless/ath/ath12k/peer.h | 1 + 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 67dbbae29ebe2..329c4011143db 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -1271,16 +1271,15 @@ void ath12k_mac_dp_peer_cleanup(struct ath12k_hw *ah) struct ath12k_dp_peer *dp_peer, *tmp; struct ath12k_dp_hw *dp_hw = &ah->dp_hw; + lockdep_assert_wiphy(ah->hw->wiphy); + INIT_LIST_HEAD(&peers); spin_lock_bh(&dp_hw->peer_lock); list_for_each_entry_safe(dp_peer, tmp, &dp_hw->dp_peers_list, list) { if (dp_peer->is_mlo) { - struct ath12k_sta *ahsta = ath12k_sta_to_ahsta(dp_peer->sta); - rcu_assign_pointer(dp_hw->dp_peers[dp_peer->peer_id], NULL); - clear_bit(ahsta->ml_peer_id, ah->free_ml_peer_id_map); - ahsta->ml_peer_id = ATH12K_MLO_PEER_ID_INVALID; + ath12k_peer_ml_free(ah, ath12k_sta_to_ahsta(dp_peer->sta)); } list_move(&dp_peer->list, &peers); @@ -3536,7 +3535,11 @@ static void ath12k_peer_assoc_h_mlo(struct ath12k_link_sta *arsta, ether_addr_copy(ml->mld_addr, sta->addr); ml->logical_link_idx = arsta->link_idx; - ml->ml_peer_id = ahsta->ml_peer_id; + /* + * WMI_MLO_PEER_ASSOC_PARAMS expects the raw ML peer ID without + * the host-side ATH12K_PEER_ML_ID_VALID bookkeeping bit. + */ + ml->ml_peer_id = ahsta->ml_peer_id & ~ATH12K_PEER_ML_ID_VALID; ml->ieee_link_id = arsta->link_id; ml->num_partner_links = 0; ml->eml_cap = sta->eml_cap; @@ -7176,10 +7179,8 @@ static void ath12k_mac_ml_station_remove(struct ath12k_vif *ahvif, ath12k_mac_free_unassign_link_sta(ah, ahsta, link_id); } - if (sta->mlo) { - clear_bit(ahsta->ml_peer_id, ah->free_ml_peer_id_map); - ahsta->ml_peer_id = ATH12K_MLO_PEER_ID_INVALID; - } + if (sta->mlo) + ath12k_peer_ml_free(ah, ahsta); } static int ath12k_mac_handle_link_sta_state(struct ieee80211_hw *hw, @@ -7650,7 +7651,7 @@ int ath12k_mac_op_sta_state(struct ieee80211_hw *hw, } dp_params.is_mlo = true; - dp_params.peer_id = ahsta->ml_peer_id | ATH12K_PEER_ML_ID_VALID; + dp_params.peer_id = ahsta->ml_peer_id; } dp_params.sta = sta; @@ -7787,10 +7788,8 @@ int ath12k_mac_op_sta_state(struct ieee80211_hw *hw, peer_delete: ath12k_dp_peer_delete(&ah->dp_hw, sta->addr, sta); ml_peer_id_clear: - if (sta->mlo) { - clear_bit(ahsta->ml_peer_id, ah->free_ml_peer_id_map); - ahsta->ml_peer_id = ATH12K_MLO_PEER_ID_INVALID; - } + if (sta->mlo) + ath12k_peer_ml_free(ah, ahsta); exit: /* update the state if everything went well */ if (!ret) diff --git a/drivers/net/wireless/ath/ath12k/peer.c b/drivers/net/wireless/ath/ath12k/peer.c index 5f3bd3b9a3e97..517416f7cd7d1 100644 --- a/drivers/net/wireless/ath/ath12k/peer.c +++ b/drivers/net/wireless/ath/ath12k/peer.c @@ -224,7 +224,7 @@ int ath12k_peer_create(struct ath12k *ar, struct ath12k_link_vif *arvif, /* Fill ML info into created peer */ if (sta->mlo) { ml_peer_id = ahsta->ml_peer_id; - peer->ml_id = ml_peer_id | ATH12K_PEER_ML_ID_VALID; + peer->ml_id = ml_peer_id; ether_addr_copy(peer->ml_addr, sta->addr); /* the assoc link is considered primary for now */ @@ -267,9 +267,20 @@ u16 ath12k_peer_ml_alloc(struct ath12k_hw *ah) } if (ml_peer_id == ATH12K_MAX_MLO_PEERS) - ml_peer_id = ATH12K_MLO_PEER_ID_INVALID; + return ATH12K_MLO_PEER_ID_INVALID; - return ml_peer_id; + return ml_peer_id | ATH12K_PEER_ML_ID_VALID; +} + +void ath12k_peer_ml_free(struct ath12k_hw *ah, struct ath12k_sta *ahsta) +{ + lockdep_assert_wiphy(ah->hw->wiphy); + + if (ahsta->ml_peer_id < + (ATH12K_MAX_MLO_PEERS | ATH12K_PEER_ML_ID_VALID)) + clear_bit(ahsta->ml_peer_id & ~ATH12K_PEER_ML_ID_VALID, + ah->free_ml_peer_id_map); + ahsta->ml_peer_id = ATH12K_MLO_PEER_ID_INVALID; } int ath12k_peer_mlo_link_peers_delete(struct ath12k_vif *ahvif, struct ath12k_sta *ahsta) diff --git a/drivers/net/wireless/ath/ath12k/peer.h b/drivers/net/wireless/ath/ath12k/peer.h index 49d89796bc46e..0f7f25b8e89c0 100644 --- a/drivers/net/wireless/ath/ath12k/peer.h +++ b/drivers/net/wireless/ath/ath12k/peer.h @@ -26,4 +26,5 @@ int ath12k_link_sta_rhash_add(struct ath12k_base *ab, struct ath12k_link_sta *ar struct ath12k_link_sta *ath12k_link_sta_find_by_addr(struct ath12k_base *ab, const u8 *addr); u16 ath12k_peer_ml_alloc(struct ath12k_hw *ah); +void ath12k_peer_ml_free(struct ath12k_hw *ah, struct ath12k_sta *ahsta); #endif /* _PEER_H_ */ From 6d41fe5355b184b40c0888e4fb12d893c02dc14e Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Mon, 13 Jul 2026 15:33:19 +0800 Subject: [PATCH 4/8] FROMLIST: wifi: ath12k: add support for HTT_T2H_MSG_TYPE_MLO_RX_PEER_MAP Firmware on chips that allocate the MLD peer ID itself (WCN7850 and QCC2072) reports the assignment back to the host through HTT_T2H_MSG_TYPE_MLO_RX_PEER_MAP. The message carries the chosen MLD peer id, the MLD MAC address etc. Add the message type, the on-the-wire struct, the field masks and a handler that parses them out. The host-side state update (publishing the dp peer into ath12k_dp_hw::dp_peers[], propagating the ID to ath12k_dp_link_peer::ml_id and ath12k_sta::ml_peer_id) is added in a follow-up patch; Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Link: https://lore.kernel.org/linux-wireless/20260713-ath12k-fw-allocated-ml-peer-id-v1-4-d0a2a1a519eb@oss.qualcomm.com/ Signed-off-by: Baochen Qiang Signed-off-by: Gaole Zhang --- drivers/net/wireless/ath/ath12k/dp_htt.c | 30 ++++++++++++++++++++++++ drivers/net/wireless/ath/ath12k/dp_htt.h | 12 ++++++++++ 2 files changed, 42 insertions(+) diff --git a/drivers/net/wireless/ath/ath12k/dp_htt.c b/drivers/net/wireless/ath/ath12k/dp_htt.c index cc71c5c5de5a1..982d3eec4de4e 100644 --- a/drivers/net/wireless/ath/ath12k/dp_htt.c +++ b/drivers/net/wireless/ath/ath12k/dp_htt.c @@ -573,6 +573,33 @@ static void ath12k_htt_mlo_offset_event_handler(struct ath12k_base *ab, rcu_read_unlock(); } +static void ath12k_dp_htt_mlo_peer_map_handler(struct ath12k_base *ab, + struct sk_buff *skb) +{ + struct htt_resp_msg *resp = (struct htt_resp_msg *)skb->data; + struct htt_t2h_mlo_peer_map_event *ev = &resp->mlo_peer_map_ev; + u16 raw_peer_id, peer_id, addr_h16; + u8 peer_addr[ETH_ALEN]; + + if (skb->len < sizeof(*ev)) { + ath12k_warn(ab, "unexpected htt mlo peer map event len %u\n", + skb->len); + return; + } + + raw_peer_id = le32_get_bits(ev->info0, + HTT_T2H_MLO_PEER_MAP_INFO0_MLO_PEER_ID); + peer_id = raw_peer_id | ATH12K_PEER_ML_ID_VALID; + + addr_h16 = le32_get_bits(ev->info1, + HTT_T2H_MLO_PEER_MAP_INFO1_MAC_ADDR_H16); + ath12k_dp_get_mac_addr(le32_to_cpu(ev->mac_addr_l32), addr_h16, + peer_addr); + + ath12k_dbg(ab, ATH12K_DBG_DP_HTT, "htt mlo peer map peer %pM id %u\n", + peer_addr, peer_id); +} + void ath12k_dp_htt_htc_t2h_msg_handler(struct ath12k_base *ab, struct sk_buff *skb) { @@ -657,6 +684,9 @@ void ath12k_dp_htt_htc_t2h_msg_handler(struct ath12k_base *ab, case HTT_T2H_MSG_TYPE_MLO_TIMESTAMP_OFFSET_IND: ath12k_htt_mlo_offset_event_handler(ab, skb); break; + case HTT_T2H_MSG_TYPE_MLO_RX_PEER_MAP: + ath12k_dp_htt_mlo_peer_map_handler(ab, skb); + break; default: ath12k_dbg(ab, ATH12K_DBG_DP_HTT, "dp_htt event %d not handled\n", type); diff --git a/drivers/net/wireless/ath/ath12k/dp_htt.h b/drivers/net/wireless/ath/ath12k/dp_htt.h index 6020e632f74ec..a18e1072ad60e 100644 --- a/drivers/net/wireless/ath/ath12k/dp_htt.h +++ b/drivers/net/wireless/ath/ath12k/dp_htt.h @@ -930,6 +930,7 @@ enum htt_t2h_msg_type { HTT_T2H_MSG_TYPE_EXT_STATS_CONF = 0x1c, HTT_T2H_MSG_TYPE_BKPRESSURE_EVENT_IND = 0x24, HTT_T2H_MSG_TYPE_MLO_TIMESTAMP_OFFSET_IND = 0x28, + HTT_T2H_MSG_TYPE_MLO_RX_PEER_MAP = 0x29, HTT_T2H_MSG_TYPE_PEER_MAP3 = 0x2b, HTT_T2H_MSG_TYPE_VDEV_TXRX_STATS_PERIODIC_IND = 0x2c, }; @@ -974,11 +975,22 @@ struct htt_t2h_peer_unmap_event { __le32 info1; } __packed; +#define HTT_T2H_MLO_PEER_MAP_INFO0_MLO_PEER_ID GENMASK(23, 8) +#define HTT_T2H_MLO_PEER_MAP_INFO1_MAC_ADDR_H16 GENMASK(15, 0) + +struct htt_t2h_mlo_peer_map_event { + __le32 info0; + __le32 mac_addr_l32; + __le32 info1; + __le32 reserved[5]; +} __packed; + struct htt_resp_msg { union { struct htt_t2h_version_conf_msg version_msg; struct htt_t2h_peer_map_event peer_map_ev; struct htt_t2h_peer_unmap_event peer_unmap_ev; + struct htt_t2h_mlo_peer_map_event mlo_peer_map_ev; }; } __packed; From d1fd810f8d5593652860334c48b00c6c4843c763 Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Mon, 13 Jul 2026 15:33:20 +0800 Subject: [PATCH 5/8] FROMLIST: wifi: ath12k: introduce host_alloc_ml_id hardware parameter Different ath12k devices diverge on who allocates MLD peer id: WCN7850/QCC2072 have the firmware allocate it and notify the host via HTT_T2H_MSG_TYPE_MLO_RX_PEER_MAP event; While others let the host allocate it and pass it down through WMI_PEER_ASSOC_CMDID with ATH12K_WMI_FLAG_MLO_PEER_ID_VALID set. Currently ath12k host allocates this ID and sends it to firmware by default for all devices. This breaks WCN7850/QCC2072, because the host maintained ID may be different from the firmware-allocated one. Consequently data path may fail to find the dp peer and drop some received packets. From user point of view, this results in bugs reported in [1] or the 4-way handshake timeout issue. Add host_alloc_ml_id flag to struct ath12k_hw_params (and a copy on struct ath12k_hw for hot-path access) so subsequent patches can branch on it. Set true for QCN9274/IPQ5332, false for WCN7850/QCC2072. The flag will be consumed by subsequent patches. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Link: https://bugzilla.kernel.org/show_bug.cgi?id=221039 # 1 Link: https://lore.kernel.org/linux-wireless/20260713-ath12k-fw-allocated-ml-peer-id-v1-5-d0a2a1a519eb@oss.qualcomm.com/ Signed-off-by: Baochen Qiang Signed-off-by: Gaole Zhang --- drivers/net/wireless/ath/ath12k/core.h | 1 + drivers/net/wireless/ath/ath12k/hw.h | 2 ++ drivers/net/wireless/ath/ath12k/mac.c | 17 ++++++++++++++++- drivers/net/wireless/ath/ath12k/wifi7/hw.c | 10 ++++++++++ 4 files changed, 29 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h index d15eb7c8a45a7..a3f22ec1a757a 100644 --- a/drivers/net/wireless/ath/ath12k/core.h +++ b/drivers/net/wireless/ath/ath12k/core.h @@ -770,6 +770,7 @@ struct ath12k_hw { enum ath12k_hw_state state; bool regd_updated; bool use_6ghz_regd; + bool host_alloc_ml_id; u8 num_radio; diff --git a/drivers/net/wireless/ath/ath12k/hw.h b/drivers/net/wireless/ath/ath12k/hw.h index a9888e0521a1d..ba8870806d14d 100644 --- a/drivers/net/wireless/ath/ath12k/hw.h +++ b/drivers/net/wireless/ath/ath12k/hw.h @@ -213,6 +213,8 @@ struct ath12k_hw_params { /* setup REO queue, frag etc only for primary link peer */ bool dp_primary_link_only:1; + + bool host_alloc_ml_id; }; struct ath12k_hw_ops { diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 329c4011143db..60ffa3578ff67 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -15126,8 +15126,9 @@ int ath12k_mac_allocate(struct ath12k_hw_group *ag) int mac_id, device_id, total_radio, num_hw; struct ath12k_base *ab; struct ath12k_hw *ah; - int ret, i, j; + bool conf = false; u8 radio_per_hw; + int ret, i, j; total_radio = 0; for (i = 0; i < ag->num_devices; i++) { @@ -15168,6 +15169,19 @@ int ath12k_mac_allocate(struct ath12k_hw_group *ag) } ab = ag->ab[device_id]; + + /* + * the assumption is all devices within an ah + * share the same host_alloc_ml_id configuration + */ + if (j == 0) { + conf = ab->hw_params->host_alloc_ml_id; + } else if (conf != ab->hw_params->host_alloc_ml_id) { + ath12k_warn(ab, "inconsistent ML ID config within ah, device 0 uses %s allocated ID, while device %u doesn't\n", + conf ? "host" : "firmware", device_id); + goto err; + } + pdev_map[j].ab = ab; pdev_map[j].pdev_idx = mac_id; mac_id++; @@ -15192,6 +15206,7 @@ int ath12k_mac_allocate(struct ath12k_hw_group *ag) } ah->dev = ab->dev; + ah->host_alloc_ml_id = conf; ag->ah[i] = ah; ag->num_hw++; diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hw.c b/drivers/net/wireless/ath/ath12k/wifi7/hw.c index df045ddf42da9..2e2b26bc4fb8d 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/hw.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/hw.c @@ -422,6 +422,8 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = { .current_cc_support = false, .dp_primary_link_only = true, + + .host_alloc_ml_id = true, }, { .name = "wcn7850 hw2.0", @@ -508,6 +510,8 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = { .current_cc_support = true, .dp_primary_link_only = false, + + .host_alloc_ml_id = false, }, { .name = "qcn9274 hw2.0", @@ -590,6 +594,8 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = { .current_cc_support = false, .dp_primary_link_only = true, + + .host_alloc_ml_id = true, }, { .name = "ipq5332 hw1.0", @@ -665,6 +671,8 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = { .bdf_addr_offset = 0xC00000, .dp_primary_link_only = true, + + .host_alloc_ml_id = true, }, { .name = "qcc2072 hw1.0", @@ -752,6 +760,8 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = { .current_cc_support = true, .dp_primary_link_only = false, + + .host_alloc_ml_id = false, }, }; From d304b18e4f2d7b21f571a06a01c07c6f67bc1804 Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Mon, 13 Jul 2026 15:33:21 +0800 Subject: [PATCH 6/8] FROMLIST: wifi: ath12k: do not advertise MLD peer ID for firmware-allocate devices ath12k_peer_assoc_h_mlo() unconditionally sets ml->peer_id_valid and copies ahsta->ml_peer_id (with the ATH12K_PEER_ML_ID_VALID bookkeeping bit masked off) into the WMI_PEER_ASSOC_CMDID ML params, which causes ath12k_wmi_send_peer_assoc_cmd() to set ATH12K_WMI_FLAG_MLO_PEER_ID_VALID. This needs to be gated on chips where the firmware allocates the MLD peer ID: - WCN7850/QCC2072 firmware always picks the ID itself and does not honor a host-supplied one, so the value would be silently ignored anyway; - QCC2072 firmware additionally crashes during MLO disconnect when ATH12K_WMI_FLAG_MLO_PEER_ID_VALID was set in the preceding peer assoc, so the bit must not be sent at all. Branch on ah->host_alloc_ml_id: - When true (QCN9274 etc.), behavior is unchanged: peer_id_valid is set and the raw ahsta->ml_peer_id (without the VALID bit) is sent down. - When false (WCN7850, QCC2072), peer_id_valid stays unset and ml_peer_id is sent as 0. The firmware ignores both fields and reports the ID it allocated through HTT_T2H_MSG_TYPE_MLO_RX_PEER_MAP. The early-return on ahsta->ml_peer_id == ATH12K_MLO_PEER_ID_INVALID only applies on the host-alloc path, since on the firmware-alloc path the value is ATH12K_MLO_PEER_ID_PENDING here, not INVALID. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Link: https://lore.kernel.org/linux-wireless/20260713-ath12k-fw-allocated-ml-peer-id-v1-6-d0a2a1a519eb@oss.qualcomm.com/ Signed-off-by: Baochen Qiang Signed-off-by: Gaole Zhang --- drivers/net/wireless/ath/ath12k/mac.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 60ffa3578ff67..98784e2fa5229 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -3518,11 +3518,16 @@ static void ath12k_peer_assoc_h_mlo(struct ath12k_link_sta *arsta, struct ath12k_sta *ahsta = arsta->ahsta; struct ath12k_link_sta *arsta_p; struct ath12k_link_vif *arvif; + struct ath12k_hw *ah = arsta->arvif->ar->ah; unsigned long links; u8 link_id; int i; - if (!sta->mlo || ahsta->ml_peer_id == ATH12K_MLO_PEER_ID_INVALID) + if (!sta->mlo) + return; + + if (ah->host_alloc_ml_id && + ahsta->ml_peer_id == ATH12K_MLO_PEER_ID_INVALID) return; ml->enabled = true; @@ -3530,16 +3535,25 @@ static void ath12k_peer_assoc_h_mlo(struct ath12k_link_sta *arsta, /* For now considering the primary umac based on assoc link */ ml->primary_umac = arsta->is_assoc_link; - ml->peer_id_valid = true; + /* + * Only chips that allocate the MLD peer ID on the host send a valid + * ml_peer_id in WMI_PEER_ASSOC_CMDID. For chips where the firmware + * picks the ID, leave peer_id_valid false to avoid unexpected issues. + */ + ml->peer_id_valid = ah->host_alloc_ml_id; ml->logical_link_idx_valid = true; ether_addr_copy(ml->mld_addr, sta->addr); ml->logical_link_idx = arsta->link_idx; /* * WMI_MLO_PEER_ASSOC_PARAMS expects the raw ML peer ID without - * the host-side ATH12K_PEER_ML_ID_VALID bookkeeping bit. + * the host-side ATH12K_PEER_ML_ID_VALID bookkeeping bit. For chips + * where the firmware allocates the ID, the field is unused (the + * firmware always allocates regardless of the value here); send 0 + * to make that intent explicit. */ - ml->ml_peer_id = ahsta->ml_peer_id & ~ATH12K_PEER_ML_ID_VALID; + ml->ml_peer_id = ah->host_alloc_ml_id ? + (ahsta->ml_peer_id & ~ATH12K_PEER_ML_ID_VALID) : 0; ml->ieee_link_id = arsta->link_id; ml->num_partner_links = 0; ml->eml_cap = sta->eml_cap; From 4e1984190f14ac6a458efd802a237a54555749a1 Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Mon, 13 Jul 2026 15:33:22 +0800 Subject: [PATCH 7/8] FROMLIST: wifi: ath12k: defer dp_peer registration when firmware allocates MLD peer ID For chips with host_alloc_ml_id=true (QCN9274 etc.), the host allocates the MLD peer ID up front; ath12k_dp_peer_create() publishes the dp_peer into dp_hw->dp_peers[] using that ID immediately. WCN7850/QCC2072 does not work that way: the firmware picks the ID and only tells the host afterwards via HTT_T2H_MSG_TYPE_MLO_RX_PEER_MAP, so the publication has to be delayed until the event arrives. Introduce ATH12K_MLO_PEER_ID_PENDING (0xFFFE) as a sentinel for "is_mlo, but ID not yet known". On the firmware-allocates path: - ath12k_mac_op_sta_state(NOTEXIST->NONE) skips ath12k_peer_ml_alloc() and stores PENDING in ahsta->ml_peer_id and dp_params.peer_id; - ath12k_dp_peer_create() skips dp_peer registration until a real ID is known; - ath12k_peer_create() leaves peer->ml_id at INVALID so consumer sites do not treat PENDING as a real ID; - ath12k_peer_ml_free() and ath12k_mac_dp_peer_cleanup() skip the dp_peers[] write and the free_ml_peer_id_map clear when host_alloc_ml_id is false or the ID is still PENDING. The HTT handler change that resolves the PENDING ID is added in a follow-up patch. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Link: https://lore.kernel.org/linux-wireless/20260713-ath12k-fw-allocated-ml-peer-id-v1-7-d0a2a1a519eb@oss.qualcomm.com/ Signed-off-by: Baochen Qiang Signed-off-by: Gaole Zhang --- drivers/net/wireless/ath/ath12k/core.h | 1 + drivers/net/wireless/ath/ath12k/dp_peer.c | 23 +++++++++++++++-------- drivers/net/wireless/ath/ath12k/mac.c | 22 ++++++++++++++++------ drivers/net/wireless/ath/ath12k/peer.c | 20 +++++++++++++++++--- 4 files changed, 49 insertions(+), 17 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h index a3f22ec1a757a..cb916a06d4ba2 100644 --- a/drivers/net/wireless/ath/ath12k/core.h +++ b/drivers/net/wireless/ath/ath12k/core.h @@ -71,6 +71,7 @@ #define ATH12K_MAX_MLO_PEERS 256 #define ATH12K_MLO_PEER_ID_INVALID 0xFFFF +#define ATH12K_MLO_PEER_ID_PENDING 0xFFFE #define ATH12K_INVALID_RSSI_FULL -1 #define ATH12K_INVALID_RSSI_EMPTY -128 diff --git a/drivers/net/wireless/ath/ath12k/dp_peer.c b/drivers/net/wireless/ath/ath12k/dp_peer.c index caa56942f0334..af7bbd1d61275 100644 --- a/drivers/net/wireless/ath/ath12k/dp_peer.c +++ b/drivers/net/wireless/ath/ath12k/dp_peer.c @@ -472,7 +472,9 @@ int ath12k_dp_peer_create(struct ath12k_dp_hw *dp_hw, u8 *addr, dp_peer->is_mlo = params->is_mlo; /* - * For MLO client, the host assigns the ML peer ID, so set peer_id in dp_peer + * For MLO client, the ML peer ID, either known or PENDING, needs to be + * initialized here since the following logic depends on it. + * * For non-MLO client, host gets link peer ID from firmware and will be * assigned at the time of link peer creation */ @@ -488,13 +490,17 @@ int ath12k_dp_peer_create(struct ath12k_dp_hw *dp_hw, u8 *addr, list_add(&dp_peer->list, &dp_hw->dp_peers_list); /* - * For MLO client, the peer_id for ath12k_dp_peer is allocated by host - * and that peer_id is known at this point, and hence this ath12k_dp_peer - * can be added to the RCU table using the peer_id. - * For non-MLO client, this addition to RCU table shall be done at the - * time of assignment of ath12k_dp_link_peer to ath12k_dp_peer. + * For an MLO client whose ML peer ID is allocated by the host, the + * peer_id is known here and the dp_peer can be added to the RCU + * table using it. For an MLO client on chips where the firmware + * allocates the ID, peer_id is ATH12K_MLO_PEER_ID_PENDING and the + * RCU table publish is deferred to the + * HTT_T2H_MSG_TYPE_MLO_RX_PEER_MAP handler. For a non-MLO client + * the publish happens later, at the time of assignment of + * ath12k_dp_link_peer to ath12k_dp_peer. */ - if (dp_peer->is_mlo) + if (dp_peer->is_mlo && + dp_peer->peer_id != ATH12K_MLO_PEER_ID_PENDING) rcu_assign_pointer(dp_hw->dp_peers[dp_peer->peer_id], dp_peer); spin_unlock_bh(&dp_hw->peer_lock); @@ -515,7 +521,8 @@ void ath12k_dp_peer_delete(struct ath12k_dp_hw *dp_hw, u8 *addr, return; } - if (dp_peer->is_mlo) + if (dp_peer->is_mlo && + dp_peer->peer_id != ATH12K_MLO_PEER_ID_PENDING) rcu_assign_pointer(dp_hw->dp_peers[dp_peer->peer_id], NULL); list_del(&dp_peer->list); diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 98784e2fa5229..6296a65763d7f 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -1278,7 +1278,9 @@ void ath12k_mac_dp_peer_cleanup(struct ath12k_hw *ah) spin_lock_bh(&dp_hw->peer_lock); list_for_each_entry_safe(dp_peer, tmp, &dp_hw->dp_peers_list, list) { if (dp_peer->is_mlo) { - rcu_assign_pointer(dp_hw->dp_peers[dp_peer->peer_id], NULL); + if (dp_peer->peer_id != ATH12K_MLO_PEER_ID_PENDING) + rcu_assign_pointer(dp_hw->dp_peers[dp_peer->peer_id], + NULL); ath12k_peer_ml_free(ah, ath12k_sta_to_ahsta(dp_peer->sta)); } @@ -7657,11 +7659,19 @@ int ath12k_mac_op_sta_state(struct ieee80211_hw *hw, /* ML sta */ if (sta->mlo && !ahsta->links_map && (hweight16(sta->valid_links) == 1)) { - ahsta->ml_peer_id = ath12k_peer_ml_alloc(ah); - if (ahsta->ml_peer_id == ATH12K_MLO_PEER_ID_INVALID) { - ath12k_hw_warn(ah, "unable to allocate ML peer id for sta %pM", - sta->addr); - goto exit; + if (ah->host_alloc_ml_id) { + ahsta->ml_peer_id = ath12k_peer_ml_alloc(ah); + if (ahsta->ml_peer_id == ATH12K_MLO_PEER_ID_INVALID) { + ath12k_hw_warn(ah, "unable to allocate ML peer id for sta %pM", + sta->addr); + goto exit; + } + } else { + /* + * firmware allocates the ML peer ID and notifies + * the host via HTT_T2H_MSG_TYPE_MLO_RX_PEER_MAP + */ + ahsta->ml_peer_id = ATH12K_MLO_PEER_ID_PENDING; } dp_params.is_mlo = true; diff --git a/drivers/net/wireless/ath/ath12k/peer.c b/drivers/net/wireless/ath/ath12k/peer.c index 517416f7cd7d1..fdcd3f5711a77 100644 --- a/drivers/net/wireless/ath/ath12k/peer.c +++ b/drivers/net/wireless/ath/ath12k/peer.c @@ -224,7 +224,16 @@ int ath12k_peer_create(struct ath12k *ar, struct ath12k_link_vif *arvif, /* Fill ML info into created peer */ if (sta->mlo) { ml_peer_id = ahsta->ml_peer_id; - peer->ml_id = ml_peer_id; + /* + * For chips where firmware allocates the ML peer ID, + * ml_peer_id is ATH12K_MLO_PEER_ID_PENDING here. The + * MLO_RX_PEER_MAP HTT event handler fixes up + * peer->ml_id once the ID is known. + */ + if (ml_peer_id == ATH12K_MLO_PEER_ID_PENDING) + peer->ml_id = ATH12K_MLO_PEER_ID_INVALID; + else + peer->ml_id = ml_peer_id; ether_addr_copy(peer->ml_addr, sta->addr); /* the assoc link is considered primary for now */ @@ -276,8 +285,13 @@ void ath12k_peer_ml_free(struct ath12k_hw *ah, struct ath12k_sta *ahsta) { lockdep_assert_wiphy(ah->hw->wiphy); - if (ahsta->ml_peer_id < - (ATH12K_MAX_MLO_PEERS | ATH12K_PEER_ML_ID_VALID)) + /* + * Only devices that allocate the ID on the host own a slot in + * free_ml_peer_id_map. + */ + if (ah->host_alloc_ml_id && + (ahsta->ml_peer_id < + (ATH12K_MAX_MLO_PEERS | ATH12K_PEER_ML_ID_VALID))) clear_bit(ahsta->ml_peer_id & ~ATH12K_PEER_ML_ID_VALID, ah->free_ml_peer_id_map); ahsta->ml_peer_id = ATH12K_MLO_PEER_ID_INVALID; From 7666589974e0218c6a36983cd7d4fb967427be8b Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Mon, 13 Jul 2026 15:33:23 +0800 Subject: [PATCH 8/8] FROMLIST: wifi: ath12k: resolve PENDING ML peer ID from MLO_PEER_MAP HTT event Add ath12k_dp_peer_fixup_peer_id() and call it from the HTT_T2H_MSG_TYPE_MLO_RX_PEER_MAP handler. For devices where the firmware allocates the MLD peer ID, this is the point at which all data structures that were left with ATH12K_MLO_PEER_ID_PENDING or ATH12K_MLO_PEER_ID_INVALID get their real ID: - dp_peer->peer_id is updated and the dp_peer is published into dp_hw->dp_peers[]; - every existing dp_link_peer in dp_peer->link_peers[] gets its ml_id set to the same value; - ahsta->ml_peer_id is updated to the same value so peer_assoc, sta_state and cleanup paths see a consistent ID. Devices with host_alloc_ml_id == true also receive the same HTT event, but the firmware-reported ID always matches the host-allocated one and everything has already been populated by ath12k_dp_peer_create(); Skips the helper entirely on those devices. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c5-00302-QCAHMTSWPL_V1.0_V2.0_SILICONZ-1.115823.3 Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221039 Link: https://lore.kernel.org/linux-wireless/20260713-ath12k-fw-allocated-ml-peer-id-v1-8-d0a2a1a519eb@oss.qualcomm.com/ Signed-off-by: Baochen Qiang Signed-off-by: Gaole Zhang --- drivers/net/wireless/ath/ath12k/core.c | 2 + drivers/net/wireless/ath/ath12k/core.h | 1 + drivers/net/wireless/ath/ath12k/dp_htt.c | 19 +++++++++ drivers/net/wireless/ath/ath12k/dp_peer.c | 52 +++++++++++++++++++++++ drivers/net/wireless/ath/ath12k/dp_peer.h | 2 + drivers/net/wireless/ath/ath12k/mac.c | 24 +++++++++++ 6 files changed, 100 insertions(+) diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c index 9d6c50a94e642..7ba9ebf52efd1 100644 --- a/drivers/net/wireless/ath/ath12k/core.c +++ b/drivers/net/wireless/ath/ath12k/core.c @@ -1515,6 +1515,8 @@ static void ath12k_core_pre_reconfigure_recovery(struct ath12k_base *ab) } wiphy_unlock(ah->hw->wiphy); + + complete(&ah->peer_ml_id_done); } wake_up(&ab->wmi_ab.tx_credits_wq); diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h index cb916a06d4ba2..1ab27821fafe5 100644 --- a/drivers/net/wireless/ath/ath12k/core.h +++ b/drivers/net/wireless/ath/ath12k/core.h @@ -772,6 +772,7 @@ struct ath12k_hw { bool regd_updated; bool use_6ghz_regd; bool host_alloc_ml_id; + struct completion peer_ml_id_done; u8 num_radio; diff --git a/drivers/net/wireless/ath/ath12k/dp_htt.c b/drivers/net/wireless/ath/ath12k/dp_htt.c index 982d3eec4de4e..2219f66cd06b1 100644 --- a/drivers/net/wireless/ath/ath12k/dp_htt.c +++ b/drivers/net/wireless/ath/ath12k/dp_htt.c @@ -6,6 +6,7 @@ #include "core.h" #include "peer.h" +#include "dp_peer.h" #include "htc.h" #include "dp_htt.h" #include "debugfs_htt_stats.h" @@ -580,6 +581,7 @@ static void ath12k_dp_htt_mlo_peer_map_handler(struct ath12k_base *ab, struct htt_t2h_mlo_peer_map_event *ev = &resp->mlo_peer_map_ev; u16 raw_peer_id, peer_id, addr_h16; u8 peer_addr[ETH_ALEN]; + int ret; if (skb->len < sizeof(*ev)) { ath12k_warn(ab, "unexpected htt mlo peer map event len %u\n", @@ -598,6 +600,23 @@ static void ath12k_dp_htt_mlo_peer_map_handler(struct ath12k_base *ab, ath12k_dbg(ab, ATH12K_DBG_DP_HTT, "htt mlo peer map peer %pM id %u\n", peer_addr, peer_id); + + /* + * Fix up the dp_peer entry created with ATH12K_MLO_PEER_ID_PENDING + * earlier; on chips with host_alloc_ml_id == false this is the only + * point at which the host learns the firmware-assigned ID. Chips + * that allocate the ID on the host also receive this event but the + * firmware-reported ID matches the host-allocated one, so there is + * nothing to fix up. + */ + if (!ab->hw_params->host_alloc_ml_id) { + ret = ath12k_dp_peer_fixup_peer_id(ab, peer_addr, + peer_id); + if (ret) + ath12k_warn(ab, + "failed to fix up peer id %u for dp peer %pM: %d\n", + peer_id, peer_addr, ret); + } } void ath12k_dp_htt_htc_t2h_msg_handler(struct ath12k_base *ab, diff --git a/drivers/net/wireless/ath/ath12k/dp_peer.c b/drivers/net/wireless/ath/ath12k/dp_peer.c index af7bbd1d61275..955fbafae0a60 100644 --- a/drivers/net/wireless/ath/ath12k/dp_peer.c +++ b/drivers/net/wireless/ath/ath12k/dp_peer.c @@ -695,3 +695,55 @@ void ath12k_dp_link_peer_reset_rx_stats(struct ath12k_dp *dp, const u8 *addr) if (rx_stats) memset(rx_stats, 0, sizeof(*rx_stats)); } + +int ath12k_dp_peer_fixup_peer_id(struct ath12k_base *ab, + const u8 *peer_addr, u16 peer_id) +{ + struct ath12k_dp_link_peer *link_peer; + struct ath12k_dp_peer *dp_peer = NULL; + struct ath12k_hw_group *ag = ab->ag; + struct ath12k_dp_hw *dp_hw = NULL; + struct ath12k_hw *ah; + int i; + + if (peer_id >= (ATH12K_PEER_ML_ID_VALID | ATH12K_MAX_MLO_PEERS)) + return -EINVAL; + + for (i = 0; i < ag->num_hw; i++) { + ah = ag->ah[i]; + if (!ah) + continue; + + spin_lock_bh(&ah->dp_hw.peer_lock); + dp_peer = ath12k_dp_peer_find_by_addr(&ah->dp_hw, + (u8 *)peer_addr); + if (dp_peer) { + dp_hw = &ah->dp_hw; + break; + } + spin_unlock_bh(&ah->dp_hw.peer_lock); + } + + if (!dp_peer) + return -ENOENT; + + /* dp_hw->peer_lock is held */ + + dp_peer->peer_id = peer_id; + rcu_assign_pointer(dp_hw->dp_peers[peer_id], dp_peer); + + for (i = 0; i < ATH12K_NUM_MAX_LINKS; i++) { + link_peer = rcu_dereference_protected(dp_peer->link_peers[i], + lockdep_is_held(&dp_hw->peer_lock)); + if (link_peer) + link_peer->ml_id = peer_id; + } + + ath12k_sta_to_ahsta(dp_peer->sta)->ml_peer_id = peer_id; + + spin_unlock_bh(&dp_hw->peer_lock); + + complete(&ah->peer_ml_id_done); + + return 0; +} diff --git a/drivers/net/wireless/ath/ath12k/dp_peer.h b/drivers/net/wireless/ath/ath12k/dp_peer.h index 20294ff095131..f821d20596a6f 100644 --- a/drivers/net/wireless/ath/ath12k/dp_peer.h +++ b/drivers/net/wireless/ath/ath12k/dp_peer.h @@ -179,4 +179,6 @@ struct ath12k_dp_peer *ath12k_dp_peer_find_by_peerid(struct ath12k_pdev_dp *dp_p struct ath12k_dp_link_peer * ath12k_dp_link_peer_find_by_peerid(struct ath12k_pdev_dp *dp_pdev, u16 peer_id); void ath12k_dp_link_peer_free(struct ath12k_dp_link_peer *peer); +int ath12k_dp_peer_fixup_peer_id(struct ath12k_base *ab, const u8 *peer_addr, + u16 peer_id); #endif diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 6296a65763d7f..fdc837c8b64b2 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -3844,9 +3844,11 @@ static u32 ath12k_mac_ieee80211_sta_bw_to_wmi(struct ath12k *ar, static int ath12k_mac_peer_assoc(struct ath12k *ar, struct ath12k_wmi_peer_assoc_arg *peer_arg) { + struct ath12k_hw *ah = ath12k_ar_to_ah(ar); int ret; reinit_completion(&ar->peer_assoc_done); + reinit_completion(&ah->peer_ml_id_done); ret = ath12k_wmi_send_peer_assoc_cmd(ar, peer_arg); if (ret) { @@ -3861,6 +3863,27 @@ static int ath12k_mac_peer_assoc(struct ath12k *ar, return -ETIMEDOUT; } + /* + * For devices where the firmware allocates the MLD peer ID, the host + * learns the real ID only from the MLO_RX_PEER_MAP HTT event, which is + * handled in a softirq (BH workqueue) context that cannot take the + * wiphy lock. Block here, while still holding the wiphy lock, until + * that event has fixed up the ID. This serialises the fixup against + * all other wiphy-locked ml_peer_id accesses. + * + * The firmware sends the event only once, in response to the assoc-link + * peer assoc, so block only for that link. + */ + if (!ah->host_alloc_ml_id && + peer_arg->is_assoc && + peer_arg->ml.enabled && + peer_arg->ml.assoc_link && + !wait_for_completion_timeout(&ah->peer_ml_id_done, 1 * HZ)) { + ath12k_warn(ar->ab, "failed to get MLO peer map event for %pM vdev %i\n", + peer_arg->peer_mac, peer_arg->vdev_id); + return -ETIMEDOUT; + } + return 0; } @@ -15077,6 +15100,7 @@ static struct ath12k_hw *ath12k_mac_hw_allocate(struct ath12k_hw_group *ag, ah->num_radio = num_pdev_map; mutex_init(&ah->hw_mutex); + init_completion(&ah->peer_ml_id_done); spin_lock_init(&ah->dp_hw.peer_lock); INIT_LIST_HEAD(&ah->dp_hw.dp_peers_list);