mirror of
https://github.com/torvalds/linux.git
synced 2026-10-08 19:46:02 +02:00
wifi: mt76: mt76u: use a threaded NAPI for the RX path
The USB RX path delivers frames to the stack via mt76_rx_complete() with a NULL napi pointer, taking the netif_receive_skb_list() path, so it never benefits from GRO -- unlike the DMA-based mt76 drivers, which pass a real napi and use napi_gro_receive(). For bulk TCP traffic this is costly, as every segment traverses the stack individually. Service the MT_RXQ_MAIN queue from a threaded NAPI, reusing mt76_dev's existing napi_dev and napi[] rather than adding new fields. The URB completion handler schedules the napi; its poll drains the URBs, builds the skbs, resubmits and delivers them through napi_gro_receive(). The MCU queue stays on the existing RX worker. This enables GRO and moves RX processing into its own kernel thread, parallelising the datapath. On mt7921u at HE-MCS 11 (2x2, 80 MHz; fast.com, multiple streams) this averages ~588 Mbit/s, versus ~424 Mbit/s when the same napi is instead driven manually from the RX worker, and ~380 Mbit/s for the unmodified driver. Suggested-by: Lorenzo Bianconi <lorenzo@kernel.org> Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Filip Bakreski <phial@phiality.com> Acked-by: Lorenzo Bianconi <lorenzo@kernel.org> Link: https://patch.msgid.link/20260609105301.196302-1-phial@phiality.com Signed-off-by: Felix Fietkau <nbd@nbd.name>
This commit is contained in:
parent
a92cd5dd79
commit
5323d3e50c
|
|
@ -580,7 +580,11 @@ static void mt76u_complete_rx(struct urb *urb)
|
|||
|
||||
q->head = (q->head + 1) % q->ndesc;
|
||||
q->queued++;
|
||||
mt76_worker_schedule(&dev->usb.rx_worker);
|
||||
|
||||
if (q == &dev->q_rx[MT_RXQ_MAIN])
|
||||
napi_schedule(&dev->napi[MT_RXQ_MAIN]);
|
||||
else
|
||||
mt76_worker_schedule(&dev->usb.rx_worker);
|
||||
out:
|
||||
spin_unlock_irqrestore(&q->lock, flags);
|
||||
}
|
||||
|
|
@ -618,11 +622,23 @@ mt76u_process_rx_queue(struct mt76_dev *dev, struct mt76_queue *q)
|
|||
}
|
||||
mt76u_submit_rx_buf(dev, qid, urb);
|
||||
}
|
||||
if (qid == MT_RXQ_MAIN) {
|
||||
local_bh_disable();
|
||||
mt76_rx_poll_complete(dev, MT_RXQ_MAIN, NULL);
|
||||
local_bh_enable();
|
||||
}
|
||||
}
|
||||
|
||||
/* Threaded NAPI poll for the MAIN RX queue: drain URBs, build skbs, resubmit,
|
||||
* then deliver through napi_gro_receive() and let napi_complete() flush GRO.
|
||||
*/
|
||||
static int mt76u_napi_poll(struct napi_struct *napi, int budget)
|
||||
{
|
||||
struct mt76_dev *dev = mt76_priv(napi->dev);
|
||||
|
||||
rcu_read_lock();
|
||||
mt76u_process_rx_queue(dev, &dev->q_rx[MT_RXQ_MAIN]);
|
||||
mt76_rx_poll_complete(dev, MT_RXQ_MAIN, napi);
|
||||
rcu_read_unlock();
|
||||
|
||||
napi_complete(napi);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mt76u_rx_worker(struct mt76_worker *w)
|
||||
|
|
@ -632,8 +648,13 @@ static void mt76u_rx_worker(struct mt76_worker *w)
|
|||
int i;
|
||||
|
||||
rcu_read_lock();
|
||||
mt76_for_each_q_rx(dev, i)
|
||||
mt76_for_each_q_rx(dev, i) {
|
||||
/* MT_RXQ_MAIN is serviced by the threaded NAPI poll */
|
||||
if (i == MT_RXQ_MAIN)
|
||||
continue;
|
||||
|
||||
mt76u_process_rx_queue(dev, &dev->q_rx[i]);
|
||||
}
|
||||
rcu_read_unlock();
|
||||
}
|
||||
|
||||
|
|
@ -731,6 +752,13 @@ void mt76u_stop_rx(struct mt76_dev *dev)
|
|||
for (j = 0; j < q->ndesc; j++)
|
||||
usb_poison_urb(q->entry[j].urb);
|
||||
}
|
||||
|
||||
/* The MAIN queue napi stays enabled for the device lifetime. The URBs
|
||||
* are now poisoned, so mt76u_complete_rx() can no longer reschedule it;
|
||||
* just drain any in-flight poll before the caller frees or resets.
|
||||
*/
|
||||
if (dev->napi_dev)
|
||||
napi_synchronize(&dev->napi[MT_RXQ_MAIN]);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mt76u_stop_rx);
|
||||
|
||||
|
|
@ -1051,6 +1079,13 @@ void mt76u_queues_deinit(struct mt76_dev *dev)
|
|||
mt76u_stop_rx(dev);
|
||||
mt76u_stop_tx(dev);
|
||||
|
||||
if (dev->napi_dev) {
|
||||
napi_disable(&dev->napi[MT_RXQ_MAIN]);
|
||||
netif_napi_del(&dev->napi[MT_RXQ_MAIN]);
|
||||
free_netdev(dev->napi_dev);
|
||||
dev->napi_dev = NULL;
|
||||
}
|
||||
|
||||
mt76u_free_rx(dev);
|
||||
mt76u_free_tx(dev);
|
||||
}
|
||||
|
|
@ -1078,6 +1113,7 @@ int __mt76u_init(struct mt76_dev *dev, struct usb_interface *intf,
|
|||
{
|
||||
struct usb_device *udev = interface_to_usbdev(intf);
|
||||
struct mt76_usb *usb = &dev->usb;
|
||||
struct mt76_dev **priv;
|
||||
int err;
|
||||
|
||||
INIT_WORK(&usb->stat_work, mt76u_tx_status_data);
|
||||
|
|
@ -1115,6 +1151,20 @@ int __mt76u_init(struct mt76_dev *dev, struct usb_interface *intf,
|
|||
sched_set_fifo_low(usb->rx_worker.task);
|
||||
sched_set_fifo_low(usb->status_worker.task);
|
||||
|
||||
/* threaded NAPI on a dummy netdev (reusing mt76_dev's napi_dev/napi[])
|
||||
* services the MAIN RX queue and gives the RX path GRO
|
||||
*/
|
||||
dev->napi_dev = alloc_netdev_dummy(sizeof(struct mt76_dev *));
|
||||
if (!dev->napi_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
priv = netdev_priv(dev->napi_dev);
|
||||
*priv = dev;
|
||||
strscpy(dev->napi_dev->name, "mt76u-rx", sizeof(dev->napi_dev->name));
|
||||
dev->napi_dev->threaded = 1;
|
||||
netif_napi_add(dev->napi_dev, &dev->napi[MT_RXQ_MAIN], mt76u_napi_poll);
|
||||
napi_enable(&dev->napi[MT_RXQ_MAIN]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__mt76u_init);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user