* [PATCH 0/3] Fix TX performance degradation when IOMMU is enabled
@ 2026-07-09 7:55 Eason Lai
2026-07-09 7:55 ` [PATCH 1/3] wifi: mt76: Separate skb and page_pool_buf pointers in mt76_txwi_cache Eason Lai
` (2 more replies)
0 siblings, 3 replies; 6+ messages in thread
From: Eason Lai @ 2026-07-09 7:55 UTC (permalink / raw)
To: nbd, lorenzo
Cc: linux-wireless, linux-mediatek, Yf.Luo, kun.wu, deren.wu,
sean.wang, quan.zhou, ryder.lee, leon.yen, litien.chang, jb.tsai,
eason.lai, Eason Lai
From: Eason Lai <Eason.Lai@mediatek.com>
This series adds a TX preallocated buffer mechanism to fix the TX
performance degradation observed after enabling IOMMU.
Currently this feature is only enabled on MT8196, where it has been
validated.
Eason Lai (3):
wifi: mt76: Separate skb and page_pool_buf pointers in mt76_txwi_cache
wifi: mt76: mt792x: Add TX page pool support for IOMMU-enabled systems
wifi: mt76: mt792x: Restrict TX page pool to MT8196 platform
drivers/net/wireless/mediatek/mt76/dma.c | 55 ++-
drivers/net/wireless/mediatek/mt76/mt76.h | 15 +-
.../wireless/mediatek/mt76/mt76_connac_mac.c | 6 +
.../net/wireless/mediatek/mt76/mt7925/pci.c | 4 +-
drivers/net/wireless/mediatek/mt76/mt792x.h | 2 +
.../net/wireless/mediatek/mt76/mt792x_dma.c | 330 ++++++++++++++++++
.../net/wireless/mediatek/mt76/mt7996/mac.c | 10 +-
drivers/net/wireless/mediatek/mt76/tx.c | 2 +-
drivers/net/wireless/mediatek/mt76/wed.c | 6 +-
9 files changed, 398 insertions(+), 32 deletions(-)
--
2.45.2
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH 1/3] wifi: mt76: Separate skb and page_pool_buf pointers in mt76_txwi_cache
2026-07-09 7:55 [PATCH 0/3] Fix TX performance degradation when IOMMU is enabled Eason Lai
@ 2026-07-09 7:55 ` Eason Lai
2026-07-09 8:19 ` Lorenzo Bianconi
2026-07-09 7:55 ` [PATCH 2/3] wifi: mt76: mt792x: Add TX page pool support for IOMMU-enabled systems Eason Lai
2026-07-09 7:55 ` [PATCH 3/3] wifi: mt76: mt792x: Restrict TX page pool to MT8196 platform Eason Lai
2 siblings, 1 reply; 6+ messages in thread
From: Eason Lai @ 2026-07-09 7:55 UTC (permalink / raw)
To: nbd, lorenzo
Cc: linux-wireless, linux-mediatek, Yf.Luo, kun.wu, deren.wu,
sean.wang, quan.zhou, ryder.lee, leon.yen, litien.chang, jb.tsai,
eason.lai, Eason Lai
From: Eason Lai <Eason.Lai@mediatek.com>
Refactor mt76_txwi_cache structure to use separate skb and page_pool_buf
fields instead of a union with ptr. This improves type safety and makes
the code more explicit about which pointer type is being used in
different contexts.
Also add skip_unmap flag to tx_info.buf to handle cases where DMA
unmapping should be skipped.
Signed-off-by: Eason Lai <Eason.Lai@mediatek.com>
---
drivers/net/wireless/mediatek/mt76/dma.c | 29 ++++++++++++-------
drivers/net/wireless/mediatek/mt76/mt76.h | 4 +--
.../net/wireless/mediatek/mt76/mt7996/mac.c | 10 +++----
drivers/net/wireless/mediatek/mt76/tx.c | 2 +-
drivers/net/wireless/mediatek/mt76/wed.c | 6 ++--
5 files changed, 28 insertions(+), 23 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/dma.c b/drivers/net/wireless/mediatek/mt76/dma.c
index f8c2fe5f2f58..2716278788bd 100644
--- a/drivers/net/wireless/mediatek/mt76/dma.c
+++ b/drivers/net/wireless/mediatek/mt76/dma.c
@@ -43,7 +43,8 @@ mt76_alloc_rxwi(struct mt76_dev *dev)
if (!t)
return NULL;
- t->ptr = NULL;
+ t->skb = NULL;
+ t->page_pool_buf = NULL;
return t;
}
@@ -84,8 +85,11 @@ mt76_get_txwi(struct mt76_dev *dev)
{
struct mt76_txwi_cache *t = __mt76_get_txwi(dev);
- if (t)
+ if (t) {
+ t->skb = NULL;
+ t->page_pool_buf = NULL;
return t;
+ }
return mt76_alloc_txwi(dev);
}
@@ -147,8 +151,8 @@ mt76_free_pending_rxwi(struct mt76_dev *dev)
local_bh_disable();
while ((t = __mt76_get_rxwi(dev)) != NULL) {
- if (t->ptr)
- mt76_put_page_pool_buf(t->ptr, false);
+ if (t->page_pool_buf)
+ mt76_put_page_pool_buf(t->page_pool_buf, false);
kfree(t);
}
local_bh_enable();
@@ -475,14 +479,14 @@ mt76_dma_get_rxdmad_c_buf(struct mt76_dev *dev, struct mt76_queue *q,
if (more)
*more = !FIELD_GET(RRO_RXDMAD_DATA1_LS_MASK, data1);
- buf = t->ptr;
+ buf = t->page_pool_buf;
ind_reason = FIELD_GET(RRO_RXDMAD_DATA2_IND_REASON_MASK, data2);
if (ind_reason == MT_DMA_WED_IND_REASON_REPEAT ||
ind_reason == MT_DMA_WED_IND_REASON_OLDPKT) {
mt76_put_page_pool_buf(buf, false);
buf = ERR_PTR(-EAGAIN);
}
- t->ptr = NULL;
+ t->page_pool_buf = NULL;
t->dma_addr = 0;
mt76_put_rxwi(dev, t);
@@ -529,9 +533,9 @@ mt76_dma_get_buf(struct mt76_dev *dev, struct mt76_queue *q, int idx,
SKB_WITH_OVERHEAD(q->buf_size),
page_pool_get_dma_dir(q->page_pool));
- buf = t->ptr;
+ buf = t->page_pool_buf;
t->dma_addr = 0;
- t->ptr = NULL;
+ t->page_pool_buf = NULL;
mt76_put_rxwi(dev, t);
if (drop)
@@ -694,6 +698,7 @@ mt76_dma_tx_queue_skb(struct mt76_phy *phy, struct mt76_queue *q,
goto unmap;
tx_info.buf[n].addr = addr;
+ tx_info.buf[n].skip_unmap = false;
tx_info.buf[n++].len = iter->len;
}
tx_info.nbuf = n;
@@ -718,9 +723,11 @@ mt76_dma_tx_queue_skb(struct mt76_phy *phy, struct mt76_queue *q,
tx_info.info, tx_info.skb, t);
unmap:
- for (n--; n > 0; n--)
- dma_unmap_single(dev->dma_dev, tx_info.buf[n].addr,
- tx_info.buf[n].len, DMA_TO_DEVICE);
+ for (n--; n > 0; n--) {
+ if (!tx_info.buf[n].skip_unmap)
+ dma_unmap_single(dev->dma_dev, tx_info.buf[n].addr,
+ tx_info.buf[n].len, DMA_TO_DEVICE);
+ }
free:
#ifdef CONFIG_NL80211_TESTMODE
diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h
index 527bef97e122..927c21536f4e 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76.h
@@ -445,10 +445,8 @@ struct mt76_txwi_cache {
struct list_head list;
dma_addr_t dma_addr;
- union {
struct sk_buff *skb;
- void *ptr;
- };
+ void *page_pool_buf;
u8 qid;
u8 phy_idx;
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
index e2a83da3a09c..924b0dc0ff1e 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
@@ -1775,12 +1775,12 @@ static void mt7996_rx_token_put(struct mt7996_dev *dev)
struct mt76_txwi_cache *t;
t = mt76_rx_token_release(&dev->mt76, i);
- if (!t || !t->ptr)
+ if (!t || !t->page_pool_buf)
continue;
- mt76_put_page_pool_buf(t->ptr, false);
+ mt76_put_page_pool_buf(t->page_pool_buf, false);
t->dma_addr = 0;
- t->ptr = NULL;
+ t->page_pool_buf = NULL;
mt76_put_rxwi(&dev->mt76, t);
}
@@ -1928,14 +1928,14 @@ void mt7996_rro_rx_process(struct mt76_dev *mdev, void *data)
goto next_page;
qid = t->qid;
- buf = t->ptr;
+ buf = t->page_pool_buf;
q = &mdev->q_rx[qid];
dma_sync_single_for_cpu(mdev->dma_dev, t->dma_addr,
SKB_WITH_OVERHEAD(q->buf_size),
page_pool_get_dma_dir(q->page_pool));
t->dma_addr = 0;
- t->ptr = NULL;
+ t->page_pool_buf = NULL;
mt76_put_rxwi(mdev, t);
if (!buf)
goto next_page;
diff --git a/drivers/net/wireless/mediatek/mt76/tx.c b/drivers/net/wireless/mediatek/mt76/tx.c
index 22f9690634c9..665156a7ea65 100644
--- a/drivers/net/wireless/mediatek/mt76/tx.c
+++ b/drivers/net/wireless/mediatek/mt76/tx.c
@@ -899,7 +899,7 @@ int mt76_rx_token_consume(struct mt76_dev *dev, void *ptr,
token = idr_alloc(&dev->rx_token, t, 0, dev->rx_token_size,
GFP_ATOMIC);
if (token >= 0) {
- t->ptr = ptr;
+ t->page_pool_buf = ptr;
t->dma_addr = phys;
}
spin_unlock_bh(&dev->rx_token_lock);
diff --git a/drivers/net/wireless/mediatek/mt76/wed.c b/drivers/net/wireless/mediatek/mt76/wed.c
index ed657d952de2..e1cf81d722b8 100644
--- a/drivers/net/wireless/mediatek/mt76/wed.c
+++ b/drivers/net/wireless/mediatek/mt76/wed.c
@@ -15,11 +15,11 @@ void mt76_wed_release_rx_buf(struct mtk_wed_device *wed)
struct mt76_txwi_cache *t;
t = mt76_rx_token_release(dev, i);
- if (!t || !t->ptr)
+ if (!t || !t->page_pool_buf)
continue;
- mt76_put_page_pool_buf(t->ptr, false);
- t->ptr = NULL;
+ mt76_put_page_pool_buf(t->page_pool_buf, false);
+ t->page_pool_buf = NULL;
mt76_put_rxwi(dev, t);
}
--
2.45.2
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH 2/3] wifi: mt76: mt792x: Add TX page pool support for IOMMU-enabled systems
2026-07-09 7:55 [PATCH 0/3] Fix TX performance degradation when IOMMU is enabled Eason Lai
2026-07-09 7:55 ` [PATCH 1/3] wifi: mt76: Separate skb and page_pool_buf pointers in mt76_txwi_cache Eason Lai
@ 2026-07-09 7:55 ` Eason Lai
2026-07-09 8:27 ` Lorenzo Bianconi
2026-07-09 7:55 ` [PATCH 3/3] wifi: mt76: mt792x: Restrict TX page pool to MT8196 platform Eason Lai
2 siblings, 1 reply; 6+ messages in thread
From: Eason Lai @ 2026-07-09 7:55 UTC (permalink / raw)
To: nbd, lorenzo
Cc: linux-wireless, linux-mediatek, Yf.Luo, kun.wu, deren.wu,
sean.wang, quan.zhou, ryder.lee, leon.yen, litien.chang, jb.tsai,
eason.lai, Eason Lai
From: Eason Lai <Eason.Lai@mediatek.com>
Add support for TX page pool allocation in mt792x devices when IOMMU is
enabled. This optimization reduces DMA mapping overhead by
pre-allocating and reusing page pool buffers for TX operations.
Key changes:
- Export DMA helper functions and ops for mt792x usage
- Add tx_prealloc_enabled flag to track page pool state
- Implement mt792x-specific DMA queue operations with TX page pool
- Create page pools per TX queue when IOMMU is detected
- Handle page pool buffer cleanup in both success and error
paths
- Add proper skip_unmap flag handling for page pool buffers
The page pool path is used for linear skbs without fragments, falling
back to standard DMA mapping for complex skb structures.
Signed-off-by: Eason Lai <Eason.Lai@mediatek.com>
---
drivers/net/wireless/mediatek/mt76/dma.c | 26 +-
drivers/net/wireless/mediatek/mt76/mt76.h | 11 +
.../wireless/mediatek/mt76/mt76_connac_mac.c | 6 +
.../net/wireless/mediatek/mt76/mt7925/pci.c | 4 +-
drivers/net/wireless/mediatek/mt76/mt792x.h | 2 +
.../net/wireless/mediatek/mt76/mt792x_dma.c | 322 ++++++++++++++++++
6 files changed, 362 insertions(+), 9 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/dma.c b/drivers/net/wireless/mediatek/mt76/dma.c
index 2716278788bd..df89c0d1d526 100644
--- a/drivers/net/wireless/mediatek/mt76/dma.c
+++ b/drivers/net/wireless/mediatek/mt76/dma.c
@@ -4,6 +4,8 @@
*/
#include <linux/dma-mapping.h>
+#include <linux/iommu.h>
+#include <net/page_pool/helpers.h>
#include "mt76.h"
#include "dma.h"
#include "mt76_connac.h"
@@ -80,7 +82,7 @@ __mt76_get_rxwi(struct mt76_dev *dev)
return t;
}
-static struct mt76_txwi_cache *
+struct mt76_txwi_cache *
mt76_get_txwi(struct mt76_dev *dev)
{
struct mt76_txwi_cache *t = __mt76_get_txwi(dev);
@@ -93,6 +95,7 @@ mt76_get_txwi(struct mt76_dev *dev)
return mt76_alloc_txwi(dev);
}
+EXPORT_SYMBOL_GPL(mt76_get_txwi);
struct mt76_txwi_cache *
mt76_get_rxwi(struct mt76_dev *dev)
@@ -137,8 +140,10 @@ mt76_free_pending_txwi(struct mt76_dev *dev)
local_bh_disable();
while ((t = __mt76_get_txwi(dev)) != NULL) {
- dma_unmap_single(dev->dma_dev, t->dma_addr, dev->drv->txwi_size,
- DMA_TO_DEVICE);
+ if (dev->dma_dev) {
+ dma_unmap_single(dev->dma_dev, t->dma_addr, dev->drv->txwi_size,
+ DMA_TO_DEVICE);
+ }
kfree(mt76_get_txwi_ptr(dev, t));
}
local_bh_enable();
@@ -190,7 +195,7 @@ mt76_dma_queue_magic_cnt_init(struct mt76_dev *dev, struct mt76_queue *q)
}
}
-static void
+void
mt76_dma_sync_idx(struct mt76_dev *dev, struct mt76_queue *q)
{
if ((q->flags & MT_QFLAG_WED_RRO_EN) &&
@@ -208,6 +213,7 @@ mt76_dma_sync_idx(struct mt76_dev *dev, struct mt76_queue *q)
q->head = Q_READ(q, dma_idx);
q->tail = q->head;
}
+EXPORT_SYMBOL_GPL(mt76_dma_sync_idx);
void mt76_dma_queue_reset(struct mt76_dev *dev, struct mt76_queue *q,
bool reset_idx)
@@ -310,7 +316,7 @@ mt76_dma_add_rx_buf(struct mt76_dev *dev, struct mt76_queue *q,
return idx;
}
-static int
+int
mt76_dma_add_buf(struct mt76_dev *dev, struct mt76_queue *q,
struct mt76_queue_buf *buf, int nbufs, u32 info,
struct sk_buff *skb, void *txwi)
@@ -378,8 +384,9 @@ mt76_dma_add_buf(struct mt76_dev *dev, struct mt76_queue *q,
return idx;
}
+EXPORT_SYMBOL_GPL(mt76_dma_add_buf);
-static void
+void
mt76_dma_tx_cleanup_idx(struct mt76_dev *dev, struct mt76_queue *q, int idx,
struct mt76_queue_entry *prev_e)
{
@@ -399,8 +406,9 @@ mt76_dma_tx_cleanup_idx(struct mt76_dev *dev, struct mt76_queue *q, int idx,
*prev_e = *e;
memset(e, 0, sizeof(*e));
}
+EXPORT_SYMBOL_GPL(mt76_dma_tx_cleanup_idx);
-static void
+void
mt76_dma_kick_queue(struct mt76_dev *dev, struct mt76_queue *q)
{
wmb();
@@ -409,6 +417,7 @@ mt76_dma_kick_queue(struct mt76_dev *dev, struct mt76_queue *q)
else
Q_WRITE(q, cpu_idx, q->head);
}
+EXPORT_SYMBOL_GPL(mt76_dma_kick_queue);
static void
mt76_dma_tx_cleanup(struct mt76_dev *dev, struct mt76_queue *q, bool flush)
@@ -1152,7 +1161,7 @@ mt76_dma_init(struct mt76_dev *dev,
return 0;
}
-static const struct mt76_queue_ops mt76_dma_ops = {
+const struct mt76_queue_ops mt76_dma_ops = {
.init = mt76_dma_init,
.alloc = mt76_dma_alloc_queue,
.reset_q = mt76_dma_queue_reset,
@@ -1164,6 +1173,7 @@ static const struct mt76_queue_ops mt76_dma_ops = {
.rx_reset = mt76_dma_rx_reset,
.kick = mt76_dma_kick_queue,
};
+EXPORT_SYMBOL_GPL(mt76_dma_ops);
void mt76_dma_attach(struct mt76_dev *dev)
{
diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h
index 927c21536f4e..25e24fa36eca 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76.h
@@ -957,6 +957,8 @@ struct mt76_dev {
int tx_dma_idx[4];
enum mt76_hwrro_mode hwrro_mode;
+ bool tx_prealloc_enabled;
+
struct mt76_worker tx_worker;
struct napi_struct tx_napi;
@@ -1785,7 +1787,16 @@ mt76_tx_status_get_hw(struct mt76_dev *dev, struct sk_buff *skb)
void mt76_put_txwi(struct mt76_dev *dev, struct mt76_txwi_cache *t);
void mt76_put_rxwi(struct mt76_dev *dev, struct mt76_txwi_cache *t);
+struct mt76_txwi_cache *mt76_get_txwi(struct mt76_dev *dev);
struct mt76_txwi_cache *mt76_get_rxwi(struct mt76_dev *dev);
+int mt76_dma_add_buf(struct mt76_dev *dev, struct mt76_queue *q,
+ struct mt76_queue_buf *buf, int nbufs, u32 info,
+ struct sk_buff *skb, void *txwi);
+void mt76_dma_tx_cleanup_idx(struct mt76_dev *dev, struct mt76_queue *q, int idx,
+ struct mt76_queue_entry *prev_e);
+void mt76_dma_kick_queue(struct mt76_dev *dev, struct mt76_queue *q);
+void mt76_dma_sync_idx(struct mt76_dev *dev, struct mt76_queue *q);
+extern const struct mt76_queue_ops mt76_dma_ops;
void mt76_free_pending_rxwi(struct mt76_dev *dev);
void mt76_rx_complete(struct mt76_dev *dev, struct sk_buff_head *frames,
struct napi_struct *napi);
diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c b/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c
index 0339e2e7ab60..d4b36b0832b8 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c
@@ -250,6 +250,12 @@ void mt76_connac_txp_skb_unmap(struct mt76_dev *dev,
{
struct mt76_connac_txp_common *txp;
+ if (t->page_pool_buf) {
+ mt76_put_page_pool_buf(t->page_pool_buf, false);
+ t->page_pool_buf = NULL;
+ return;
+ }
+
txp = mt76_connac_txwi_to_txp(dev, t);
if (is_mt76_fw_txp(dev))
mt76_connac_txp_skb_unmap_fw(dev, &txp->fw);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c
index c4161754c01d..3d69d8c67dea 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c
@@ -214,7 +214,7 @@ static int mt7925_dma_init(struct mt792x_dev *dev)
{
int ret;
- mt76_dma_attach(&dev->mt76);
+ mt792x_dma_attach(&dev->mt76);
ret = mt792x_dma_disable(dev, true);
if (ret)
@@ -263,6 +263,8 @@ static int mt7925_dma_init(struct mt792x_dev *dev)
mt792x_poll_tx);
napi_enable(&dev->mt76.tx_napi);
+ mt792x_dma_tx_page_pool_init(dev);
+
return mt792x_dma_enable(dev);
}
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h
index 4ff93f2cd624..7ac0318dc249 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x.h
+++ b/drivers/net/wireless/mediatek/mt76/mt792x.h
@@ -418,8 +418,10 @@ void mt792x_sta_statistics(struct ieee80211_hw *hw,
struct station_info *sinfo);
void mt792x_set_coverage_class(struct ieee80211_hw *hw, int radio_idx,
s16 coverage_class);
+void mt792x_dma_attach(struct mt76_dev *dev);
void mt792x_dma_cleanup(struct mt792x_dev *dev);
int mt792x_dma_enable(struct mt792x_dev *dev);
+int mt792x_dma_tx_page_pool_init(struct mt792x_dev *dev);
int mt792x_wpdma_reset(struct mt792x_dev *dev, bool force);
int mt792x_wpdma_reinit_cond(struct mt792x_dev *dev);
int mt792x_dma_disable(struct mt792x_dev *dev, bool force);
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_dma.c b/drivers/net/wireless/mediatek/mt76/mt792x_dma.c
index 002aece857b2..b341f1cb3ce0 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x_dma.c
+++ b/drivers/net/wireless/mediatek/mt76/mt792x_dma.c
@@ -3,6 +3,9 @@
#include <linux/module.h>
#include <linux/firmware.h>
+#include <linux/iommu.h>
+#include <net/page_pool/helpers.h>
+#include <linux/of.h>
#include "mt792x.h"
#include "dma.h"
@@ -266,6 +269,323 @@ int mt792x_wpdma_reinit_cond(struct mt792x_dev *dev)
}
EXPORT_SYMBOL_GPL(mt792x_wpdma_reinit_cond);
+static int mt792x_create_tx_page_pool(struct mt76_dev *dev, struct mt76_queue *q)
+{
+ struct page_pool_params pp_params = {
+ .order = 0,
+ .flags = 0,
+ .nid = NUMA_NO_NODE,
+ .dev = dev->dma_dev,
+ };
+
+ if (!q || !dev->tx_prealloc_enabled)
+ return 0;
+
+ if (!mt76_is_mmio(dev))
+ return 0;
+
+ pp_params.pool_size = 256;
+ pp_params.flags |= PP_FLAG_DMA_MAP;
+ pp_params.dma_dir = DMA_BIDIRECTIONAL;
+ pp_params.max_len = PAGE_SIZE;
+ pp_params.offset = 0;
+
+ q->page_pool = page_pool_create(&pp_params);
+ if (IS_ERR(q->page_pool)) {
+ int err = PTR_ERR(q->page_pool);
+
+ q->page_pool = NULL;
+ dev_warn(dev->dev, "Failed to create TX page pool for queue %d (err=%d)\n",
+ q->hw_idx, err);
+ return 0;
+ }
+
+ return 0;
+}
+
+int mt792x_dma_tx_page_pool_init(struct mt792x_dev *dev)
+{
+ struct mt76_dev *mdev = &dev->mt76;
+ int i, ret, pool_count = 0;
+
+ if (!iommu_get_domain_for_dev(mdev->dma_dev))
+ return 0;
+
+ if (!mt76_is_mmio(mdev))
+ return 0;
+
+ mdev->tx_prealloc_enabled = true;
+
+ for (i = 0; i < ARRAY_SIZE(mdev->phy.q_tx); i++) {
+ struct mt76_queue *q = mdev->phy.q_tx[i];
+
+ if (!q)
+ continue;
+
+ ret = mt792x_create_tx_page_pool(mdev, q);
+ if (ret)
+ return ret;
+
+ if (q->page_pool)
+ pool_count++;
+ }
+
+ if (pool_count > 0)
+ dev_info(mdev->dev,
+ "IOMMU enabled, created %d TX page pools\n", pool_count);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(mt792x_dma_tx_page_pool_init);
+
+static void mt792x_dma_tx_page_pool_cleanup(struct mt792x_dev *dev)
+{
+ struct mt76_dev *mdev = &dev->mt76;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(mdev->phy.q_tx); i++) {
+ struct mt76_queue *q = mdev->phy.q_tx[i];
+
+ if (!q || !q->page_pool)
+ continue;
+
+ page_pool_destroy(q->page_pool);
+ q->page_pool = NULL;
+ }
+
+ mdev->tx_prealloc_enabled = false;
+}
+
+static void *
+mt792x_dma_tx_alloc_page_pool_buf(struct mt76_dev *dev, struct mt76_queue *q,
+ struct sk_buff *skb, dma_addr_t *dma_addr,
+ int *buf_len)
+{
+ struct page *page;
+ void *buf;
+ int len;
+ u32 offset;
+
+ if (!q->page_pool || !dev->tx_prealloc_enabled)
+ return NULL;
+
+ len = skb_headlen(skb);
+ if (len > PAGE_SIZE)
+ return NULL;
+
+ buf = mt76_get_page_pool_buf(q, &offset, len);
+ if (!buf)
+ return NULL;
+
+ page = virt_to_head_page(buf);
+ *dma_addr = page_pool_get_dma_addr(page) + offset;
+ if (unlikely(!*dma_addr)) {
+ dev_warn_ratelimited(dev->dev, "Page pool returned NULL DMA address\n");
+ mt76_put_page_pool_buf(buf, false);
+ return NULL;
+ }
+
+ *buf_len = len;
+
+ dma_sync_single_for_cpu(dev->dma_dev, *dma_addr, len, DMA_TO_DEVICE);
+ skb_copy_from_linear_data(skb, buf, len);
+ dma_sync_single_for_device(dev->dma_dev, *dma_addr, len, DMA_TO_DEVICE);
+
+ return buf;
+}
+
+static int
+mt792x_dma_tx_queue_skb(struct mt76_phy *phy, struct mt76_queue *q,
+ enum mt76_txq_id qid, struct sk_buff *skb,
+ struct mt76_wcid *wcid, struct ieee80211_sta *sta)
+{
+ struct ieee80211_tx_status status = {
+ .sta = sta,
+ };
+ struct mt76_tx_info tx_info = {
+ .skb = skb,
+ };
+ struct mt76_dev *dev = phy->dev;
+ struct ieee80211_hw *hw;
+ int len, n = 0, ret = -ENOMEM;
+ struct mt76_txwi_cache *t;
+ struct sk_buff *iter;
+ dma_addr_t addr;
+ u8 *txwi;
+
+ if (test_bit(MT76_RESET, &phy->state))
+ goto free_skb;
+
+ t = mt76_get_txwi(dev);
+ if (!t)
+ goto free_skb;
+
+ txwi = mt76_get_txwi_ptr(dev, t);
+
+ skb->prev = NULL;
+ skb->next = NULL;
+ if (dev->drv->drv_flags & MT_DRV_TX_ALIGNED4_SKBS)
+ mt76_insert_hdr_pad(skb);
+
+ len = skb_headlen(skb);
+
+ if (dev->tx_prealloc_enabled && q->page_pool &&
+ !skb_has_frag_list(skb) && !skb_shinfo(skb)->nr_frags) {
+ void *buf;
+ int pp_len;
+
+ buf = mt792x_dma_tx_alloc_page_pool_buf(dev, q, skb, &addr, &pp_len);
+ if (buf) {
+ t->page_pool_buf = buf;
+ len = pp_len;
+
+ tx_info.buf[n].addr = t->dma_addr;
+ tx_info.buf[n++].len = dev->drv->txwi_size;
+ tx_info.buf[n].addr = addr;
+ tx_info.buf[n].len = len;
+ tx_info.buf[n].skip_unmap = true;
+ n++;
+
+ goto skip_dma_map;
+ }
+ }
+
+ addr = dma_map_single(dev->dma_dev, skb->data, len, DMA_TO_DEVICE);
+ if (unlikely(dma_mapping_error(dev->dma_dev, addr)))
+ goto free;
+
+ t->page_pool_buf = NULL;
+
+ tx_info.buf[n].addr = t->dma_addr;
+ tx_info.buf[n++].len = dev->drv->txwi_size;
+ tx_info.buf[n].addr = addr;
+ tx_info.buf[n++].len = len;
+
+skip_dma_map:
+ skb_walk_frags(skb, iter) {
+ if (n == ARRAY_SIZE(tx_info.buf))
+ goto unmap;
+
+ addr = dma_map_single(dev->dma_dev, iter->data, iter->len,
+ DMA_TO_DEVICE);
+ if (unlikely(dma_mapping_error(dev->dma_dev, addr)))
+ goto unmap;
+
+ tx_info.buf[n].addr = addr;
+ tx_info.buf[n].skip_unmap = false;
+ tx_info.buf[n++].len = iter->len;
+ }
+ tx_info.nbuf = n;
+
+ if (q->queued + (tx_info.nbuf + 1) / 2 >= q->ndesc - 1) {
+ ret = -ENOMEM;
+ goto unmap;
+ }
+
+ dma_sync_single_for_cpu(dev->dma_dev, t->dma_addr, dev->drv->txwi_size,
+ DMA_TO_DEVICE);
+ ret = dev->drv->tx_prepare_skb(dev, txwi, qid, wcid, sta, &tx_info);
+ dma_sync_single_for_device(dev->dma_dev, t->dma_addr, dev->drv->txwi_size,
+ DMA_TO_DEVICE);
+ if (ret < 0)
+ goto unmap;
+
+ return mt76_dma_add_buf(dev, q, tx_info.buf, tx_info.nbuf,
+ tx_info.info, tx_info.skb, t);
+
+unmap:
+ for (n--; n > 0; n--) {
+ if (!tx_info.buf[n].skip_unmap)
+ dma_unmap_single(dev->dma_dev, tx_info.buf[n].addr,
+ tx_info.buf[n].len, DMA_TO_DEVICE);
+ }
+
+ if (t->page_pool_buf) {
+ mt76_put_page_pool_buf(t->page_pool_buf, false);
+ t->page_pool_buf = NULL;
+ }
+
+free:
+#ifdef CONFIG_NL80211_TESTMODE
+ if (mt76_is_testmode_skb(dev, skb, &hw)) {
+ struct mt76_phy *phy = hw->priv;
+
+ if (tx_info.skb == phy->test.tx_skb)
+ phy->test.tx_done--;
+ }
+#endif
+
+ mt76_put_txwi(dev, t);
+
+free_skb:
+ status.skb = tx_info.skb;
+ hw = mt76_tx_status_get_hw(dev, tx_info.skb);
+ spin_lock_bh(&dev->rx_lock);
+ ieee80211_tx_status_ext(hw, &status);
+ spin_unlock_bh(&dev->rx_lock);
+
+ return ret;
+}
+
+static void
+mt792x_dma_tx_cleanup(struct mt76_dev *dev, struct mt76_queue *q, bool flush)
+{
+ struct mt76_queue_entry entry;
+ int last;
+
+ if (!q || !q->ndesc)
+ return;
+
+ spin_lock_bh(&q->cleanup_lock);
+ if (flush)
+ last = -1;
+ else
+ last = Q_READ(q, dma_idx);
+
+ while (q->queued > 0 && q->tail != last) {
+ mt76_dma_tx_cleanup_idx(dev, q, q->tail, &entry);
+ mt76_npu_txdesc_cleanup(q, q->tail);
+ mt76_queue_tx_complete(dev, q, &entry);
+
+ if (entry.txwi) {
+ if (entry.skb && entry.txwi->page_pool_buf) {
+ mt76_put_page_pool_buf(entry.txwi->page_pool_buf, false);
+ entry.txwi->page_pool_buf = NULL;
+ }
+
+ if (!(dev->drv->drv_flags & MT_DRV_TXWI_NO_FREE))
+ mt76_put_txwi(dev, entry.txwi);
+ }
+
+ if (!flush && q->tail == last)
+ last = Q_READ(q, dma_idx);
+ }
+ spin_unlock_bh(&q->cleanup_lock);
+
+ if (flush) {
+ spin_lock_bh(&q->lock);
+ mt76_dma_sync_idx(dev, q);
+ mt76_dma_kick_queue(dev, q);
+ spin_unlock_bh(&q->lock);
+ }
+
+ if (!q->queued)
+ wake_up(&dev->tx_wait);
+}
+
+static struct mt76_queue_ops mt792x_queue_ops;
+
+void mt792x_dma_attach(struct mt76_dev *dev)
+{
+ mt792x_queue_ops = mt76_dma_ops;
+
+ mt792x_queue_ops.tx_queue_skb = mt792x_dma_tx_queue_skb;
+ mt792x_queue_ops.tx_cleanup = mt792x_dma_tx_cleanup;
+
+ dev->queue_ops = &mt792x_queue_ops;
+}
+EXPORT_SYMBOL_GPL(mt792x_dma_attach);
+
int mt792x_dma_disable(struct mt792x_dev *dev, bool force)
{
/* disable WFDMA0 */
@@ -326,6 +646,8 @@ void mt792x_dma_cleanup(struct mt792x_dev *dev)
MT_WFDMA0_RST_LOGIC_RST);
mt76_dma_cleanup(&dev->mt76);
+
+ mt792x_dma_tx_page_pool_cleanup(dev);
}
EXPORT_SYMBOL_GPL(mt792x_dma_cleanup);
--
2.45.2
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH 3/3] wifi: mt76: mt792x: Restrict TX page pool to MT8196 platform
2026-07-09 7:55 [PATCH 0/3] Fix TX performance degradation when IOMMU is enabled Eason Lai
2026-07-09 7:55 ` [PATCH 1/3] wifi: mt76: Separate skb and page_pool_buf pointers in mt76_txwi_cache Eason Lai
2026-07-09 7:55 ` [PATCH 2/3] wifi: mt76: mt792x: Add TX page pool support for IOMMU-enabled systems Eason Lai
@ 2026-07-09 7:55 ` Eason Lai
2 siblings, 0 replies; 6+ messages in thread
From: Eason Lai @ 2026-07-09 7:55 UTC (permalink / raw)
To: nbd, lorenzo
Cc: linux-wireless, linux-mediatek, Yf.Luo, kun.wu, deren.wu,
sean.wang, quan.zhou, ryder.lee, leon.yen, litien.chang, jb.tsai,
eason.lai, Eason Lai
From: Eason Lai <Eason.Lai@mediatek.com>
The TX page pool optimization significantly improves performance on
MT8196 when IOMMU is enabled, but this benefit is platform-specific.
Restrict this feature to MT8196 where it has been tested and validated.
On MT8196 with IOMMU enabled, DMA mapping overhead increases
dramatically compared to IOMMU-disabled configurations:
dma_unmap_single() in mt76_connac_txp_skb_unmap_hw() (NAPI context):
- IOMMU disabled: 181.25 ns (avg over 20,000 calls)
- IOMMU enabled: 5216.19 ns (avg over 21,000 calls)
dma_map_single() in mt76_dma_tx_queue_skb() (workqueue context):
- IOMMU disabled: 880.20 ns (avg over 20,000 calls)
- IOMMU enabled: 2106.65 ns (avg over 20,000 calls)
The TX page pool mitigates this overhead by reusing DMA mappings,
but should only be enabled on platforms where it has been verified
to work correctly.
Signed-off-by: Eason Lai <Eason.Lai@mediatek.com>
---
drivers/net/wireless/mediatek/mt76/mt792x_dma.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_dma.c b/drivers/net/wireless/mediatek/mt76/mt792x_dma.c
index b341f1cb3ce0..6d5725a5b10f 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x_dma.c
+++ b/drivers/net/wireless/mediatek/mt76/mt792x_dma.c
@@ -307,6 +307,7 @@ int mt792x_dma_tx_page_pool_init(struct mt792x_dev *dev)
{
struct mt76_dev *mdev = &dev->mt76;
int i, ret, pool_count = 0;
+ bool is_mt8196;
if (!iommu_get_domain_for_dev(mdev->dma_dev))
return 0;
@@ -314,6 +315,13 @@ int mt792x_dma_tx_page_pool_init(struct mt792x_dev *dev)
if (!mt76_is_mmio(mdev))
return 0;
+ is_mt8196 = of_machine_is_compatible("mediatek,mt8196");
+ if (!is_mt8196) {
+ dev_info(mdev->dev, "Not MT8196 platform, TX page pool optimization disabled\n");
+ return 0;
+ }
+
+ dev_info(mdev->dev, "MT8196 platform detected, enabling TX page pool optimization\n");
mdev->tx_prealloc_enabled = true;
for (i = 0; i < ARRAY_SIZE(mdev->phy.q_tx); i++) {
--
2.45.2
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH 1/3] wifi: mt76: Separate skb and page_pool_buf pointers in mt76_txwi_cache
2026-07-09 7:55 ` [PATCH 1/3] wifi: mt76: Separate skb and page_pool_buf pointers in mt76_txwi_cache Eason Lai
@ 2026-07-09 8:19 ` Lorenzo Bianconi
0 siblings, 0 replies; 6+ messages in thread
From: Lorenzo Bianconi @ 2026-07-09 8:19 UTC (permalink / raw)
To: Eason Lai
Cc: nbd, linux-wireless, linux-mediatek, Yf.Luo, kun.wu, deren.wu,
sean.wang, quan.zhou, ryder.lee, leon.yen, litien.chang, jb.tsai
[-- Attachment #1: Type: text/plain, Size: 6697 bytes --]
> From: Eason Lai <Eason.Lai@mediatek.com>
>
> Refactor mt76_txwi_cache structure to use separate skb and page_pool_buf
> fields instead of a union with ptr. This improves type safety and makes
> the code more explicit about which pointer type is being used in
> different contexts.
>
> Also add skip_unmap flag to tx_info.buf to handle cases where DMA
> unmapping should be skipped.
>
> Signed-off-by: Eason Lai <Eason.Lai@mediatek.com>
> ---
> drivers/net/wireless/mediatek/mt76/dma.c | 29 ++++++++++++-------
> drivers/net/wireless/mediatek/mt76/mt76.h | 4 +--
> .../net/wireless/mediatek/mt76/mt7996/mac.c | 10 +++----
> drivers/net/wireless/mediatek/mt76/tx.c | 2 +-
> drivers/net/wireless/mediatek/mt76/wed.c | 6 ++--
> 5 files changed, 28 insertions(+), 23 deletions(-)
>
> diff --git a/drivers/net/wireless/mediatek/mt76/dma.c b/drivers/net/wireless/mediatek/mt76/dma.c
> index f8c2fe5f2f58..2716278788bd 100644
> --- a/drivers/net/wireless/mediatek/mt76/dma.c
> +++ b/drivers/net/wireless/mediatek/mt76/dma.c
> @@ -43,7 +43,8 @@ mt76_alloc_rxwi(struct mt76_dev *dev)
> if (!t)
> return NULL;
>
> - t->ptr = NULL;
> + t->skb = NULL;
> + t->page_pool_buf = NULL;
> return t;
> }
>
> @@ -84,8 +85,11 @@ mt76_get_txwi(struct mt76_dev *dev)
> {
> struct mt76_txwi_cache *t = __mt76_get_txwi(dev);
>
> - if (t)
> + if (t) {
> + t->skb = NULL;
> + t->page_pool_buf = NULL;
> return t;
> + }
>
> return mt76_alloc_txwi(dev);
> }
> @@ -147,8 +151,8 @@ mt76_free_pending_rxwi(struct mt76_dev *dev)
>
> local_bh_disable();
> while ((t = __mt76_get_rxwi(dev)) != NULL) {
> - if (t->ptr)
> - mt76_put_page_pool_buf(t->ptr, false);
> + if (t->page_pool_buf)
> + mt76_put_page_pool_buf(t->page_pool_buf, false);
> kfree(t);
> }
> local_bh_enable();
> @@ -475,14 +479,14 @@ mt76_dma_get_rxdmad_c_buf(struct mt76_dev *dev, struct mt76_queue *q,
> if (more)
> *more = !FIELD_GET(RRO_RXDMAD_DATA1_LS_MASK, data1);
>
> - buf = t->ptr;
> + buf = t->page_pool_buf;
> ind_reason = FIELD_GET(RRO_RXDMAD_DATA2_IND_REASON_MASK, data2);
> if (ind_reason == MT_DMA_WED_IND_REASON_REPEAT ||
> ind_reason == MT_DMA_WED_IND_REASON_OLDPKT) {
> mt76_put_page_pool_buf(buf, false);
> buf = ERR_PTR(-EAGAIN);
> }
> - t->ptr = NULL;
> + t->page_pool_buf = NULL;
> t->dma_addr = 0;
>
> mt76_put_rxwi(dev, t);
> @@ -529,9 +533,9 @@ mt76_dma_get_buf(struct mt76_dev *dev, struct mt76_queue *q, int idx,
> SKB_WITH_OVERHEAD(q->buf_size),
> page_pool_get_dma_dir(q->page_pool));
>
> - buf = t->ptr;
> + buf = t->page_pool_buf;
> t->dma_addr = 0;
> - t->ptr = NULL;
> + t->page_pool_buf = NULL;
>
> mt76_put_rxwi(dev, t);
> if (drop)
> @@ -694,6 +698,7 @@ mt76_dma_tx_queue_skb(struct mt76_phy *phy, struct mt76_queue *q,
> goto unmap;
>
> tx_info.buf[n].addr = addr;
> + tx_info.buf[n].skip_unmap = false;
> tx_info.buf[n++].len = iter->len;
> }
> tx_info.nbuf = n;
> @@ -718,9 +723,11 @@ mt76_dma_tx_queue_skb(struct mt76_phy *phy, struct mt76_queue *q,
> tx_info.info, tx_info.skb, t);
>
> unmap:
> - for (n--; n > 0; n--)
> - dma_unmap_single(dev->dma_dev, tx_info.buf[n].addr,
> - tx_info.buf[n].len, DMA_TO_DEVICE);
> + for (n--; n > 0; n--) {
> + if (!tx_info.buf[n].skip_unmap)
> + dma_unmap_single(dev->dma_dev, tx_info.buf[n].addr,
> + tx_info.buf[n].len, DMA_TO_DEVICE);
> + }
>
> free:
> #ifdef CONFIG_NL80211_TESTMODE
> diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h
> index 527bef97e122..927c21536f4e 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt76.h
> +++ b/drivers/net/wireless/mediatek/mt76/mt76.h
> @@ -445,10 +445,8 @@ struct mt76_txwi_cache {
> struct list_head list;
> dma_addr_t dma_addr;
>
> - union {
> struct sk_buff *skb;
> - void *ptr;
> - };
> + void *page_pool_buf;
if you do not rename the pointer here, the patch would be much less intrusive.
Regards,
Lorenzo
>
> u8 qid;
> u8 phy_idx;
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
> index e2a83da3a09c..924b0dc0ff1e 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
> @@ -1775,12 +1775,12 @@ static void mt7996_rx_token_put(struct mt7996_dev *dev)
> struct mt76_txwi_cache *t;
>
> t = mt76_rx_token_release(&dev->mt76, i);
> - if (!t || !t->ptr)
> + if (!t || !t->page_pool_buf)
> continue;
>
> - mt76_put_page_pool_buf(t->ptr, false);
> + mt76_put_page_pool_buf(t->page_pool_buf, false);
> t->dma_addr = 0;
> - t->ptr = NULL;
> + t->page_pool_buf = NULL;
>
> mt76_put_rxwi(&dev->mt76, t);
> }
> @@ -1928,14 +1928,14 @@ void mt7996_rro_rx_process(struct mt76_dev *mdev, void *data)
> goto next_page;
>
> qid = t->qid;
> - buf = t->ptr;
> + buf = t->page_pool_buf;
> q = &mdev->q_rx[qid];
> dma_sync_single_for_cpu(mdev->dma_dev, t->dma_addr,
> SKB_WITH_OVERHEAD(q->buf_size),
> page_pool_get_dma_dir(q->page_pool));
>
> t->dma_addr = 0;
> - t->ptr = NULL;
> + t->page_pool_buf = NULL;
> mt76_put_rxwi(mdev, t);
> if (!buf)
> goto next_page;
> diff --git a/drivers/net/wireless/mediatek/mt76/tx.c b/drivers/net/wireless/mediatek/mt76/tx.c
> index 22f9690634c9..665156a7ea65 100644
> --- a/drivers/net/wireless/mediatek/mt76/tx.c
> +++ b/drivers/net/wireless/mediatek/mt76/tx.c
> @@ -899,7 +899,7 @@ int mt76_rx_token_consume(struct mt76_dev *dev, void *ptr,
> token = idr_alloc(&dev->rx_token, t, 0, dev->rx_token_size,
> GFP_ATOMIC);
> if (token >= 0) {
> - t->ptr = ptr;
> + t->page_pool_buf = ptr;
> t->dma_addr = phys;
> }
> spin_unlock_bh(&dev->rx_token_lock);
> diff --git a/drivers/net/wireless/mediatek/mt76/wed.c b/drivers/net/wireless/mediatek/mt76/wed.c
> index ed657d952de2..e1cf81d722b8 100644
> --- a/drivers/net/wireless/mediatek/mt76/wed.c
> +++ b/drivers/net/wireless/mediatek/mt76/wed.c
> @@ -15,11 +15,11 @@ void mt76_wed_release_rx_buf(struct mtk_wed_device *wed)
> struct mt76_txwi_cache *t;
>
> t = mt76_rx_token_release(dev, i);
> - if (!t || !t->ptr)
> + if (!t || !t->page_pool_buf)
> continue;
>
> - mt76_put_page_pool_buf(t->ptr, false);
> - t->ptr = NULL;
> + mt76_put_page_pool_buf(t->page_pool_buf, false);
> + t->page_pool_buf = NULL;
>
> mt76_put_rxwi(dev, t);
> }
> --
> 2.45.2
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH 2/3] wifi: mt76: mt792x: Add TX page pool support for IOMMU-enabled systems
2026-07-09 7:55 ` [PATCH 2/3] wifi: mt76: mt792x: Add TX page pool support for IOMMU-enabled systems Eason Lai
@ 2026-07-09 8:27 ` Lorenzo Bianconi
0 siblings, 0 replies; 6+ messages in thread
From: Lorenzo Bianconi @ 2026-07-09 8:27 UTC (permalink / raw)
To: Eason Lai
Cc: nbd, linux-wireless, linux-mediatek, Yf.Luo, kun.wu, deren.wu,
sean.wang, quan.zhou, ryder.lee, leon.yen, litien.chang, jb.tsai
[-- Attachment #1: Type: text/plain, Size: 15545 bytes --]
On Jul 09, Eason Lai wrote:
> From: Eason Lai <Eason.Lai@mediatek.com>
>
> Add support for TX page pool allocation in mt792x devices when IOMMU is
> enabled. This optimization reduces DMA mapping overhead by
> pre-allocating and reusing page pool buffers for TX operations.
Can you please provide some test results here? In particular regarding the
resulting throughput.
>
> Key changes:
> - Export DMA helper functions and ops for mt792x usage
> - Add tx_prealloc_enabled flag to track page pool state
> - Implement mt792x-specific DMA queue operations with TX page pool
> - Create page pools per TX queue when IOMMU is detected
> - Handle page pool buffer cleanup in both success and error
> paths
> - Add proper skip_unmap flag handling for page pool buffers
>
> The page pool path is used for linear skbs without fragments, falling
> back to standard DMA mapping for complex skb structures.
>
> Signed-off-by: Eason Lai <Eason.Lai@mediatek.com>
> ---
[...]
>
> -static void
> +void
> mt76_dma_kick_queue(struct mt76_dev *dev, struct mt76_queue *q)
> {
> wmb();
> @@ -409,6 +417,7 @@ mt76_dma_kick_queue(struct mt76_dev *dev, struct mt76_queue *q)
> else
> Q_WRITE(q, cpu_idx, q->head);
> }
> +EXPORT_SYMBOL_GPL(mt76_dma_kick_queue);
this is already available in mt76_queue_ops struct.
>
> static void
> mt76_dma_tx_cleanup(struct mt76_dev *dev, struct mt76_queue *q, bool flush)
> @@ -1152,7 +1161,7 @@ mt76_dma_init(struct mt76_dev *dev,
> return 0;
> }
>
> -static const struct mt76_queue_ops mt76_dma_ops = {
> +const struct mt76_queue_ops mt76_dma_ops = {
> .init = mt76_dma_init,
> .alloc = mt76_dma_alloc_queue,
> .reset_q = mt76_dma_queue_reset,
> @@ -1164,6 +1173,7 @@ static const struct mt76_queue_ops mt76_dma_ops = {
> .rx_reset = mt76_dma_rx_reset,
> .kick = mt76_dma_kick_queue,
> };
> +EXPORT_SYMBOL_GPL(mt76_dma_ops);
>
> void mt76_dma_attach(struct mt76_dev *dev)
> {
> diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h
> index 927c21536f4e..25e24fa36eca 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt76.h
> +++ b/drivers/net/wireless/mediatek/mt76/mt76.h
> @@ -957,6 +957,8 @@ struct mt76_dev {
> int tx_dma_idx[4];
> enum mt76_hwrro_mode hwrro_mode;
>
> + bool tx_prealloc_enabled;
> +
> struct mt76_worker tx_worker;
> struct napi_struct tx_napi;
>
> @@ -1785,7 +1787,16 @@ mt76_tx_status_get_hw(struct mt76_dev *dev, struct sk_buff *skb)
>
> void mt76_put_txwi(struct mt76_dev *dev, struct mt76_txwi_cache *t);
> void mt76_put_rxwi(struct mt76_dev *dev, struct mt76_txwi_cache *t);
> +struct mt76_txwi_cache *mt76_get_txwi(struct mt76_dev *dev);
> struct mt76_txwi_cache *mt76_get_rxwi(struct mt76_dev *dev);
> +int mt76_dma_add_buf(struct mt76_dev *dev, struct mt76_queue *q,
> + struct mt76_queue_buf *buf, int nbufs, u32 info,
> + struct sk_buff *skb, void *txwi);
> +void mt76_dma_tx_cleanup_idx(struct mt76_dev *dev, struct mt76_queue *q, int idx,
> + struct mt76_queue_entry *prev_e);
> +void mt76_dma_kick_queue(struct mt76_dev *dev, struct mt76_queue *q);
> +void mt76_dma_sync_idx(struct mt76_dev *dev, struct mt76_queue *q);
> +extern const struct mt76_queue_ops mt76_dma_ops;
> void mt76_free_pending_rxwi(struct mt76_dev *dev);
> void mt76_rx_complete(struct mt76_dev *dev, struct sk_buff_head *frames,
> struct napi_struct *napi);
> diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c b/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c
> index 0339e2e7ab60..d4b36b0832b8 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c
> @@ -250,6 +250,12 @@ void mt76_connac_txp_skb_unmap(struct mt76_dev *dev,
> {
> struct mt76_connac_txp_common *txp;
>
> + if (t->page_pool_buf) {
> + mt76_put_page_pool_buf(t->page_pool_buf, false);
> + t->page_pool_buf = NULL;
> + return;
> + }
> +
> txp = mt76_connac_txwi_to_txp(dev, t);
> if (is_mt76_fw_txp(dev))
> mt76_connac_txp_skb_unmap_fw(dev, &txp->fw);
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c
> index c4161754c01d..3d69d8c67dea 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c
> @@ -214,7 +214,7 @@ static int mt7925_dma_init(struct mt792x_dev *dev)
> {
> int ret;
>
> - mt76_dma_attach(&dev->mt76);
> + mt792x_dma_attach(&dev->mt76);
>
> ret = mt792x_dma_disable(dev, true);
> if (ret)
> @@ -263,6 +263,8 @@ static int mt7925_dma_init(struct mt792x_dev *dev)
> mt792x_poll_tx);
> napi_enable(&dev->mt76.tx_napi);
>
> + mt792x_dma_tx_page_pool_init(dev);
> +
> return mt792x_dma_enable(dev);
> }
>
> diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h
> index 4ff93f2cd624..7ac0318dc249 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt792x.h
> +++ b/drivers/net/wireless/mediatek/mt76/mt792x.h
> @@ -418,8 +418,10 @@ void mt792x_sta_statistics(struct ieee80211_hw *hw,
> struct station_info *sinfo);
> void mt792x_set_coverage_class(struct ieee80211_hw *hw, int radio_idx,
> s16 coverage_class);
> +void mt792x_dma_attach(struct mt76_dev *dev);
> void mt792x_dma_cleanup(struct mt792x_dev *dev);
> int mt792x_dma_enable(struct mt792x_dev *dev);
> +int mt792x_dma_tx_page_pool_init(struct mt792x_dev *dev);
> int mt792x_wpdma_reset(struct mt792x_dev *dev, bool force);
> int mt792x_wpdma_reinit_cond(struct mt792x_dev *dev);
> int mt792x_dma_disable(struct mt792x_dev *dev, bool force);
> diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_dma.c b/drivers/net/wireless/mediatek/mt76/mt792x_dma.c
> index 002aece857b2..b341f1cb3ce0 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt792x_dma.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt792x_dma.c
> @@ -3,6 +3,9 @@
>
> #include <linux/module.h>
> #include <linux/firmware.h>
> +#include <linux/iommu.h>
> +#include <net/page_pool/helpers.h>
> +#include <linux/of.h>
>
> #include "mt792x.h"
> #include "dma.h"
> @@ -266,6 +269,323 @@ int mt792x_wpdma_reinit_cond(struct mt792x_dev *dev)
> }
> EXPORT_SYMBOL_GPL(mt792x_wpdma_reinit_cond);
>
> +static int mt792x_create_tx_page_pool(struct mt76_dev *dev, struct mt76_queue *q)
> +{
> + struct page_pool_params pp_params = {
> + .order = 0,
> + .flags = 0,
> + .nid = NUMA_NO_NODE,
> + .dev = dev->dma_dev,
> + };
> +
> + if (!q || !dev->tx_prealloc_enabled)
> + return 0;
> +
> + if (!mt76_is_mmio(dev))
> + return 0;
do you really need this check? you already check it in
mt792x_dma_tx_page_pool_init().
> +
> + pp_params.pool_size = 256;
> + pp_params.flags |= PP_FLAG_DMA_MAP;
> + pp_params.dma_dir = DMA_BIDIRECTIONAL;
> + pp_params.max_len = PAGE_SIZE;
> + pp_params.offset = 0;
> +
> + q->page_pool = page_pool_create(&pp_params);
> + if (IS_ERR(q->page_pool)) {
> + int err = PTR_ERR(q->page_pool);
> +
> + q->page_pool = NULL;
> + dev_warn(dev->dev, "Failed to create TX page pool for queue %d (err=%d)\n",
> + q->hw_idx, err);
> + return 0;
> + }
> +
> + return 0;
> +}
> +
> +int mt792x_dma_tx_page_pool_init(struct mt792x_dev *dev)
> +{
> + struct mt76_dev *mdev = &dev->mt76;
> + int i, ret, pool_count = 0;
> +
> + if (!iommu_get_domain_for_dev(mdev->dma_dev))
> + return 0;
> +
> + if (!mt76_is_mmio(mdev))
> + return 0;
> +
> + mdev->tx_prealloc_enabled = true;
> +
> + for (i = 0; i < ARRAY_SIZE(mdev->phy.q_tx); i++) {
> + struct mt76_queue *q = mdev->phy.q_tx[i];
> +
> + if (!q)
> + continue;
> +
> + ret = mt792x_create_tx_page_pool(mdev, q);
> + if (ret)
> + return ret;
> +
> + if (q->page_pool)
> + pool_count++;
> + }
> +
> + if (pool_count > 0)
> + dev_info(mdev->dev,
> + "IOMMU enabled, created %d TX page pools\n", pool_count);
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(mt792x_dma_tx_page_pool_init);
> +
> +static void mt792x_dma_tx_page_pool_cleanup(struct mt792x_dev *dev)
> +{
> + struct mt76_dev *mdev = &dev->mt76;
> + int i;
> +
> + for (i = 0; i < ARRAY_SIZE(mdev->phy.q_tx); i++) {
> + struct mt76_queue *q = mdev->phy.q_tx[i];
> +
> + if (!q || !q->page_pool)
> + continue;
> +
> + page_pool_destroy(q->page_pool);
> + q->page_pool = NULL;
> + }
> +
> + mdev->tx_prealloc_enabled = false;
> +}
> +
> +static void *
> +mt792x_dma_tx_alloc_page_pool_buf(struct mt76_dev *dev, struct mt76_queue *q,
> + struct sk_buff *skb, dma_addr_t *dma_addr,
> + int *buf_len)
> +{
> + struct page *page;
> + void *buf;
> + int len;
> + u32 offset;
> +
> + if (!q->page_pool || !dev->tx_prealloc_enabled)
> + return NULL;
> +
> + len = skb_headlen(skb);
> + if (len > PAGE_SIZE)
> + return NULL;
> +
> + buf = mt76_get_page_pool_buf(q, &offset, len);
> + if (!buf)
> + return NULL;
> +
> + page = virt_to_head_page(buf);
> + *dma_addr = page_pool_get_dma_addr(page) + offset;
> + if (unlikely(!*dma_addr)) {
> + dev_warn_ratelimited(dev->dev, "Page pool returned NULL DMA address\n");
> + mt76_put_page_pool_buf(buf, false);
> + return NULL;
> + }
> +
> + *buf_len = len;
> +
> + dma_sync_single_for_cpu(dev->dma_dev, *dma_addr, len, DMA_TO_DEVICE);
> + skb_copy_from_linear_data(skb, buf, len);
I would like to see some results here since usually it is not a good idea to
copy all the packets (even if it is just the linear part).
> + dma_sync_single_for_device(dev->dma_dev, *dma_addr, len, DMA_TO_DEVICE);
> +
> + return buf;
> +}
> +
> +static int
> +mt792x_dma_tx_queue_skb(struct mt76_phy *phy, struct mt76_queue *q,
> + enum mt76_txq_id qid, struct sk_buff *skb,
> + struct mt76_wcid *wcid, struct ieee80211_sta *sta)
> +{
> + struct ieee80211_tx_status status = {
> + .sta = sta,
> + };
> + struct mt76_tx_info tx_info = {
> + .skb = skb,
> + };
> + struct mt76_dev *dev = phy->dev;
> + struct ieee80211_hw *hw;
> + int len, n = 0, ret = -ENOMEM;
> + struct mt76_txwi_cache *t;
> + struct sk_buff *iter;
> + dma_addr_t addr;
> + u8 *txwi;
> +
> + if (test_bit(MT76_RESET, &phy->state))
> + goto free_skb;
> +
> + t = mt76_get_txwi(dev);
> + if (!t)
> + goto free_skb;
> +
> + txwi = mt76_get_txwi_ptr(dev, t);
> +
> + skb->prev = NULL;
> + skb->next = NULL;
> + if (dev->drv->drv_flags & MT_DRV_TX_ALIGNED4_SKBS)
> + mt76_insert_hdr_pad(skb);
> +
> + len = skb_headlen(skb);
> +
> + if (dev->tx_prealloc_enabled && q->page_pool &&
> + !skb_has_frag_list(skb) && !skb_shinfo(skb)->nr_frags) {
> + void *buf;
> + int pp_len;
> +
> + buf = mt792x_dma_tx_alloc_page_pool_buf(dev, q, skb, &addr, &pp_len);
> + if (buf) {
> + t->page_pool_buf = buf;
> + len = pp_len;
> +
> + tx_info.buf[n].addr = t->dma_addr;
> + tx_info.buf[n++].len = dev->drv->txwi_size;
> + tx_info.buf[n].addr = addr;
> + tx_info.buf[n].len = len;
> + tx_info.buf[n].skip_unmap = true;
> + n++;
> +
> + goto skip_dma_map;
> + }
> + }
> +
> + addr = dma_map_single(dev->dma_dev, skb->data, len, DMA_TO_DEVICE);
> + if (unlikely(dma_mapping_error(dev->dma_dev, addr)))
> + goto free;
> +
> + t->page_pool_buf = NULL;
> +
> + tx_info.buf[n].addr = t->dma_addr;
> + tx_info.buf[n++].len = dev->drv->txwi_size;
> + tx_info.buf[n].addr = addr;
> + tx_info.buf[n++].len = len;
> +
> +skip_dma_map:
> + skb_walk_frags(skb, iter) {
> + if (n == ARRAY_SIZE(tx_info.buf))
> + goto unmap;
> +
> + addr = dma_map_single(dev->dma_dev, iter->data, iter->len,
> + DMA_TO_DEVICE);
> + if (unlikely(dma_mapping_error(dev->dma_dev, addr)))
> + goto unmap;
> +
> + tx_info.buf[n].addr = addr;
> + tx_info.buf[n].skip_unmap = false;
> + tx_info.buf[n++].len = iter->len;
> + }
> + tx_info.nbuf = n;
> +
> + if (q->queued + (tx_info.nbuf + 1) / 2 >= q->ndesc - 1) {
> + ret = -ENOMEM;
> + goto unmap;
> + }
> +
> + dma_sync_single_for_cpu(dev->dma_dev, t->dma_addr, dev->drv->txwi_size,
> + DMA_TO_DEVICE);
> + ret = dev->drv->tx_prepare_skb(dev, txwi, qid, wcid, sta, &tx_info);
> + dma_sync_single_for_device(dev->dma_dev, t->dma_addr, dev->drv->txwi_size,
> + DMA_TO_DEVICE);
> + if (ret < 0)
> + goto unmap;
> +
> + return mt76_dma_add_buf(dev, q, tx_info.buf, tx_info.nbuf,
> + tx_info.info, tx_info.skb, t);
> +
> +unmap:
> + for (n--; n > 0; n--) {
> + if (!tx_info.buf[n].skip_unmap)
> + dma_unmap_single(dev->dma_dev, tx_info.buf[n].addr,
> + tx_info.buf[n].len, DMA_TO_DEVICE);
> + }
> +
> + if (t->page_pool_buf) {
> + mt76_put_page_pool_buf(t->page_pool_buf, false);
> + t->page_pool_buf = NULL;
> + }
> +
> +free:
> +#ifdef CONFIG_NL80211_TESTMODE
> + if (mt76_is_testmode_skb(dev, skb, &hw)) {
> + struct mt76_phy *phy = hw->priv;
> +
> + if (tx_info.skb == phy->test.tx_skb)
> + phy->test.tx_done--;
> + }
> +#endif
> +
> + mt76_put_txwi(dev, t);
> +
> +free_skb:
> + status.skb = tx_info.skb;
> + hw = mt76_tx_status_get_hw(dev, tx_info.skb);
> + spin_lock_bh(&dev->rx_lock);
> + ieee80211_tx_status_ext(hw, &status);
> + spin_unlock_bh(&dev->rx_lock);
> +
> + return ret;
> +}
> +
> +static void
> +mt792x_dma_tx_cleanup(struct mt76_dev *dev, struct mt76_queue *q, bool flush)
this is almost a copy of mt76_dma_tx_cleanup(). Can we do something better
here?
> +{
> + struct mt76_queue_entry entry;
> + int last;
> +
> + if (!q || !q->ndesc)
> + return;
> +
> + spin_lock_bh(&q->cleanup_lock);
> + if (flush)
> + last = -1;
> + else
> + last = Q_READ(q, dma_idx);
> +
> + while (q->queued > 0 && q->tail != last) {
> + mt76_dma_tx_cleanup_idx(dev, q, q->tail, &entry);
> + mt76_npu_txdesc_cleanup(q, q->tail);
> + mt76_queue_tx_complete(dev, q, &entry);
> +
> + if (entry.txwi) {
> + if (entry.skb && entry.txwi->page_pool_buf) {
> + mt76_put_page_pool_buf(entry.txwi->page_pool_buf, false);
> + entry.txwi->page_pool_buf = NULL;
> + }
> +
> + if (!(dev->drv->drv_flags & MT_DRV_TXWI_NO_FREE))
> + mt76_put_txwi(dev, entry.txwi);
> + }
> +
> + if (!flush && q->tail == last)
> + last = Q_READ(q, dma_idx);
> + }
> + spin_unlock_bh(&q->cleanup_lock);
> +
> + if (flush) {
> + spin_lock_bh(&q->lock);
> + mt76_dma_sync_idx(dev, q);
> + mt76_dma_kick_queue(dev, q);
> + spin_unlock_bh(&q->lock);
> + }
> +
> + if (!q->queued)
> + wake_up(&dev->tx_wait);
> +}
> +
> +static struct mt76_queue_ops mt792x_queue_ops;
> +
> +void mt792x_dma_attach(struct mt76_dev *dev)
> +{
> + mt792x_queue_ops = mt76_dma_ops;
can you please take a look to mt7615_mmio_probe()?
Regards,
Lorenzo
> +
> + mt792x_queue_ops.tx_queue_skb = mt792x_dma_tx_queue_skb;
> + mt792x_queue_ops.tx_cleanup = mt792x_dma_tx_cleanup;
> +
> + dev->queue_ops = &mt792x_queue_ops;
> +}
> +EXPORT_SYMBOL_GPL(mt792x_dma_attach);
> +
> int mt792x_dma_disable(struct mt792x_dev *dev, bool force)
> {
> /* disable WFDMA0 */
> @@ -326,6 +646,8 @@ void mt792x_dma_cleanup(struct mt792x_dev *dev)
> MT_WFDMA0_RST_LOGIC_RST);
>
> mt76_dma_cleanup(&dev->mt76);
> +
> + mt792x_dma_tx_page_pool_cleanup(dev);
> }
> EXPORT_SYMBOL_GPL(mt792x_dma_cleanup);
>
> --
> 2.45.2
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-07-09 8:27 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-09 7:55 [PATCH 0/3] Fix TX performance degradation when IOMMU is enabled Eason Lai
2026-07-09 7:55 ` [PATCH 1/3] wifi: mt76: Separate skb and page_pool_buf pointers in mt76_txwi_cache Eason Lai
2026-07-09 8:19 ` Lorenzo Bianconi
2026-07-09 7:55 ` [PATCH 2/3] wifi: mt76: mt792x: Add TX page pool support for IOMMU-enabled systems Eason Lai
2026-07-09 8:27 ` Lorenzo Bianconi
2026-07-09 7:55 ` [PATCH 3/3] wifi: mt76: mt792x: Restrict TX page pool to MT8196 platform Eason Lai
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox