Re: [PATCH RFC v2] wifi: ath10k: make in-order rx amsdu buffers persistent
Jeff Johnson <[email protected]> Thu, 30 Jul 2026 19:24:00 -0700
| Newsgroups | org.infradead.lists.ath10k,org.kernel.vger.linux-arm-msm,org.kernel.vger.linux-kernel,org.kernel.vger.linux-wireless,org.kernel.vger.phone-devel |
|---|---|
| Message-ID | <[email protected]> |
On 7/19/2026 2:45 PM, David Heidelberg via B4 Relay wrote: > From: Richard Acayan <[email protected]> > > The WCN3990 might split MSDUs among multiple "in-order" indications. The > driver needs information from previous indications to handle MPDUs that > are not started by the same indications that complete them. Move the > list that tracks unprocessed MSDUs to the driver state so the driver can > handle MPDUs that are split in this way and be less confused. > > Fixes: c545070e404b ("ath10k: implement rx reorder support") > Signed-off-by: Richard Acayan <[email protected]> > Co-developed-by: David Heidelberg <[email protected]> > Signed-off-by: David Heidelberg <[email protected]> > --- > This one we have in-tree forever. I'm transcribing a few comments from my review agent (which may overlap Sashiko). I have not vetted them for correctness. Hopefully I placed them at the correct spots! > > Changes in v2: > - checkpatch & style. (Jeff) > - Improve comments. > - Link to v1: https://lore.kernel.org/linux-wireless/[email protected]/ > --- > drivers/net/wireless/ath/ath10k/htt.h | 4 +++ > drivers/net/wireless/ath/ath10k/htt_rx.c | 50 +++++++++++++++++++++++++------- > 2 files changed, 43 insertions(+), 11 deletions(-) > > diff --git a/drivers/net/wireless/ath/ath10k/htt.h b/drivers/net/wireless/ath/ath10k/htt.h > index 25c6b2e2f81c8..07c3dc088f07b 100644 > --- a/drivers/net/wireless/ath/ath10k/htt.h > +++ b/drivers/net/wireless/ath/ath10k/htt.h > @@ -1924,16 +1924,20 @@ struct ath10k_htt { > > bool tx_mem_allocated; > const struct ath10k_htt_tx_ops *tx_ops; > const struct ath10k_htt_rx_ops *rx_ops; > bool disable_tx_comp; > bool bundle_tx; > struct sk_buff_head tx_req_head; > struct sk_buff_head tx_complete_head; > + > + u8 rx_in_ord_split_tid; > + u16 rx_in_ord_split_peer_id; > + struct sk_buff_head rx_in_ord_split; > }; > > struct ath10k_htt_tx_ops { > int (*htt_send_rx_ring_cfg)(struct ath10k_htt *htt); > int (*htt_send_frag_desc_bank_cfg)(struct ath10k_htt *htt); > int (*htt_alloc_frag_desc)(struct ath10k_htt *htt); > void (*htt_free_frag_desc)(struct ath10k_htt *htt); > int (*htt_tx)(struct ath10k_htt *htt, enum ath10k_hw_txrx_mode txmode, > diff --git a/drivers/net/wireless/ath/ath10k/htt_rx.c b/drivers/net/wireless/ath/ath10k/htt_rx.c > index ab2d373b4750d..732bd3a2f9992 100644 > --- a/drivers/net/wireless/ath/ath10k/htt_rx.c > +++ b/drivers/net/wireless/ath/ath10k/htt_rx.c > @@ -291,16 +291,18 @@ void ath10k_htt_rx_free(struct ath10k_htt *htt) > return; > > timer_delete_sync(&htt->rx_ring.refill_retry_timer); > > skb_queue_purge(&htt->rx_msdus_q); > skb_queue_purge(&htt->rx_in_ord_compl_q); > skb_queue_purge(&htt->tx_fetch_ind_q); > > + skb_queue_purge(&htt->rx_in_ord_split); > + > spin_lock_bh(&htt->rx_ring.lock); > ath10k_htt_rx_ring_free(htt); > spin_unlock_bh(&htt->rx_ring.lock); > > dma_free_coherent(htt->ar->dev, > ath10k_htt_get_rx_ring_size(htt), > ath10k_htt_get_vaddr_ring(htt), > htt->rx_ring.base_paddr); > @@ -841,16 +843,18 @@ int ath10k_htt_rx_alloc(struct ath10k_htt *htt) > htt->rx_ring.sw_rd_idx.msdu_payld = 0; > hash_init(htt->rx_ring.skb_table); > > skb_queue_head_init(&htt->rx_msdus_q); > skb_queue_head_init(&htt->rx_in_ord_compl_q); > skb_queue_head_init(&htt->tx_fetch_ind_q); > atomic_set(&htt->num_mpdus_ready, 0); > > + skb_queue_head_init(&htt->rx_in_ord_split); > + > ath10k_dbg(ar, ATH10K_DBG_BOOT, "htt rx ring size %d fill_level %d\n", > htt->rx_ring.size, htt->rx_ring.fill_level); > return 0; > > err_dma_idx: > dma_free_coherent(htt->ar->dev, > ath10k_htt_get_rx_ring_size(htt), > vaddr_ring, > @@ -3156,16 +3160,20 @@ static int ath10k_htt_rx_extract_amsdu(struct ath10k_hw_params *hw, > struct rx_msdu_end_common *rxd_msdu_end_common; > > if (skb_queue_empty(list)) > return -ENOBUFS; > > if (WARN_ON(!skb_queue_empty(amsdu))) > return -EINVAL; > > + msdu = skb_peek(list); > + rxd = HTT_RX_BUF_TO_RX_DESC(hw, > + (void *)msdu->data - hw->rx_desc_ops->rx_desc_size); Dead rxd computation before the loop — VALID, MINOR Lines 3168–3170 compute rxd via skb_peek(), but the while loop at 3172 immediately dequeues the same SKB and unconditionally recomputes rxd at lines 3175–3177. The pre-loop assignment is never read. It's dead code. The original patch must have introduced this when restructuring (the pre-existing code likely used rxd from before the loop). It should be removed. > + > while ((msdu = __skb_dequeue(list))) { > __skb_queue_tail(amsdu, msdu); > > rxd = HTT_RX_BUF_TO_RX_DESC(hw, > (void *)msdu->data - > hw->rx_desc_ops->rx_desc_size); > > rxd_msdu_end_common = ath10k_htt_rx_desc_get_msdu_end(hw, rxd); > @@ -3257,17 +3265,16 @@ static void ath10k_htt_rx_h_rx_offload(struct ath10k *ar, > } > } > > static int ath10k_htt_rx_in_ord_ind(struct ath10k *ar, struct sk_buff *skb) > { > struct ath10k_htt *htt = &ar->htt; > struct htt_resp *resp = (void *)skb->data; > struct ieee80211_rx_status *status = &htt->rx_status; > - struct sk_buff_head list; > struct sk_buff_head amsdu; > u16 peer_id; > u16 msdu_count; > u8 vdev_id; > u8 tid; > bool offload; > bool frag; > int ret; > @@ -3292,64 +3299,85 @@ static int ath10k_htt_rx_in_ord_ind(struct ath10k *ar, struct sk_buff *skb) > "htt rx in ord vdev %i peer %i tid %i offload %i frag %i msdu count %i\n", > vdev_id, peer_id, tid, offload, frag, msdu_count); > > if (skb->len < msdu_count * sizeof(*resp->rx_in_ord_ind.msdu_descs32)) { > ath10k_warn(ar, "dropping invalid in order rx indication\n"); > return -EINVAL; > } > > - /* The event can deliver more than 1 A-MSDU. Each A-MSDU is later > - * extracted and processed. > + if (!skb_queue_empty(&htt->rx_in_ord_split)) { > + /* > + * It might still be possible to handle this case if there is > + * only one peer that splits at each given moment. We are > + * bailing out because we should have a test case for this > + * before trying to fix it. > + */ > + if (tid != htt->rx_in_ord_split_tid || > + peer_id != htt->rx_in_ord_split_peer_id || > + offload) { Dead offload disjunct — VALID, MINOR ath10k_htt_rx_h_rx_offload() at line 3346 drains rx_in_ord_split entirely (via __skb_dequeue loop). After it runs, the while (!skb_queue_empty(...)) loop at 3348 never executes. Therefore a split (-EAGAIN from ath10k_htt_rx_extract_amsdu) can never occur for offload frames. The || offload condition at line 3316 in the split-validation guard is unreachable. It should be removed for clarity. > + ath10k_warn(ar, "split amsdu did not resume immediately\n"); > + htt->rx_confused = true; > + ath10k_core_start_recovery(ar); > + return -EIO; > + } > + } > + > + /* > + * The event can deliver more than 1 A-MSDU or continue a previous one. > + * Each A-MSDU is later extracted and processed. > */ > - __skb_queue_head_init(&list); > if (ar->hw_params.target_64bit) > ret = ath10k_htt_rx_pop_paddr64_list(htt, &resp->rx_in_ord_ind, > - &list); > + &htt->rx_in_ord_split); > else > ret = ath10k_htt_rx_pop_paddr32_list(htt, &resp->rx_in_ord_ind, > - &list); > + &htt->rx_in_ord_split); > > if (ret < 0) { > ath10k_warn(ar, "failed to pop paddr list: %d\n", ret); > htt->rx_confused = true; > ath10k_core_start_recovery(ar); > return -EIO; rx_in_ord_split not purged on pop_paddr error — VALID BUG Lines 3335–3339: on pop_paddr error, htt->rx_confused = true and ath10k_core_start_recovery() are called, but __skb_queue_purge(&htt->rx_in_ord_split) is missing. If a split is already in progress (queue is non-empty) when pop_paddr fails on the continuation event, the existing SKBs are stranded. The default: path at lines 3371–3377 does purge the queue, making this an inconsistency. This will leak DMA-mapped SKBs. > } > > /* Offloaded frames are very different and need to be handled > * separately. > */ > if (offload) > - ath10k_htt_rx_h_rx_offload(ar, &list); > + ath10k_htt_rx_h_rx_offload(ar, &htt->rx_in_ord_split); > > - while (!skb_queue_empty(&list)) { > + while (!skb_queue_empty(&htt->rx_in_ord_split)) { > __skb_queue_head_init(&amsdu); > - ret = ath10k_htt_rx_extract_amsdu(&ar->hw_params, &list, &amsdu); > + ret = ath10k_htt_rx_extract_amsdu(&ar->hw_params, > + &htt->rx_in_ord_split, &amsdu); > switch (ret) { > case 0: > /* Note: The in-order indication may report interleaved > * frames from different PPDUs meaning reported rx rate > * to mac80211 isn't accurate/reliable. It's still > * better to report something than nothing though. This > * should still give an idea about rx rate to the user. > */ > ath10k_htt_rx_h_ppdu(ar, &amsdu, status, vdev_id); > ath10k_htt_rx_h_filter(ar, &amsdu, status, NULL); > ath10k_htt_rx_h_mpdu(ar, &amsdu, status, false, NULL, > NULL, peer_id, frag); > ath10k_htt_rx_h_enqueue(ar, &amsdu, status); > break; > case -EAGAIN: > - fallthrough; > + htt->rx_in_ord_split_tid = tid; > + htt->rx_in_ord_split_peer_id = peer_id; > + > + return -EIO; frag flag not saved across -EAGAIN — VALID BUG frag is a local extracted from the current event's header (line 3296). On -EAGAIN (line 3366–3370), only tid and peer_id are saved. When the continuation event arrives, ath10k_htt_rx_h_mpdu() at line 3362 is called with the frag from the new event, not the original split event. If the original said frag=1, the fragments will be misprocessed by mac80211 on continuation. This needs rx_in_ord_split_frag added alongside _tid and _peer_id. > default: > /* Should not happen. */ > ath10k_warn(ar, "failed to extract amsdu: %d\n", ret); > htt->rx_confused = true; > - __skb_queue_purge(&list); > + __skb_queue_purge(&htt->rx_in_ord_split); > ath10k_core_start_recovery(ar); > return -EIO; > } > } > return ret; > } > > static void ath10k_htt_rx_tx_fetch_resp_id_confirm(struct ath10k *ar, > > --- > base-commit: 0718283ab28bc3907e10b61a6b4be6fefa1cbb2f > change-id: 20260719-ath10k-a-msdu-c49334eb091d > > Best regards,