[PATCH 2/3] wifi: mt76: mt792x: Add TX page pool support for IOMMU-enabled systems
Lorenzo Bianconi
lorenzo at kernel.org
Thu Jul 9 01:27:04 PDT 2026
On Jul 09, Eason Lai wrote:
> From: Eason Lai <Eason.Lai at 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 at 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
>
-------------- 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/20260709/6def45d0/attachment.sig>
More information about the Linux-mediatek
mailing list