[PATCH 2/5] wifi: mt76: usb: support out-of-order RX URB completion
Lorenzo Bianconi
lorenzo at kernel.org
Sun Jun 14 23:35:54 PDT 2026
> From: Sean Wang <sean.wang at mediatek.com>
>
> Keep per-URB RX queue context and complete entries by their real queue
> position instead of assuming the completed URB is always at q->head.
>
> USB RX URBs can complete out of order, and advancing q->head too early
> can corrupt RX queue accounting and process buffers in the wrong order.
>
> Signed-off-by: Sean Wang <sean.wang at mediatek.com>
> ---
> drivers/net/wireless/mediatek/mt76/mt76.h | 5 ++
> drivers/net/wireless/mediatek/mt76/usb.c | 77 ++++++++++++++++-------
> 2 files changed, 61 insertions(+), 21 deletions(-)
>
> diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h
> index 122e77a5f2f4..81740aa7df71 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt76.h
> +++ b/drivers/net/wireless/mediatek/mt76/mt76.h
> @@ -655,6 +655,11 @@ struct mt76_mcu {
> wait_queue_head_t wait;
> };
>
> +struct mt76u_rx_entry {
> + struct mt76_queue_entry *e;
> + struct mt76_queue *q;
> +};
> +
> #define MT_TX_SG_MAX_SIZE 8
> #define MT_RX_SG_MAX_SIZE 4
> #define MT_NUM_TX_ENTRIES 256
> diff --git a/drivers/net/wireless/mediatek/mt76/usb.c b/drivers/net/wireless/mediatek/mt76/usb.c
> index ce68e1d0c786..cab36630c978 100644
> --- a/drivers/net/wireless/mediatek/mt76/usb.c
> +++ b/drivers/net/wireless/mediatek/mt76/usb.c
> @@ -397,11 +397,25 @@ mt76u_urb_alloc(struct mt76_dev *dev, struct mt76_queue_entry *e,
> return 0;
> }
>
> +static void mt76u_urb_free(struct urb *urb)
> +{
> + int i;
> +
> + for (i = 0; i < urb->num_sgs; i++)
> + mt76_put_page_pool_buf(sg_virt(&urb->sg[i]), false);
> +
> + if (urb->transfer_buffer)
> + mt76_put_page_pool_buf(urb->transfer_buffer, false);
> +
> + usb_free_urb(urb);
> +}
> +
> static int
> mt76u_rx_urb_alloc(struct mt76_dev *dev, struct mt76_queue *q,
> struct mt76_queue_entry *e)
> {
> enum mt76_rxq_id qid = q - &dev->q_rx[MT_RXQ_MAIN];
> + struct mt76u_rx_entry *rxe;
> int err, sg_size;
>
> sg_size = qid == MT_RXQ_MAIN ? MT_RX_SG_MAX_SIZE : 0;
> @@ -409,20 +423,25 @@ mt76u_rx_urb_alloc(struct mt76_dev *dev, struct mt76_queue *q,
> if (err)
> return err;
>
> - return mt76u_refill_rx(dev, q, e->urb, sg_size);
> -}
> -
> -static void mt76u_urb_free(struct urb *urb)
> -{
> - int i;
> + rxe = kzalloc_obj(*rxe, GFP_KERNEL);
I guess you can move it at the beginning of mt76u_rx_urb_alloc(), right?
> + if (!rxe) {
> + usb_free_urb(e->urb);
IIRC, if mt76u_rx_urb_alloc() fails, mt76u_free_rx_queue() will run so we do
not need to run usb_free_urb() manually here, right?
> + e->urb = NULL;
> + return -ENOMEM;
> + }
>
> - for (i = 0; i < urb->num_sgs; i++)
> - mt76_put_page_pool_buf(sg_virt(&urb->sg[i]), false);
> + rxe->e = e;
> + rxe->q = q;
> + e->urb->context = rxe;
>
> - if (urb->transfer_buffer)
> - mt76_put_page_pool_buf(urb->transfer_buffer, false);
> + err = mt76u_refill_rx(dev, q, e->urb, sg_size);
> + if (err) {
> + kfree(rxe);
> + mt76u_urb_free(e->urb);
> + e->urb = NULL;
> + }
>
> - usb_free_urb(urb);
> + return err;
> }
>
> static void
> @@ -566,8 +585,12 @@ mt76u_process_rx_entry(struct mt76_dev *dev, struct urb *urb,
> static void mt76u_complete_rx(struct urb *urb)
> {
> struct mt76_dev *dev = dev_get_drvdata(&urb->dev->dev);
> - struct mt76_queue *q = urb->context;
> + struct mt76u_rx_entry *rxe = urb->context;
> + struct mt76_queue_entry *e = rxe->e;
> + unsigned int idx, pending, pos;
> + struct mt76_queue *q = rxe->q;
> unsigned long flags;
> + bool wake = false;
>
> trace_rx_urb(dev, urb);
>
> @@ -586,18 +609,28 @@ static void mt76u_complete_rx(struct urb *urb)
> }
>
> spin_lock_irqsave(&q->lock, flags);
> - if (WARN_ONCE(q->entry[q->head].urb != urb, "rx urb mismatch"))
> + idx = e - q->entry;
> + pending = q->ndesc - q->queued;
> + pos = (idx + q->ndesc - q->head) % q->ndesc;
> + if (WARN_ONCE(idx >= q->ndesc || pos >= pending, "rx urb mismatch"))
can idx be >= of q->ndesc?
> goto out;
>
> - q->head = (q->head + 1) % q->ndesc;
> - q->queued++;
> -
> - if (q == &dev->q_rx[MT_RXQ_MAIN])
> - napi_schedule(&dev->napi[MT_RXQ_MAIN]);
> - else
> - mt76_worker_schedule(&dev->usb.rx_worker);
> + e->done = true;
> + while (q->entry[q->head].done) {
> + q->entry[q->head].done = false;
> + q->head = (q->head + 1) % q->ndesc;
> + q->queued++;
> + wake = true;
This seems a fix to me since theoretically we could have the same issue in
the current codebase, right?
Regards,
Lorenzo
> + }
> out:
> spin_unlock_irqrestore(&q->lock, flags);
> +
> + if (wake) {
> + if (q == &dev->q_rx[MT_RXQ_MAIN])
> + napi_schedule(&dev->napi[MT_RXQ_MAIN]);
> + else
> + mt76_worker_schedule(&dev->usb.rx_worker);
> + }
> }
>
> static int
> @@ -607,7 +640,7 @@ mt76u_submit_rx_buf(struct mt76_dev *dev, enum mt76_rxq_id qid,
> int ep = qid == MT_RXQ_MAIN ? MT_EP_IN_PKT_RX : MT_EP_IN_CMD_RESP;
>
> mt76u_fill_bulk_urb(dev, USB_DIR_IN, ep, urb,
> - mt76u_complete_rx, &dev->q_rx[qid]);
> + mt76u_complete_rx, urb->context);
> trace_submit_urb(dev, urb);
>
> return usb_submit_urb(urb, GFP_ATOMIC);
> @@ -678,6 +711,7 @@ mt76u_submit_rx_buffers(struct mt76_dev *dev, enum mt76_rxq_id qid)
>
> spin_lock_irqsave(&q->lock, flags);
> for (i = 0; i < q->ndesc; i++) {
> + q->entry[i].done = false;
> err = mt76u_submit_rx_buf(dev, qid, q->entry[i].urb);
> if (err < 0)
> break;
> @@ -733,6 +767,7 @@ mt76u_free_rx_queue(struct mt76_dev *dev, struct mt76_queue *q)
> if (!q->entry[i].urb)
> continue;
>
> + kfree(q->entry[i].urb->context);
> mt76u_urb_free(q->entry[i].urb);
> q->entry[i].urb = NULL;
> }
> --
> 2.43.0
>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 228 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-mediatek/attachments/20260615/183a19b5/attachment.sig>
More information about the Linux-mediatek
mailing list