* [PATCH 1/5] wifi: mt76: usb: size RX page-pool pages from queue buffer
2026-06-13 22:46 [PATCH 0/5] wifi: mt76: add USB RX aggregation support Sean Wang
@ 2026-06-13 22:46 ` Sean Wang
2026-06-13 22:46 ` [PATCH 2/5] wifi: mt76: usb: support out-of-order RX URB completion Sean Wang
` (3 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Sean Wang @ 2026-06-13 22:46 UTC (permalink / raw)
To: Felix Fietkau, Lorenzo Bianconi; +Cc: linux-wireless, linux-mediatek, Sean Wang
From: Sean Wang <sean.wang@mediatek.com>
Use the RX queue buffer size to select the page-pool allocation order.
This lets USB devices use larger RX buffers without silently allocating
undersized order-0 pages.
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
---
drivers/net/wireless/mediatek/mt76/mac80211.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c
index 13c4e8abe281..6ff1eada6d09 100644
--- a/drivers/net/wireless/mediatek/mt76/mac80211.c
+++ b/drivers/net/wireless/mediatek/mt76/mac80211.c
@@ -628,6 +628,9 @@ int mt76_create_page_pool(struct mt76_dev *dev, struct mt76_queue *q)
if (!is_qrx && !mt76_queue_is_wed_tx_free(q))
return 0;
+ if (q->buf_size > PAGE_SIZE)
+ pp_params.order = get_order(q->buf_size);
+
switch (idx) {
case MT_RXQ_MAIN:
case MT_RXQ_BAND1:
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH 2/5] wifi: mt76: usb: support out-of-order RX URB completion
2026-06-13 22:46 [PATCH 0/5] wifi: mt76: add USB RX aggregation support Sean Wang
2026-06-13 22:46 ` [PATCH 1/5] wifi: mt76: usb: size RX page-pool pages from queue buffer Sean Wang
@ 2026-06-13 22:46 ` Sean Wang
2026-06-13 22:46 ` [PATCH 3/5] wifi: mt76: usb: add optional RX aggregation support Sean Wang
` (2 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Sean Wang @ 2026-06-13 22:46 UTC (permalink / raw)
To: Felix Fietkau, Lorenzo Bianconi; +Cc: linux-wireless, linux-mediatek, Sean Wang
From: Sean Wang <sean.wang@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@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);
+ if (!rxe) {
+ usb_free_urb(e->urb);
+ 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"))
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;
+ }
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
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH 3/5] wifi: mt76: usb: add optional RX aggregation support
2026-06-13 22:46 [PATCH 0/5] wifi: mt76: add USB RX aggregation support Sean Wang
2026-06-13 22:46 ` [PATCH 1/5] wifi: mt76: usb: size RX page-pool pages from queue buffer Sean Wang
2026-06-13 22:46 ` [PATCH 2/5] wifi: mt76: usb: support out-of-order RX URB completion Sean Wang
@ 2026-06-13 22:46 ` Sean Wang
2026-06-13 22:46 ` [PATCH 4/5] wifi: mt76: usb: add debugfs aggregation stats Sean Wang
2026-06-13 22:46 ` [PATCH 5/5] wifi: mt76: mt7927u: enable USB RX aggregation Sean Wang
4 siblings, 0 replies; 6+ messages in thread
From: Sean Wang @ 2026-06-13 22:46 UTC (permalink / raw)
To: Felix Fietkau, Lorenzo Bianconi; +Cc: linux-wireless, linux-mediatek, Sean Wang
From: Sean Wang <sean.wang@mediatek.com>
Add common USB RX aggregation support and let drivers opt in by programming
the UDMA RX aggregation limit and timeout.
RX aggregation allows the device to pack multiple RX packets into one USB
transfer, reducing URB completion rate, USB interrupt/IO overhead, and host
RX scheduling pressure. This is especially useful at high throughput, where
per-packet USB RX handling can become a CPU bottleneck.
Keep it disabled by default so existing USB drivers keep the current RX
behavior unless they explicitly enable aggregation.
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
---
drivers/net/wireless/mediatek/mt76/mt76.h | 21 ++-
.../net/wireless/mediatek/mt76/mt7925/usb.c | 12 ++
.../net/wireless/mediatek/mt76/mt792x_usb.c | 23 +++-
drivers/net/wireless/mediatek/mt76/usb.c | 124 +++++++++++++++++-
4 files changed, 169 insertions(+), 11 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h
index 81740aa7df71..125c97dc1f28 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76.h
@@ -680,6 +680,13 @@ struct mt76_usb {
void (*ctrl_timeout)(struct mt76_dev *dev, int err);
bool sg_en;
+ struct {
+ bool enable;
+ int align;
+ int padding;
+ int buf_size;
+ } rx_aggr;
+
struct mt76u_mcu {
u8 *data;
/* multiple reads */
@@ -1857,6 +1864,17 @@ mt76u_bulk_msg(struct mt76_dev *dev, void *data, int len, int *actual_len,
return usb_bulk_msg(udev, pipe, data, len, actual_len, timeout);
}
+static inline int
+mt76u_rx_aggr_buf_size(int max_mpdu, int aggr_limit, int aggr_pkt_limit,
+ int padding)
+{
+ int aggr_size;
+
+ aggr_size = min(aggr_limit, aggr_pkt_limit * (max_mpdu + padding));
+
+ return PAGE_ALIGN(max_mpdu + aggr_size);
+}
+
void mt76_ethtool_page_pool_stats(struct mt76_dev *dev, u64 *data, int *index);
void mt76_ethtool_worker(struct mt76_ethtool_worker_info *wi,
struct mt76_sta_stats *stats, bool eht);
@@ -1882,7 +1900,8 @@ void mt76u_stop_tx(struct mt76_dev *dev);
void mt76u_stop_rx(struct mt76_dev *dev);
int mt76u_resume_rx(struct mt76_dev *dev);
void mt76u_queues_deinit(struct mt76_dev *dev);
-
+void mt76u_enable_rx_aggr(struct mt76_dev *dev, int align, int padding,
+ int buf_size);
int mt76s_init(struct mt76_dev *dev, struct sdio_func *func,
const struct mt76_bus_ops *bus_ops);
int mt76s_alloc_rx_queue(struct mt76_dev *dev, enum mt76_rxq_id qid);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/usb.c b/drivers/net/wireless/mediatek/mt76/mt7925/usb.c
index 49ad4fe9eb1b..a0bfe6f09ae4 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/usb.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/usb.c
@@ -3,12 +3,24 @@
#include <linux/kernel.h>
#include <linux/module.h>
+#include <linux/sizes.h>
#include <linux/usb.h>
#include "mt7925.h"
#include "mcu.h"
#include "mac.h"
+#define MT7927_USB_RX_AGGR_ALIGN 16
+#define MT7927_USB_RX_AGGR_PADDING 12
+#define MT7927_USB_RX_AGGR_LIMIT SZ_32K
+#define MT7927_USB_RX_AGGR_PKT_LIMIT 30
+#define MT7927_USB_RX_MAX_MPDU (13 * SZ_1K)
+#define MT7927_USB_RX_AGGR_BUF_SIZE \
+ mt76u_rx_aggr_buf_size(MT7927_USB_RX_MAX_MPDU, \
+ MT7927_USB_RX_AGGR_LIMIT, \
+ MT7927_USB_RX_AGGR_PKT_LIMIT, \
+ MT7927_USB_RX_AGGR_PADDING)
+
static const struct usb_device_id mt7925u_device_table[] = {
{ USB_DEVICE_AND_INTERFACE_INFO(0x0e8d, 0x6639, 0xff, 0xff, 0xff),
.driver_info = (kernel_ulong_t)MT7925_FIRMWARE_WM },
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_usb.c b/drivers/net/wireless/mediatek/mt76/mt792x_usb.c
index 6280bc4bf78d..769e828e9449 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x_usb.c
+++ b/drivers/net/wireless/mediatek/mt76/mt792x_usb.c
@@ -13,6 +13,9 @@
#define MT792X_USB_TX_TIMEOUT_LIMIT 50000
#define MT792X_USB_UDMA_IDLE_TIMEOUT 1000
+#define MT792X_USB_RX_AGG_LIMIT 32
+#define MT792X_USB_RX_AGG_TIMEOUT 100
+#define MT792X_USB_RX_AGG_PKT_LIMIT 30
static int mt792xu_read32(struct mt76_dev *dev, u32 addr, void *buf)
{
@@ -403,9 +406,23 @@ int mt792xu_dma_init(struct mt792x_dev *dev, bool resume)
FIELD_PREP(MT_WL_TX_TMOUT_LMT,
MT792X_USB_TX_TIMEOUT_LIMIT));
mt76_set(dev, MT_UDMA_WLCFG_0, MT_WL_TX_TMOUT_FUNC_EN);
- mt76_clear(dev, MT_UDMA_WLCFG_0,
- MT_WL_RX_AGG_TO | MT_WL_RX_AGG_LMT);
- mt76_clear(dev, MT_UDMA_WLCFG_1, MT_WL_RX_AGG_PKT_LMT);
+
+ if (dev->mt76.usb.rx_aggr.enable) {
+ mt76_set(dev, MT_UDMA_WLCFG_0, MT_WL_RX_AGG_EN);
+ mt76_rmw(dev, MT_UDMA_WLCFG_0,
+ MT_WL_RX_AGG_TO | MT_WL_RX_AGG_LMT,
+ FIELD_PREP(MT_WL_RX_AGG_TO,
+ MT792X_USB_RX_AGG_TIMEOUT) |
+ FIELD_PREP(MT_WL_RX_AGG_LMT,
+ MT792X_USB_RX_AGG_LIMIT));
+ mt76_rmw(dev, MT_UDMA_WLCFG_1, MT_WL_RX_AGG_PKT_LMT,
+ FIELD_PREP(MT_WL_RX_AGG_PKT_LMT,
+ MT792X_USB_RX_AGG_PKT_LIMIT));
+ } else {
+ mt76_clear(dev, MT_UDMA_WLCFG_0, MT_WL_RX_AGG_EN |
+ MT_WL_RX_AGG_TO | MT_WL_RX_AGG_LMT);
+ mt76_clear(dev, MT_UDMA_WLCFG_1, MT_WL_RX_AGG_PKT_LMT);
+ }
if (resume)
return 0;
diff --git a/drivers/net/wireless/mediatek/mt76/usb.c b/drivers/net/wireless/mediatek/mt76/usb.c
index cab36630c978..cbdd663fbb25 100644
--- a/drivers/net/wireless/mediatek/mt76/usb.c
+++ b/drivers/net/wireless/mediatek/mt76/usb.c
@@ -371,6 +371,14 @@ mt76u_refill_rx(struct mt76_dev *dev, struct mt76_queue *q,
return mt76u_fill_rx_sg(dev, q, urb, nsgs);
urb->transfer_buffer_length = q->buf_size;
+ if (qid == MT_RXQ_MAIN && dev->usb.rx_aggr.enable) {
+ if (!urb->transfer_buffer)
+ urb->transfer_buffer =
+ mt76_get_page_pool_buf(q, &offset, q->buf_size);
+
+ return urb->transfer_buffer ? 0 : -ENOMEM;
+ }
+
urb->transfer_buffer = mt76_get_page_pool_buf(q, &offset, q->buf_size);
return urb->transfer_buffer ? 0 : -ENOMEM;
@@ -538,18 +546,113 @@ mt76u_build_rx_skb(struct mt76_dev *dev, void *data,
return skb;
}
+static struct sk_buff *
+mt76u_build_rx_skb_aggr(struct mt76_dev *dev, void *data, int data_len,
+ int buf_len)
+{
+ int head_room, drv_flags = dev->drv->drv_flags;
+ int len = min_t(int, data_len, MT_SKB_HEAD_LEN);
+ struct sk_buff *skb;
+
+ if (data_len <= 0)
+ return NULL;
+
+ head_room = drv_flags & MT_DRV_RX_DMA_HDR ? 0 : MT_DMA_HDR_LEN;
+ skb = alloc_skb(len, GFP_ATOMIC);
+ if (!skb)
+ return NULL;
+
+ data += head_room;
+ skb_put_data(skb, data, len);
+ if (data_len > len) {
+ struct page *page;
+
+ data += len;
+ page = virt_to_head_page(data);
+ skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags,
+ page, data - page_address(page),
+ data_len - len, buf_len);
+ get_page(page);
+ }
+
+ return skb;
+}
+
+static int mt76u_process_rx_agg_entry(struct mt76_dev *dev, struct urb *urb)
+{
+ int offset = 0, head_room, drv_flags = dev->drv->drv_flags;
+ int align = dev->usb.rx_aggr.align ?: 4;
+ int padding = dev->usb.rx_aggr.padding ?: 4;
+ u8 *data = urb->transfer_buffer;
+ int min_len;
+ int nframes = 0;
+
+ if (!test_bit(MT76_STATE_INITIALIZED, &dev->phy.state) ||
+ test_bit(MT76_REMOVED, &dev->phy.state))
+ return 0;
+
+ head_room = drv_flags & MT_DRV_RX_DMA_HDR ? 0 : MT_DMA_HDR_LEN;
+ min_len = head_room + MT_RX_RXWI_LEN;
+
+ while (urb->actual_length - offset >= min_len) {
+ struct sk_buff *skb;
+ int len, frame_len, agg_len;
+
+ len = mt76u_get_rx_entry_len(dev, data + offset,
+ urb->actual_length - offset);
+ if (len < 0) {
+ dev_warn_ratelimited(dev->dev,
+ "invalid USB RX aggregate at offset %d\n",
+ offset);
+ break;
+ }
+
+ frame_len = head_room + len;
+ if (frame_len > urb->actual_length - offset) {
+ dev_warn_ratelimited(dev->dev,
+ "truncated USB RX aggregate at offset %d\n",
+ offset);
+ break;
+ }
+
+ agg_len = ALIGN(frame_len, align) + padding;
+ if (dev->drv->rx_check &&
+ !dev->drv->rx_check(dev, data + offset + head_room, len))
+ goto next;
+
+ skb = mt76u_build_rx_skb_aggr(dev, data + offset, len,
+ agg_len);
+ if (skb) {
+ dev->drv->rx_skb(dev, MT_RXQ_MAIN, skb, NULL);
+ nframes++;
+ }
+
+next:
+ offset += agg_len;
+ }
+
+ mt76_put_page_pool_buf(urb->transfer_buffer, false);
+ urb->transfer_buffer = NULL;
+
+ return max(nframes, 1);
+}
+
static int
mt76u_process_rx_entry(struct mt76_dev *dev, struct urb *urb,
- int buf_size)
+ enum mt76_rxq_id qid, int buf_size)
{
u8 *data = urb->num_sgs ? sg_virt(&urb->sg[0]) : urb->transfer_buffer;
int data_len = urb->num_sgs ? urb->sg[0].length : urb->actual_length;
int len, nsgs = 1, head_room, drv_flags = dev->drv->drv_flags;
struct sk_buff *skb;
- if (!test_bit(MT76_STATE_INITIALIZED, &dev->phy.state))
+ if (!test_bit(MT76_STATE_INITIALIZED, &dev->phy.state) ||
+ test_bit(MT76_REMOVED, &dev->phy.state))
return 0;
+ if (qid == MT_RXQ_MAIN && dev->usb.rx_aggr.enable && !urb->num_sgs)
+ return mt76u_process_rx_agg_entry(dev, urb);
+
len = mt76u_get_rx_entry_len(dev, data, urb->actual_length);
if (len < 0)
return 0;
@@ -594,6 +697,9 @@ static void mt76u_complete_rx(struct urb *urb)
trace_rx_urb(dev, urb);
+ if (test_bit(MT76_REMOVED, &dev->phy.state))
+ return;
+
switch (urb->status) {
case -ECONNRESET:
case -ESHUTDOWN:
@@ -658,12 +764,14 @@ mt76u_process_rx_queue(struct mt76_dev *dev, struct mt76_queue *q)
if (!urb)
break;
- count = mt76u_process_rx_entry(dev, urb, q->buf_size);
+ count = mt76u_process_rx_entry(dev, urb, qid, q->buf_size);
if (count > 0) {
err = mt76u_refill_rx(dev, q, urb, count);
if (err < 0)
break;
}
+ if (test_bit(MT76_REMOVED, &dev->phy.state))
+ break;
mt76u_submit_rx_buf(dev, qid, urb);
}
}
@@ -729,10 +837,6 @@ mt76u_alloc_rx_queue(struct mt76_dev *dev, enum mt76_rxq_id qid)
struct mt76_queue *q = &dev->q_rx[qid];
int i, err;
- err = mt76_create_page_pool(dev, q);
- if (err)
- return err;
-
spin_lock_init(&q->lock);
q->entry = devm_kcalloc(dev->dev,
MT_NUM_RX_ENTRIES, sizeof(*q->entry),
@@ -742,6 +846,12 @@ mt76u_alloc_rx_queue(struct mt76_dev *dev, enum mt76_rxq_id qid)
q->ndesc = MT_NUM_RX_ENTRIES;
q->buf_size = PAGE_SIZE;
+ if (qid == MT_RXQ_MAIN && dev->usb.rx_aggr.enable)
+ q->buf_size = dev->usb.rx_aggr.buf_size ?: PAGE_SIZE;
+
+ err = mt76_create_page_pool(dev, q);
+ if (err)
+ return err;
for (i = 0; i < q->ndesc; i++) {
err = mt76u_rx_urb_alloc(dev, q, &q->entry[i]);
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH 4/5] wifi: mt76: usb: add debugfs aggregation stats
2026-06-13 22:46 [PATCH 0/5] wifi: mt76: add USB RX aggregation support Sean Wang
` (2 preceding siblings ...)
2026-06-13 22:46 ` [PATCH 3/5] wifi: mt76: usb: add optional RX aggregation support Sean Wang
@ 2026-06-13 22:46 ` Sean Wang
2026-06-13 22:46 ` [PATCH 5/5] wifi: mt76: mt7927u: enable USB RX aggregation Sean Wang
4 siblings, 0 replies; 6+ messages in thread
From: Sean Wang @ 2026-06-13 22:46 UTC (permalink / raw)
To: Felix Fietkau, Lorenzo Bianconi; +Cc: linux-wireless, linux-mediatek, Sean Wang
From: Sean Wang <sean.wang@mediatek.com>
Add USB debugfs counters for RX/TX URBs, packets, bytes and recent RX
aggregation frame counts.
These stats make it easier to verify whether USB RX aggregation are working
as expected, and to debug throughput issues without adding
temporary driver logs.
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
---
drivers/net/wireless/mediatek/mt76/debugfs.c | 35 ++++++++++++++++++
drivers/net/wireless/mediatek/mt76/mt76.h | 12 ++++++
drivers/net/wireless/mediatek/mt76/usb.c | 39 ++++++++++++++++++++
3 files changed, 86 insertions(+)
diff --git a/drivers/net/wireless/mediatek/mt76/debugfs.c b/drivers/net/wireless/mediatek/mt76/debugfs.c
index a5ac6ca86735..b3f1bc3cd69b 100644
--- a/drivers/net/wireless/mediatek/mt76/debugfs.c
+++ b/drivers/net/wireless/mediatek/mt76/debugfs.c
@@ -88,6 +88,38 @@ static int mt76_rx_queues_read(struct seq_file *s, void *data)
return 0;
}
+static int mt76_usb_stats_read(struct seq_file *s, void *data)
+{
+ struct mt76_dev *dev = dev_get_drvdata(s->private);
+ struct mt76_usb *usb = &dev->usb;
+ u64 seq;
+ int i, n;
+
+ seq_printf(s, "rx_aggr\t%d\n", usb->rx_aggr.enable);
+ seq_printf(s, "sg_en\t%d\n", usb->sg_en);
+ seq_printf(s, "tx_urbs\t%lld\n", atomic64_read(&usb->stats.tx_urbs));
+ seq_printf(s, "tx_packets\t%lld\n",
+ atomic64_read(&usb->stats.tx_packets));
+ seq_printf(s, "tx_bytes\t%lld\n", atomic64_read(&usb->stats.tx_bytes));
+ seq_printf(s, "rx_urbs\t%lld\n", atomic64_read(&usb->stats.rx_urbs));
+ seq_printf(s, "rx_packets\t%lld\n",
+ atomic64_read(&usb->stats.rx_packets));
+ seq_printf(s, "rx_bytes\t%lld\n", atomic64_read(&usb->stats.rx_bytes));
+
+ seq = atomic64_read(&usb->stats.rx_aggr_seq);
+ seq_puts(s, "rx_aggr_nframes");
+ n = min_t(u64, seq, MT_USB_AGGR_STATS_LEN);
+ for (i = 0; i < n; i++) {
+ u64 idx = seq - n + i;
+ u64 slot = idx % MT_USB_AGGR_STATS_LEN;
+
+ seq_printf(s, " %u", READ_ONCE(usb->stats.rx_aggr_nframes[slot]));
+ }
+ seq_puts(s, "\n");
+
+ return 0;
+}
+
void mt76_seq_puts_array(struct seq_file *file, const char *str,
s8 *val, int len)
{
@@ -120,6 +152,9 @@ mt76_register_debugfs_fops(struct mt76_phy *phy,
debugfs_create_blob("otp", 0400, dir, &dev->otp);
debugfs_create_devm_seqfile(dev->dev, "rx-queues", dir,
mt76_rx_queues_read);
+ if (mt76_is_usb(dev))
+ debugfs_create_devm_seqfile(dev->dev, "usb-stats", dir,
+ mt76_usb_stats_read);
return dir;
}
diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h
index 125c97dc1f28..c11a463ae092 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76.h
@@ -664,6 +664,7 @@ struct mt76u_rx_entry {
#define MT_RX_SG_MAX_SIZE 4
#define MT_NUM_TX_ENTRIES 256
#define MT_NUM_RX_ENTRIES 128
+#define MT_USB_AGGR_STATS_LEN 128
#define MCU_RESP_URB_SIZE 1024
struct mt76_usb {
struct mutex usb_ctrl_mtx;
@@ -687,6 +688,17 @@ struct mt76_usb {
int buf_size;
} rx_aggr;
+ struct {
+ atomic64_t tx_urbs;
+ atomic64_t tx_packets;
+ atomic64_t tx_bytes;
+ atomic64_t rx_urbs;
+ atomic64_t rx_packets;
+ atomic64_t rx_bytes;
+ atomic64_t rx_aggr_seq;
+ u8 rx_aggr_nframes[MT_USB_AGGR_STATS_LEN];
+ } stats;
+
struct mt76u_mcu {
u8 *data;
/* multiple reads */
diff --git a/drivers/net/wireless/mediatek/mt76/usb.c b/drivers/net/wireless/mediatek/mt76/usb.c
index cbdd663fbb25..10ad2b024985 100644
--- a/drivers/net/wireless/mediatek/mt76/usb.c
+++ b/drivers/net/wireless/mediatek/mt76/usb.c
@@ -15,6 +15,36 @@ static bool disable_usb_sg;
module_param_named(disable_usb_sg, disable_usb_sg, bool, 0644);
MODULE_PARM_DESC(disable_usb_sg, "Disable usb scatter-gather support");
+static void mt76u_tx_stats_add(struct mt76_dev *dev, struct urb *urb,
+ unsigned int packets)
+{
+ atomic64_inc(&dev->usb.stats.tx_urbs);
+ atomic64_add(packets, &dev->usb.stats.tx_packets);
+ atomic64_add(urb->transfer_buffer_length, &dev->usb.stats.tx_bytes);
+}
+
+static void mt76u_rx_urb_stats_add(struct mt76_dev *dev, struct urb *urb)
+{
+ atomic64_inc(&dev->usb.stats.rx_urbs);
+ atomic64_add(urb->actual_length, &dev->usb.stats.rx_bytes);
+}
+
+static void mt76u_rx_packet_stats_add(struct mt76_dev *dev,
+ unsigned int packets)
+{
+ atomic64_add(packets, &dev->usb.stats.rx_packets);
+}
+
+static void mt76u_rx_aggr_stats_add(struct mt76_dev *dev, unsigned int packets)
+{
+ u64 idx, slot;
+
+ idx = atomic64_inc_return(&dev->usb.stats.rx_aggr_seq) - 1;
+ slot = idx % MT_USB_AGGR_STATS_LEN;
+ WRITE_ONCE(dev->usb.stats.rx_aggr_nframes[slot],
+ min_t(unsigned int, packets, U8_MAX));
+}
+
int __mt76u_vendor_request(struct mt76_dev *dev, u8 req, u8 req_type,
u16 val, u16 offset, void *buf, size_t len)
{
@@ -634,6 +664,10 @@ static int mt76u_process_rx_agg_entry(struct mt76_dev *dev, struct urb *urb)
mt76_put_page_pool_buf(urb->transfer_buffer, false);
urb->transfer_buffer = NULL;
+ if (nframes)
+ mt76u_rx_packet_stats_add(dev, nframes);
+ mt76u_rx_aggr_stats_add(dev, nframes);
+
return max(nframes, 1);
}
@@ -681,6 +715,7 @@ mt76u_process_rx_entry(struct mt76_dev *dev, struct urb *urb,
skb_mark_for_recycle(skb);
dev->drv->rx_skb(dev, MT_RXQ_MAIN, skb, NULL);
+ mt76u_rx_packet_stats_add(dev, 1);
return nsgs;
}
@@ -714,6 +749,9 @@ static void mt76u_complete_rx(struct urb *urb)
break;
}
+ if (!urb->status)
+ mt76u_rx_urb_stats_add(dev, urb);
+
spin_lock_irqsave(&q->lock, flags);
idx = e - q->entry;
pending = q->ndesc - q->queued;
@@ -1083,6 +1121,7 @@ static void mt76u_tx_kick(struct mt76_dev *dev, struct mt76_queue *q)
err);
break;
}
+ mt76u_tx_stats_add(dev, urb, 1);
q->first = (q->first + 1) % q->ndesc;
}
}
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH 5/5] wifi: mt76: mt7927u: enable USB RX aggregation
2026-06-13 22:46 [PATCH 0/5] wifi: mt76: add USB RX aggregation support Sean Wang
` (3 preceding siblings ...)
2026-06-13 22:46 ` [PATCH 4/5] wifi: mt76: usb: add debugfs aggregation stats Sean Wang
@ 2026-06-13 22:46 ` Sean Wang
4 siblings, 0 replies; 6+ messages in thread
From: Sean Wang @ 2026-06-13 22:46 UTC (permalink / raw)
To: Felix Fietkau, Lorenzo Bianconi; +Cc: linux-wireless, linux-mediatek, Sean Wang
From: Sean Wang <sean.wang@mediatek.com>
Enable USB RX aggregation on MT7927u with vendor driver parameters for
alignment, padding and buffer size. According to the vendor driver, the
hardware should run RX aggregation with USB SG disabled.
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
---
drivers/net/wireless/mediatek/mt76/mt7925/usb.c | 6 +++++-
drivers/net/wireless/mediatek/mt76/usb.c | 11 +++++++++++
2 files changed, 16 insertions(+), 1 deletion(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/usb.c b/drivers/net/wireless/mediatek/mt76/mt7925/usb.c
index a0bfe6f09ae4..42d13bc6ebbc 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/usb.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/usb.c
@@ -5,7 +5,6 @@
#include <linux/module.h>
#include <linux/sizes.h>
#include <linux/usb.h>
-
#include "mt7925.h"
#include "mcu.h"
#include "mac.h"
@@ -235,6 +234,11 @@ static int mt7925u_probe(struct usb_interface *usb_intf,
mdev->rev = (0x7927 << 16) | (mdev->rev & 0xff);
}
+ if (is_mt7927(mdev))
+ mt76u_enable_rx_aggr(mdev, MT7927_USB_RX_AGGR_ALIGN,
+ MT7927_USB_RX_AGGR_PADDING,
+ MT7927_USB_RX_AGGR_BUF_SIZE);
+
if (mt76_get_field(dev, MT_CONN_ON_MISC, MT_TOP_MISC2_FW_N9_RDY)) {
ret = mt792xu_wfsys_reset(dev);
if (ret)
diff --git a/drivers/net/wireless/mediatek/mt76/usb.c b/drivers/net/wireless/mediatek/mt76/usb.c
index 10ad2b024985..f0df510904c5 100644
--- a/drivers/net/wireless/mediatek/mt76/usb.c
+++ b/drivers/net/wireless/mediatek/mt76/usb.c
@@ -1300,6 +1300,17 @@ static const struct mt76_queue_ops usb_queue_ops = {
.kick = mt76u_tx_kick,
};
+void mt76u_enable_rx_aggr(struct mt76_dev *dev, int align, int padding,
+ int buf_size)
+{
+ dev->usb.sg_en = false;
+ dev->usb.rx_aggr.enable = true;
+ dev->usb.rx_aggr.align = align;
+ dev->usb.rx_aggr.padding = padding;
+ dev->usb.rx_aggr.buf_size = buf_size;
+}
+EXPORT_SYMBOL_GPL(mt76u_enable_rx_aggr);
+
int __mt76u_init(struct mt76_dev *dev, struct usb_interface *intf,
struct mt76_bus_ops *ops)
{
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread