diff options
Diffstat (limited to 'sys/contrib/dev/iwlwifi/mld/agg.c')
| -rw-r--r-- | sys/contrib/dev/iwlwifi/mld/agg.c | 680 | 
1 files changed, 680 insertions, 0 deletions
| diff --git a/sys/contrib/dev/iwlwifi/mld/agg.c b/sys/contrib/dev/iwlwifi/mld/agg.c new file mode 100644 index 000000000000..3a346bcd6665 --- /dev/null +++ b/sys/contrib/dev/iwlwifi/mld/agg.c @@ -0,0 +1,680 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/* + * Copyright (C) 2024-2025 Intel Corporation + */ +#include "agg.h" +#include "sta.h" +#include "hcmd.h" +#if defined(__FreeBSD__) +#include <linux/cache.h> +#endif + +static void +iwl_mld_reorder_release_frames(struct iwl_mld *mld, struct ieee80211_sta *sta, +			       struct napi_struct *napi, +			       struct iwl_mld_baid_data *baid_data, +			       struct iwl_mld_reorder_buffer *reorder_buf, +			       u16 nssn) +{ +	struct iwl_mld_reorder_buf_entry *entries = +		&baid_data->entries[reorder_buf->queue * +				    baid_data->entries_per_queue]; +	u16 ssn = reorder_buf->head_sn; + +	while (ieee80211_sn_less(ssn, nssn)) { +		int index = ssn % baid_data->buf_size; +		struct sk_buff_head *skb_list = &entries[index].frames; +		struct sk_buff *skb; + +		ssn = ieee80211_sn_inc(ssn); + +		/* Empty the list. Will have more than one frame for A-MSDU. +		 * Empty list is valid as well since nssn indicates frames were +		 * received. +		 */ +		while ((skb = __skb_dequeue(skb_list))) { +			iwl_mld_pass_packet_to_mac80211(mld, napi, skb, +							reorder_buf->queue, +							sta); +			reorder_buf->num_stored--; +		} +	} +	reorder_buf->head_sn = nssn; +} + +static void iwl_mld_release_frames_from_notif(struct iwl_mld *mld, +					      struct napi_struct *napi, +					      u8 baid, u16 nssn, int queue) +{ +	struct iwl_mld_reorder_buffer *reorder_buf; +	struct iwl_mld_baid_data *ba_data; +	struct ieee80211_link_sta *link_sta; +	u32 sta_id; + +	IWL_DEBUG_HT(mld, "Frame release notification for BAID %u, NSSN %d\n", +		     baid, nssn); + +	if (WARN_ON_ONCE(baid == IWL_RX_REORDER_DATA_INVALID_BAID || +			 baid >= ARRAY_SIZE(mld->fw_id_to_ba))) +		return; + +	rcu_read_lock(); + +	ba_data = rcu_dereference(mld->fw_id_to_ba[baid]); +	if (!ba_data) { +		IWL_DEBUG_HT(mld, "BAID %d not found in map\n", baid); +		goto out_unlock; +	} + +	/* pick any STA ID to find the pointer */ +	sta_id = ffs(ba_data->sta_mask) - 1; +	link_sta = rcu_dereference(mld->fw_id_to_link_sta[sta_id]); +	if (WARN_ON_ONCE(IS_ERR_OR_NULL(link_sta) || !link_sta->sta)) +		goto out_unlock; + +	reorder_buf = &ba_data->reorder_buf[queue]; + +	iwl_mld_reorder_release_frames(mld, link_sta->sta, napi, ba_data, +				       reorder_buf, nssn); +out_unlock: +	rcu_read_unlock(); +} + +void iwl_mld_handle_frame_release_notif(struct iwl_mld *mld, +					struct napi_struct *napi, +					struct iwl_rx_packet *pkt, int queue) +{ +	struct iwl_frame_release *release = (void *)pkt->data; +	u32 pkt_len = iwl_rx_packet_payload_len(pkt); + +	if (IWL_FW_CHECK(mld, pkt_len < sizeof(*release), +			 "Unexpected frame release notif size %u (expected %zu)\n", +			 pkt_len, sizeof(*release))) +		return; + +	iwl_mld_release_frames_from_notif(mld, napi, release->baid, +					  le16_to_cpu(release->nssn), +					  queue); +} + +void iwl_mld_handle_bar_frame_release_notif(struct iwl_mld *mld, +					    struct napi_struct *napi, +					    struct iwl_rx_packet *pkt, +					    int queue) +{ +	struct iwl_bar_frame_release *release = (void *)pkt->data; +	struct iwl_mld_baid_data *baid_data; +	unsigned int baid, nssn, sta_id, tid; +	u32 pkt_len = iwl_rx_packet_payload_len(pkt); + +	if (IWL_FW_CHECK(mld, pkt_len < sizeof(*release), +			 "Unexpected frame release notif size %u (expected %zu)\n", +			 pkt_len, sizeof(*release))) +		return; + +	baid = le32_get_bits(release->ba_info, +			     IWL_BAR_FRAME_RELEASE_BAID_MASK); +	nssn = le32_get_bits(release->ba_info, +			     IWL_BAR_FRAME_RELEASE_NSSN_MASK); +	sta_id = le32_get_bits(release->sta_tid, +			       IWL_BAR_FRAME_RELEASE_STA_MASK); +	tid = le32_get_bits(release->sta_tid, +			    IWL_BAR_FRAME_RELEASE_TID_MASK); + +	if (IWL_FW_CHECK(mld, baid >= ARRAY_SIZE(mld->fw_id_to_ba), +			 "BAR release: invalid BAID (%x)\n", baid)) +		return; + +	rcu_read_lock(); +	baid_data = rcu_dereference(mld->fw_id_to_ba[baid]); +	if (!baid_data) { +		IWL_DEBUG_HT(mld, +			     "Got valid BAID %d but not allocated\n", +			     baid); +		goto out_unlock; +	} + +	if (IWL_FW_CHECK(mld, tid != baid_data->tid || +			 sta_id > mld->fw->ucode_capa.num_stations || +			 !(baid_data->sta_mask & BIT(sta_id)), +			 "BAID 0x%x is mapped to sta_mask:0x%x tid:%d, but BAR release received for sta:%d tid:%d\n", +			 baid, baid_data->sta_mask, baid_data->tid, sta_id, +			 tid)) +		goto out_unlock; + +	IWL_DEBUG_DROP(mld, "Received a BAR, expect packet loss: nssn %d\n", +		       nssn); + +	iwl_mld_release_frames_from_notif(mld, napi, baid, nssn, queue); +out_unlock: +	rcu_read_unlock(); +} + +void iwl_mld_del_ba(struct iwl_mld *mld, int queue, +		    struct iwl_mld_delba_data *data) +{ +	struct iwl_mld_baid_data *ba_data; +	struct iwl_mld_reorder_buffer *reorder_buf; +	struct ieee80211_link_sta *link_sta; +	u8 baid = data->baid; +	u32 sta_id; + +	if (WARN_ONCE(baid >= IWL_MAX_BAID, "invalid BAID: %x\n", baid)) +		return; + +	rcu_read_lock(); + +	ba_data = rcu_dereference(mld->fw_id_to_ba[baid]); +	if (WARN_ON_ONCE(!ba_data)) +		goto out_unlock; + +	/* pick any STA ID to find the pointer */ +	sta_id = ffs(ba_data->sta_mask) - 1; +	link_sta = rcu_dereference(mld->fw_id_to_link_sta[sta_id]); +	if (WARN_ON_ONCE(IS_ERR_OR_NULL(link_sta) || !link_sta->sta)) +		goto out_unlock; + +	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, +				       ba_data, reorder_buf, +				       ieee80211_sn_add(reorder_buf->head_sn, +							ba_data->buf_size)); +out_unlock: +	rcu_read_unlock(); +} + +/* Returns true if the MPDU was buffered\dropped, false if it should be passed + * to upper layer. + */ +enum iwl_mld_reorder_result +iwl_mld_reorder(struct iwl_mld *mld, struct napi_struct *napi, +		int queue, struct ieee80211_sta *sta, +		struct sk_buff *skb, struct iwl_rx_mpdu_desc *desc) +{ +	struct ieee80211_hdr *hdr = (void *)skb_mac_header(skb); +	struct iwl_mld_baid_data *baid_data; +	struct iwl_mld_reorder_buffer *buffer; +	struct iwl_mld_reorder_buf_entry *entries; +	struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta); +	struct iwl_mld_link_sta *mld_link_sta; +	u32 reorder = le32_to_cpu(desc->reorder_data); +	bool amsdu, last_subframe, is_old_sn, is_dup; +	u8 tid = ieee80211_get_tid(hdr); +	u8 baid; +	u16 nssn, sn; +	u32 sta_mask = 0; +	int index; +	u8 link_id; + +	baid = u32_get_bits(reorder, IWL_RX_MPDU_REORDER_BAID_MASK); + +	/* This also covers the case of receiving a Block Ack Request +	 * outside a BA session; we'll pass it to mac80211 and that +	 * then sends a delBA action frame. +	 * This also covers pure monitor mode, in which case we won't +	 * have any BA sessions. +	 */ +	if (baid == IWL_RX_REORDER_DATA_INVALID_BAID) +		return IWL_MLD_PASS_SKB; + +	/* no sta yet */ +	if (WARN_ONCE(!sta, +		      "Got valid BAID without a valid station assigned\n")) +		return IWL_MLD_PASS_SKB; + +	/* not a data packet */ +	if (!ieee80211_is_data_qos(hdr->frame_control) || +	    is_multicast_ether_addr(hdr->addr1)) +		return IWL_MLD_PASS_SKB; + +	if (unlikely(!ieee80211_is_data_present(hdr->frame_control))) +		return IWL_MLD_PASS_SKB; + +	baid_data = rcu_dereference(mld->fw_id_to_ba[baid]); +	if (!baid_data) { +		IWL_DEBUG_HT(mld, +			     "Got valid BAID but no baid allocated, bypass re-ordering (BAID=%d reorder=0x%x)\n", +			     baid, reorder); +		return IWL_MLD_PASS_SKB; +	} + +	for_each_mld_link_sta(mld_sta, mld_link_sta, link_id) +		sta_mask |= BIT(mld_link_sta->fw_id); + +	/* verify the BAID is correctly mapped to the sta and tid */ +	if (IWL_FW_CHECK(mld, +			 tid != baid_data->tid || +			 !(sta_mask & baid_data->sta_mask), +			 "BAID 0x%x is mapped to sta_mask:0x%x tid:%d, but was received for sta_mask:0x%x tid:%d\n", +			 baid, baid_data->sta_mask, baid_data->tid, +			 sta_mask, tid)) +		return IWL_MLD_PASS_SKB; + +	buffer = &baid_data->reorder_buf[queue]; +	entries = &baid_data->entries[queue * baid_data->entries_per_queue]; + +	is_old_sn = !!(reorder & IWL_RX_MPDU_REORDER_BA_OLD_SN); + +	if (!buffer->valid && is_old_sn) +		return IWL_MLD_PASS_SKB; + +	buffer->valid = true; + +	is_dup = !!(desc->status & cpu_to_le32(IWL_RX_MPDU_STATUS_DUPLICATE)); + +	/* drop any duplicated or outdated packets */ +	if (is_dup || is_old_sn) +		return IWL_MLD_DROP_SKB; + +	sn = u32_get_bits(reorder, IWL_RX_MPDU_REORDER_SN_MASK); +	nssn = u32_get_bits(reorder, IWL_RX_MPDU_REORDER_NSSN_MASK); +	amsdu = desc->mac_flags2 & IWL_RX_MPDU_MFLG2_AMSDU; +	last_subframe = desc->amsdu_info & IWL_RX_MPDU_AMSDU_LAST_SUBFRAME; + +	/* release immediately if allowed by nssn and no stored frames */ +	if (!buffer->num_stored && ieee80211_sn_less(sn, nssn)) { +		if (!amsdu || last_subframe) +			buffer->head_sn = nssn; +		return IWL_MLD_PASS_SKB; +	} + +	/* release immediately if there are no stored frames, and the sn is +	 * equal to the head. +	 * This can happen due to reorder timer, where NSSN is behind head_sn. +	 * When we released everything, and we got the next frame in the +	 * sequence, according to the NSSN we can't release immediately, +	 * while technically there is no hole and we can move forward. +	 */ +	if (!buffer->num_stored && sn == buffer->head_sn) { +		if (!amsdu || last_subframe) +			buffer->head_sn = ieee80211_sn_inc(buffer->head_sn); +		return IWL_MLD_PASS_SKB; +	} + +	/* put in reorder buffer */ +	index = sn % baid_data->buf_size; +	__skb_queue_tail(&entries[index].frames, skb); +	buffer->num_stored++; + +	/* We cannot trust NSSN for AMSDU sub-frames that are not the last. The +	 * reason is that NSSN advances on the first sub-frame, and may cause +	 * the reorder buffer to advance before all the sub-frames arrive. +	 * +	 * Example: reorder buffer contains SN 0 & 2, and we receive AMSDU with +	 * SN 1. NSSN for first sub frame will be 3 with the result of driver +	 * releasing SN 0,1, 2. When sub-frame 1 arrives - reorder buffer is +	 * already ahead and it will be dropped. +	 * If the last sub-frame is not on this queue - we will get frame +	 * release notification with up to date NSSN. +	 * If this is the first frame that is stored in the buffer, the head_sn +	 * may be outdated. Update it based on the last NSSN to make sure it +	 * will be released when the frame release notification arrives. +	 */ +	if (!amsdu || last_subframe) +		iwl_mld_reorder_release_frames(mld, sta, napi, baid_data, +					       buffer, nssn); +	else if (buffer->num_stored == 1) +		buffer->head_sn = nssn; + +	return IWL_MLD_BUFFERED_SKB; +} +EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(iwl_mld_reorder); + +static void iwl_mld_rx_agg_session_expired(struct timer_list *t) +{ +	struct iwl_mld_baid_data *data = +		timer_container_of(data, t, session_timer); +	struct iwl_mld_baid_data __rcu **rcu_ptr = data->rcu_ptr; +	struct iwl_mld_baid_data *ba_data; +	struct ieee80211_link_sta *link_sta; +	struct iwl_mld_sta *mld_sta; +	unsigned long timeout; +	unsigned int sta_id; + +	rcu_read_lock(); + +	ba_data = rcu_dereference(*rcu_ptr); +	if (WARN_ON(!ba_data)) +		goto unlock; + +	if (WARN_ON(!ba_data->timeout)) +		goto unlock; + +	timeout = ba_data->last_rx_timestamp + +		  TU_TO_JIFFIES(ba_data->timeout * 2); +	if (time_is_after_jiffies(timeout)) { +		mod_timer(&ba_data->session_timer, timeout); +		goto unlock; +	} + +	/* timer expired, pick any STA ID to find the pointer */ +	sta_id = ffs(ba_data->sta_mask) - 1; +	link_sta = rcu_dereference(ba_data->mld->fw_id_to_link_sta[sta_id]); + +	/* sta should be valid unless the following happens: +	 * The firmware asserts which triggers a reconfig flow, but +	 * the reconfig fails before we set the pointer to sta into +	 * the fw_id_to_link_sta pointer table. mac80211 can't stop +	 * A-MPDU and hence the timer continues to run. Then, the +	 * timer expires and sta is NULL. +	 */ +	if (IS_ERR_OR_NULL(link_sta) || WARN_ON(!link_sta->sta)) +		goto unlock; + +	mld_sta = iwl_mld_sta_from_mac80211(link_sta->sta); +	ieee80211_rx_ba_timer_expired(mld_sta->vif, link_sta->sta->addr, +				      ba_data->tid); +unlock: +	rcu_read_unlock(); +} + +static int +iwl_mld_stop_ba_in_fw(struct iwl_mld *mld, struct ieee80211_sta *sta, int tid) +{ +	struct iwl_rx_baid_cfg_cmd cmd = { +		.action = cpu_to_le32(IWL_RX_BAID_ACTION_REMOVE), +		.remove.sta_id_mask = +			cpu_to_le32(iwl_mld_fw_sta_id_mask(mld, sta)), +		.remove.tid = cpu_to_le32(tid), + +	}; +	int ret; + +	ret = iwl_mld_send_cmd_pdu(mld, +				   WIDE_ID(DATA_PATH_GROUP, +					   RX_BAID_ALLOCATION_CONFIG_CMD), +				   &cmd); +	if (ret) +		return ret; + +	IWL_DEBUG_HT(mld, "RX BA Session stopped in fw\n"); + +	return ret; +} + +static int +iwl_mld_start_ba_in_fw(struct iwl_mld *mld, struct ieee80211_sta *sta, +		       int tid, u16 ssn, u16 buf_size) +{ +	struct iwl_rx_baid_cfg_cmd cmd = { +		.action = cpu_to_le32(IWL_RX_BAID_ACTION_ADD), +		.alloc.sta_id_mask = +			cpu_to_le32(iwl_mld_fw_sta_id_mask(mld, sta)), +		.alloc.tid = tid, +		.alloc.ssn = cpu_to_le16(ssn), +		.alloc.win_size = cpu_to_le16(buf_size), +	}; +	struct iwl_host_cmd hcmd = { +		.id = WIDE_ID(DATA_PATH_GROUP, RX_BAID_ALLOCATION_CONFIG_CMD), +		.flags = CMD_WANT_SKB, +		.len[0] = sizeof(cmd), +		.data[0] = &cmd, +	}; +	struct iwl_rx_baid_cfg_resp *resp; +	struct iwl_rx_packet *pkt; +	u32 resp_len; +	int ret, baid; + +	BUILD_BUG_ON(sizeof(*resp) != sizeof(baid)); + +	ret = iwl_mld_send_cmd(mld, &hcmd); +	if (ret) +		return ret; + +	pkt = hcmd.resp_pkt; + +	resp_len = iwl_rx_packet_payload_len(pkt); +	if (IWL_FW_CHECK(mld, resp_len != sizeof(*resp), +			 "BAID_ALLOC_CMD: unexpected response length %d\n", +			 resp_len)) { +		ret = -EIO; +		goto out; +	} + +	IWL_DEBUG_HT(mld, "RX BA Session started in fw\n"); + +	resp = (void *)pkt->data; +	baid = le32_to_cpu(resp->baid); + +	if (IWL_FW_CHECK(mld, baid < 0 || baid >= ARRAY_SIZE(mld->fw_id_to_ba), +			 "BAID_ALLOC_CMD: invalid BAID response %d\n", baid)) { +		ret = -EINVAL; +		goto out; +	} + +	ret = baid; +out: +	iwl_free_resp(&hcmd); +	return ret; +} + +static void iwl_mld_init_reorder_buffer(struct iwl_mld *mld, +					struct iwl_mld_baid_data *data, +					u16 ssn) +{ +	for (int i = 0; i < mld->trans->info.num_rxqs; i++) { +		struct iwl_mld_reorder_buffer *reorder_buf = +			&data->reorder_buf[i]; +		struct iwl_mld_reorder_buf_entry *entries = +			&data->entries[i * data->entries_per_queue]; + +		reorder_buf->head_sn = ssn; +		reorder_buf->queue = i; + +		for (int j = 0; j < data->buf_size; j++) +			__skb_queue_head_init(&entries[j].frames); +	} +} + +static void iwl_mld_free_reorder_buffer(struct iwl_mld *mld, +					struct iwl_mld_baid_data *data) +{ +	struct iwl_mld_delba_data delba_data = { +		.baid = data->baid, +	}; + +	iwl_mld_sync_rx_queues(mld, IWL_MLD_RXQ_NOTIF_DEL_BA, +			       &delba_data, sizeof(delba_data)); + +	for (int i = 0; i < mld->trans->info.num_rxqs; i++) { +		struct iwl_mld_reorder_buffer *reorder_buf = +			&data->reorder_buf[i]; +		struct iwl_mld_reorder_buf_entry *entries = +			&data->entries[i * data->entries_per_queue]; + +		if (likely(!reorder_buf->num_stored)) +			continue; + +		/* This shouldn't happen in regular DELBA since the RX queues +		 * sync internal DELBA notification should trigger a release +		 * of all frames in the reorder buffer. +		 */ +		WARN_ON(1); + +		for (int j = 0; j < data->buf_size; j++) +			__skb_queue_purge(&entries[j].frames); +	} +} + +int iwl_mld_ampdu_rx_start(struct iwl_mld *mld, struct ieee80211_sta *sta, +			   int tid, u16 ssn, u16 buf_size, u16 timeout) +{ +	struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta); +	struct iwl_mld_baid_data *baid_data = NULL; +	u32 reorder_buf_size = buf_size * sizeof(baid_data->entries[0]); +	int ret, baid; +	u32 sta_mask; + +	lockdep_assert_wiphy(mld->wiphy); + +	if (mld->num_rx_ba_sessions >= IWL_MAX_BAID) { +		IWL_DEBUG_HT(mld, +			     "Max num of RX BA sessions reached; blocking new session\n"); +		return -ENOSPC; +	} + +	sta_mask = iwl_mld_fw_sta_id_mask(mld, sta); +	if (WARN_ON(!sta_mask)) +		return -EINVAL; + +	/* sparse doesn't like the __align() so don't check */ +#ifndef __CHECKER__ +	/* The division below will be OK if either the cache line size +	 * can be divided by the entry size (ALIGN will round up) or if +	 * the entry size can be divided by the cache line size, in which +	 * case the ALIGN() will do nothing. +	 */ +	BUILD_BUG_ON(SMP_CACHE_BYTES % sizeof(baid_data->entries[0]) && +		     sizeof(baid_data->entries[0]) % SMP_CACHE_BYTES); +#endif + +	/* Upward align the reorder buffer size to fill an entire cache +	 * line for each queue, to avoid sharing cache lines between +	 * different queues. +	 */ +	reorder_buf_size = ALIGN(reorder_buf_size, SMP_CACHE_BYTES); + +	/* Allocate here so if allocation fails we can bail out early +	 * before starting the BA session in the firmware +	 */ +	baid_data = kzalloc(sizeof(*baid_data) + +			    mld->trans->info.num_rxqs * reorder_buf_size, +			    GFP_KERNEL); +	if (!baid_data) +		return -ENOMEM; + +	/* This division is why we need the above BUILD_BUG_ON(), +	 * if that doesn't hold then this will not be right. +	 */ +	baid_data->entries_per_queue = +		reorder_buf_size / sizeof(baid_data->entries[0]); + +	baid = iwl_mld_start_ba_in_fw(mld, sta, tid, ssn, buf_size); +	if (baid < 0) { +		ret = baid; +		goto out_free; +	} + +	mld->num_rx_ba_sessions++; +	mld_sta->tid_to_baid[tid] = baid; + +	baid_data->baid = baid; +	baid_data->mld = mld; +	baid_data->tid = tid; +	baid_data->buf_size = buf_size; +	baid_data->sta_mask = sta_mask; +	baid_data->timeout = timeout; +	baid_data->last_rx_timestamp = jiffies; +	baid_data->rcu_ptr = &mld->fw_id_to_ba[baid]; + +	iwl_mld_init_reorder_buffer(mld, baid_data, ssn); + +	timer_setup(&baid_data->session_timer, iwl_mld_rx_agg_session_expired, +		    0); +	if (timeout) +		mod_timer(&baid_data->session_timer, +			  TU_TO_EXP_TIME(timeout * 2)); + +	IWL_DEBUG_HT(mld, "STA mask=0x%x (tid=%d) is assigned to BAID %d\n", +		     baid_data->sta_mask, tid, baid); + +	/* protect the BA data with RCU to cover a case where our +	 * internal RX sync mechanism will timeout (not that it's +	 * supposed to happen) and we will free the session data while +	 * RX is being processed in parallel +	 */ +	WARN_ON(rcu_access_pointer(mld->fw_id_to_ba[baid])); +	rcu_assign_pointer(mld->fw_id_to_ba[baid], baid_data); + +	return 0; + +out_free: +	kfree(baid_data); +	return ret; +} + +int iwl_mld_ampdu_rx_stop(struct iwl_mld *mld, struct ieee80211_sta *sta, +			  int tid) +{ +	struct iwl_mld_sta *mld_sta = iwl_mld_sta_from_mac80211(sta); +	int baid = mld_sta->tid_to_baid[tid]; +	struct iwl_mld_baid_data *baid_data; +	int ret; + +	lockdep_assert_wiphy(mld->wiphy); + +	/* during firmware restart, do not send the command as the firmware no +	 * longer recognizes the session. instead, only clear the driver BA +	 * session data. +	 */ +	if (!mld->fw_status.in_hw_restart) { +		ret = iwl_mld_stop_ba_in_fw(mld, sta, tid); +		if (ret) +			return ret; +	} + +	if (!WARN_ON(mld->num_rx_ba_sessions == 0)) +		mld->num_rx_ba_sessions--; + +	baid_data = wiphy_dereference(mld->wiphy, mld->fw_id_to_ba[baid]); +	if (WARN_ON(!baid_data)) +		return -EINVAL; + +	if (timer_pending(&baid_data->session_timer)) +		timer_shutdown_sync(&baid_data->session_timer); + +	iwl_mld_free_reorder_buffer(mld, baid_data); + +	RCU_INIT_POINTER(mld->fw_id_to_ba[baid], NULL); +	kfree_rcu(baid_data, rcu_head); + +	IWL_DEBUG_HT(mld, "BAID %d is free\n", baid); + +	return 0; +} + +int iwl_mld_update_sta_baids(struct iwl_mld *mld, +			     u32 old_sta_mask, +			     u32 new_sta_mask) +{ +	struct iwl_rx_baid_cfg_cmd cmd = { +		.action = cpu_to_le32(IWL_RX_BAID_ACTION_MODIFY), +		.modify.old_sta_id_mask = cpu_to_le32(old_sta_mask), +		.modify.new_sta_id_mask = cpu_to_le32(new_sta_mask), +	}; +	u32 cmd_id = WIDE_ID(DATA_PATH_GROUP, RX_BAID_ALLOCATION_CONFIG_CMD); +	int baid; + +	/* mac80211 will remove sessions later, but we ignore all that */ +	if (mld->fw_status.in_hw_restart) +		return 0; + +	BUILD_BUG_ON(sizeof(struct iwl_rx_baid_cfg_resp) != sizeof(baid)); + +	for (baid = 0; baid < ARRAY_SIZE(mld->fw_id_to_ba); baid++) { +		struct iwl_mld_baid_data *data; +		int ret; + +		data = wiphy_dereference(mld->wiphy, mld->fw_id_to_ba[baid]); +		if (!data) +			continue; + +		if (!(data->sta_mask & old_sta_mask)) +			continue; + +		WARN_ONCE(data->sta_mask != old_sta_mask, +			  "BAID data for %d corrupted - expected 0x%x found 0x%x\n", +			  baid, old_sta_mask, data->sta_mask); + +		cmd.modify.tid = cpu_to_le32(data->tid); + +		ret = iwl_mld_send_cmd_pdu(mld, cmd_id, &cmd); +		if (ret) +			return ret; +		data->sta_mask = new_sta_mask; +	} + +	return 0; +} | 
