[PATCH net-next v4 06/15] net: ethernet: cortina: Index free queue fragments with XArray
flat view
COOLING8d
SUPERSEDED
From: Linus Walleij <linusw@kernel.org>
Date: 2026-10-11 18:06:36
Subsystem:
arm/cortina systems gemini arm architecture, networking drivers, the rest · Maintainers:
Hans Ulli Kroll, Linus Walleij, Andrew Lunn, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds
Revision v4 of 4 in this series.
Revisions (4)
- v2 [diff vs current]
- v3 [diff vs current]
- v4 current
- v5 [diff vs current]
RX descriptors return the DMA address of the free queue fragment used by the hardware. The driver currently recovers its page by scanning every free queue page and comparing address ranges. This isn't very efficient... Instead index each fragment in an XArray using its DMA address in fragment units. Populate and erase the fragment index under the free queue lock. The lookup remains serialized with refill and validates the exact fragment address before returning the page and offset snapshot. Assisted-by: LLM Signed-off-by: Linus Walleij <linusw@kernel.org> --- drivers/net/ethernet/cortina/gemini.c | 105 ++++++++++++++++++++++++---------- 1 file changed, 76 insertions(+), 29 deletions(-)
diff --git a/drivers/net/ethernet/cortina/gemini.c b/drivers/net/ethernet/cortina/gemini.c
index 3b915a4c8900..dc82c896fa00 100644
--- a/drivers/net/ethernet/cortina/gemini.c
+++ b/drivers/net/ethernet/cortina/gemini.c@@ -38,6 +38,7 @@ #include <linux/ethtool.h> #include <linux/tcp.h> #include <linux/u64_stats_sync.h> +#include <linux/xarray.h> #include <linux/in.h> #include <linux/ip.h>
@@ -167,6 +168,7 @@ struct gemini_ethernet { struct gmac_rxdesc *freeq_ring; dma_addr_t freeq_dma_base; struct gmac_queue_page *freeq_pages; + struct xarray freeq_mappings; unsigned int num_freeq_pages; spinlock_t freeq_lock; /* Locks queue from reentrance */ };
@@ -726,43 +728,47 @@ static int gmac_setup_rxq(struct net_device *netdev) return 0; } +static unsigned long +geth_freeq_mapping_index(const struct gemini_ethernet *geth, + dma_addr_t mapping) +{ + return (unsigned long)(mapping >> geth->freeq_frag_order); +} + static struct page *geth_freeq_lookup(struct gemini_ethernet *geth, dma_addr_t mapping, unsigned int *page_offs) { unsigned int frag_len = 1 << geth->freeq_frag_order; struct gmac_queue_page *gpage; + unsigned long index; unsigned long flags; dma_addr_t page_mapping; - struct page *page = NULL; - int i; - - spin_lock_irqsave(&geth->freeq_lock, flags); - if (!geth->freeq_pages) { - dev_err_ratelimited(geth->dev, - "try to get page with no page list\n"); - goto unlock; - } - - /* Look up a ring buffer page from virtual mapping */ - for (i = 0; i < geth->num_freeq_pages; i++) { - gpage = &geth->freeq_pages[i]; - if (!gpage->page || mapping < gpage->mapping) - continue; - - page_mapping = gpage->mapping; - if (mapping - page_mapping > PAGE_SIZE - frag_len || - ((mapping - page_mapping) & (frag_len - 1))) - continue; + struct page *page; + bool valid; - page = gpage->page; - *page_offs = mapping - page_mapping; - break; - } + index = geth_freeq_mapping_index(geth, mapping); -unlock: + spin_lock_irqsave(&geth->freeq_lock, flags); + gpage = xa_load(&geth->freeq_mappings, index); + if (!gpage || !gpage->page) + goto err_unlock; + + page = gpage->page; + page_mapping = gpage->mapping; + valid = mapping >= page_mapping && + mapping - page_mapping <= PAGE_SIZE - frag_len && + !((mapping - page_mapping) & (frag_len - 1)); + if (!valid) + goto err_unlock; + + *page_offs = mapping - page_mapping; spin_unlock_irqrestore(&geth->freeq_lock, flags); return page; + +err_unlock: + spin_unlock_irqrestore(&geth->freeq_lock, flags); + return NULL; } static void gmac_cleanup_rxq(struct net_device *netdev)
@@ -821,12 +827,16 @@ static struct page *geth_freeq_alloc_map_page(struct gemini_ethernet *geth, struct gmac_rxdesc *freeq_entry; struct gmac_queue_page *gpage; unsigned int fpp_order; + unsigned int fragments; unsigned int frag_len; dma_addr_t page_mapping; dma_addr_t mapping; struct page *page; + int ret; int i; + lockdep_assert_held(&geth->freeq_lock); + /* First allocate and DMA map a single page */ page = alloc_page(GFP_ATOMIC); if (!page)
@@ -855,9 +865,26 @@ static struct page *geth_freeq_alloc_map_page(struct gemini_ethernet *geth, */ frag_len = 1 << geth->freeq_frag_order; /* Usually 2048 */ fpp_order = PAGE_SHIFT - geth->freeq_frag_order; + fragments = 1 << fpp_order; + gpage = &geth->freeq_pages[pn]; - if (gpage->page) + for (i = 0; i < fragments; i++) { + mapping = page_mapping + i * frag_len; + ret = xa_insert(&geth->freeq_mappings, + geth_freeq_mapping_index(geth, mapping), + gpage, GFP_ATOMIC); + if (ret) + goto err_mappings; + } + + if (gpage->page) { + for (i = 0; i < fragments; i++) { + mapping = gpage->mapping + i * frag_len; + xa_erase(&geth->freeq_mappings, + geth_freeq_mapping_index(geth, mapping)); + } put_page(gpage->page); + } /* Then put our new mapping into the page table */ gpage->mapping = page_mapping;
@@ -876,6 +903,17 @@ static struct page *geth_freeq_alloc_map_page(struct gemini_ethernet *geth, pn, &page_mapping, page); return page; + +err_mappings: + while (i--) { + mapping = page_mapping + i * frag_len; + xa_erase(&geth->freeq_mappings, + geth_freeq_mapping_index(geth, mapping)); + } + dma_unmap_single(geth->dev, page_mapping, PAGE_SIZE, + DMA_FROM_DEVICE); + put_page(page); + return NULL; } /**
@@ -948,6 +986,8 @@ static int geth_setup_freeq(struct gemini_ethernet *geth) unsigned int pages = len >> fpp_order; union queue_threshold qt; union dma_skb_size skbsz; + unsigned long flags; + struct page *page; unsigned int filled; unsigned int pn;
@@ -961,16 +1001,20 @@ static int geth_setup_freeq(struct gemini_ethernet *geth) goto err_freeq; } - /* Allocate a mapping to page look-up index */ + /* Allocate page metadata referenced by the mapping index */ geth->freeq_pages = kzalloc_objs(*geth->freeq_pages, pages); if (!geth->freeq_pages) goto err_freeq; geth->num_freeq_pages = pages; dev_info(geth->dev, "allocate %d pages for queue\n", pages); - for (pn = 0; pn < pages; pn++) - if (!geth_freeq_alloc_map_page(geth, pn)) + for (pn = 0; pn < pages; pn++) { + spin_lock_irqsave(&geth->freeq_lock, flags); + page = geth_freeq_alloc_map_page(geth, pn); + spin_unlock_irqrestore(&geth->freeq_lock, flags); + if (!page) goto err_freeq_alloc; + } /* Discard pointer state belonging to the previous queue geometry. */ writel(0, geth->base + GLOBAL_SWFQ_RWPTR_REG);
@@ -1000,6 +1044,7 @@ static int geth_setup_freeq(struct gemini_ethernet *geth) DMA_FROM_DEVICE); put_page(gpage->page); } + xa_destroy(&geth->freeq_mappings); kfree(geth->freeq_pages); geth->freeq_pages = NULL;
@@ -1041,6 +1086,7 @@ static void geth_cleanup_freeq(struct gemini_ethernet *geth) while (page_ref_count(gpage->page) > 0) put_page(gpage->page); } + xa_destroy(&geth->freeq_mappings); kfree(geth->freeq_pages); geth->freeq_pages = NULL;
@@ -2740,6 +2786,7 @@ static int gemini_ethernet_probe(struct platform_device *pdev) spin_lock_init(&geth->irq_lock); spin_lock_init(&geth->freeq_lock); + xa_init(&geth->freeq_mappings); /* The children will use this */ platform_set_drvdata(pdev, geth);
--
2.56.0