From: Luigi Rizzo <lrizzo@google.com>
To: Luigi Rizzo <rizzo.unipi@gmail.com>,
Joerg Roedel <joro@8bytes.org>, Will Deacon <will@kernel.org>,
Robin Murphy <robin.murphy@arm.com>,
Christoph Hellwig <hch@lst.de>,
Marek Szyprowski <m.szyprowski@samsung.com>,
Andrew Morton <akpm@linux-foundation.org>,
Vlastimil Babka <vbabka@kernel.org>,
David Hildenbrand <david@kernel.org>,
Jens Axboe <axboe@kernel.dk>,
"David S . Miller" <davem@davemloft.net>,
Eric Dumazet <edumazet@kernel.org>,
Jakub Kicinski <kuba@kernel.org>,
Paolo Abeni <pabeni@redhat.com>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
"Rafael J . Wysocki" <rafael@kernel.org>,
Danilo Krummrich <dakr@kernel.org>,
Jonathan Corbet <corbet@lwn.net>,
Matthew Wilcox <willy@infradead.org>,
Lorenzo Stoakes <ljs@kernel.org>,
Jesper Dangaard Brouer <hawk@kernel.org>,
Ilias Apalodimas <ilias.apalodimas@linaro.org>,
Willem de Bruijn <willemb@google.com>,
Kuniyuki Iwashima <kuniyu@google.com>,
Joshua Washington <joshwash@google.com>,
Harshitha Ramamurthy <hramamurthy@google.com>,
Alexander Lobakin <aleksander.lobakin@intel.com>,
iommu@lists.linux.dev, netdev@vger.kernel.org,
linux-mm@kvack.org, linux-block@vger.kernel.org,
driver-core@lists.linux.dev, linux-doc@vger.kernel.org,
linux-kernel@vger.kernel.org, Luigi Rizzo <lrizzo@google.com>
Subject: [RFC PATCH v2 11/25] iommu/dma: Add per-CPU block caches to PISA allocators
Date: Mon, 9 Nov 2026 18:25:19 +0000 [thread overview]
Message-ID: <20261109182533.3594984-12-lrizzo@google.com> (raw)
In-Reply-To: <20261003212241.3432303-1-lrizzo@google.com>
Every block allocation and free takes the allocator's lock. Highly
concurrent users e.g. socket tx fragments, can cause significant lock
contention.
Add pisa_enable_pcp(), an opt-in per-CPU front cache, similar
to the page allocator's per-CPU lists:
- Each per-CPU cache keeps up to 64 free blocks, and refills it from
the main allocator in batches of 32.
- returned blocks belonging to the local NUMA node are kept in the local
cache.
- returned blocks belonging to a remote NUMA node are returned to the
proper per-node allocator.
The cache is fully integrated with the shrinker and CPU offlining to
drain the cache.
Enable per-CPU caches for the per-node order-0 RW and SKB_FRAG_PAGE_ORDER
RO allocators.
Signed-off-by: Luigi Rizzo <lrizzo@google.com>
---
drivers/iommu/pisa-alloc.c | 342 +++++++++++++++++++++++++++++++++++--
drivers/iommu/pisa-kunit.c | 78 +++++++++
drivers/iommu/pisa-priv.h | 35 +++-
include/linux/pisa.h | 6 +
4 files changed, 448 insertions(+), 13 deletions(-)
diff --git a/drivers/iommu/pisa-alloc.c b/drivers/iommu/pisa-alloc.c
index 25e4645310678..913706a503341 100644
--- a/drivers/iommu/pisa-alloc.c
+++ b/drivers/iommu/pisa-alloc.c
@@ -9,6 +9,7 @@
#include <linux/bitmap.h>
#include <linux/bitops.h>
#include <linux/cache.h>
+#include <linux/cpuhotplug.h>
#include <linux/device.h>
#include <linux/dma-direct.h>
#include <linux/dma-map-ops.h>
@@ -24,6 +25,7 @@
#include <linux/moduleparam.h>
#include <linux/mutex.h>
#include <linux/page_owner.h>
+#include <linux/percpu.h>
#include <linux/pgalloc_tag.h>
#include <linux/rcupdate.h>
#include <linux/sched/mm.h>
@@ -60,6 +62,10 @@ module_param_named(max_idle, pisa_max_idle, uint, 0644);
static unsigned int pisa_idle_ms __read_mostly = 200;
module_param_named(idle_ms, pisa_idle_ms, uint, 0644);
+/* Stop recycling partial-page blocks when free blocks exceed @pisa_compact PISA pages. */
+static unsigned int pisa_compact __read_mostly;
+module_param_named(compact, pisa_compact, uint, 0644);
+
/* All live allocators, protected by pisa_allocs_lock. */
static LIST_HEAD(pisa_allocs);
static DEFINE_RWLOCK(pisa_allocs_lock);
@@ -90,7 +96,11 @@ static void pisa_release_page_rcu(struct rcu_head *head)
struct pisa_meta *meta = container_of(head, struct pisa_meta, rcu);
struct page *block, *page = pfn_to_page(pisa_meta_to_pfn(meta));
unsigned int i, order = meta->pa->order, slot = meta->slot;
+ unsigned long flags;
+ spin_lock_irqsave(&meta->pa->lock, flags);
+ meta->pa->stats.page_free_cnt++;
+ spin_unlock_irqrestore(&meta->pa->lock, flags);
WRITE_ONCE(meta->pa, NULL);
/* Possibly still mapped by a device: leak it, still charged. */
@@ -355,9 +365,223 @@ static void pisa_return_block_locked(struct pisa_alloc *pa, struct page *block,
__set_bit(idx, meta->dirty_bitmap);
meta->nr_dirty++;
}
+ pa->blocks_in_use--;
*release |= pisa_place_meta(meta);
}
+/**
+ * pisa_page_can_recycle - Check if a block can stay cached in a caller pool
+ * @page: Block head page
+ *
+ * Called on the recycle fast path (per-CPU cache, TX page_frag, page_pool).
+ * Fast-path cost is a static-key branch plus a READ_ONCE() of module parameters
+ * (~1-2 ns when compaction is off); when enabled, it does a lockless RCU +
+ * metadata array lookup (~5-10 ns).
+ *
+ * Return: true if @page can be recycled in place, false if it should be
+ * released back to its allocator so sparse 2MB pages can drain.
+ */
+bool pisa_page_can_recycle(const struct page *page)
+{
+ unsigned long bpp, in_use, free_blocks, pfn;
+ const struct pisa_meta *meta;
+ const struct pisa_alloc *pa;
+ unsigned int compact;
+ bool ret = true;
+ u8 on_list;
+ long held;
+
+ if (!static_branch_unlikely(&pisa_key))
+ return true;
+
+ compact = READ_ONCE(pisa_compact);
+ if (!compact)
+ return true;
+
+ pfn = page_to_pfn(page);
+ rcu_read_lock();
+ if (!is_pisa_pfn(pfn))
+ goto out;
+
+ meta = pisa_meta_of_pfn(pfn);
+ on_list = READ_ONCE(meta->on_list);
+ if (on_list == PISA_LIST_FULL || on_list == PISA_LIST_NONE)
+ goto out;
+
+ pa = meta->pa;
+ if (!pa)
+ goto out;
+
+ /* Do not drain the partial page currently being filled at list head. */
+ if (READ_ONCE(pa->partial_dirty.next) == &meta->list ||
+ READ_ONCE(pa->partial.next) == &meta->list)
+ goto out;
+
+ bpp = PISA_BLOCKS(pa->order);
+ /* Do not drain dense pages that still have more than half their blocks in use. */
+ if (READ_ONCE(meta->nr_clean) + READ_ONCE(meta->nr_dirty) < (bpp >> 1))
+ goto out;
+
+ held = pisa_alloc_held_pages(pa);
+ in_use = READ_ONCE(pa->blocks_in_use);
+ free_blocks = held > 0 && (unsigned long)held * bpp > in_use ?
+ (unsigned long)held * bpp - in_use : 0;
+
+ ret = free_blocks <= (unsigned long)compact * bpp;
+out:
+ rcu_read_unlock();
+ return ret;
+}
+EXPORT_SYMBOL_GPL(pisa_page_can_recycle);
+
+/* Return @n freed blocks to @pa under a single acquisition of @pa->lock. */
+static void pisa_free_bulk(struct pisa_alloc *pa, struct page **blocks, unsigned int n)
+{
+ bool release = false;
+ unsigned long flags;
+ unsigned int i;
+
+ spin_lock_irqsave(&pa->lock, flags);
+ for (i = 0; i < n; i++) {
+ if (likely(!pisa_block_poisoned(pa, blocks[i])))
+ pisa_return_block_locked(pa, blocks[i], &release);
+ }
+ spin_unlock_irqrestore(&pa->lock, flags);
+
+ if (release)
+ pisa_schedule_reclaim();
+}
+
+/*
+ * Per-CPU block cache: each CPU caches up to PISA_PCP_HIGH free blocks
+ * (refcount 0, not in bitmaps), refilling and flushing in PISA_PCP_BATCH
+ * batches. Drained by the shrinker and CPU hotplug.
+ */
+
+/* Max blocks moved per per-CPU cache refill or flush (1..PISA_PCP_BATCH). */
+static unsigned int pisa_pcp_batch __read_mostly = PISA_PCP_BATCH;
+
+static int pisa_pcp_batch_set(const char *val, const struct kernel_param *kp)
+{
+ return param_set_uint_minmax(val, kp, 1, PISA_PCP_BATCH);
+}
+
+static const struct kernel_param_ops pisa_pcp_batch_ops = {
+ .set = pisa_pcp_batch_set,
+ .get = param_get_uint,
+};
+module_param_cb(pcp_batch, &pisa_pcp_batch_ops, &pisa_pcp_batch, 0644);
+
+/* Cache @n blocks at refcount 0 on this CPU, @n <= PISA_PCP_BATCH. */
+static void pisa_pcp_push(struct pisa_alloc *pa, struct page **blocks, unsigned int n)
+{
+ unsigned int i, batch = READ_ONCE(pisa_pcp_batch);
+ struct pisa_pcp *pcp;
+ unsigned long flags;
+
+ local_irq_save(flags);
+ pcp = this_cpu_ptr(pa->pcp);
+ spin_lock(&pcp->lock);
+ for (i = 0; i < n; i++) {
+ if (pcp->count == PISA_PCP_HIGH) {
+ /* Keep the most recently freed, likely still in cache. */
+ pisa_free_bulk(pa, pcp->blocks, batch);
+ pcp->count -= batch;
+ memmove(pcp->blocks, pcp->blocks + batch,
+ pcp->count * sizeof(*pcp->blocks));
+ }
+ pcp->blocks[pcp->count++] = blocks[i];
+ }
+ spin_unlock(&pcp->lock);
+ local_irq_restore(flags);
+}
+
+/* Take up to @nr blocks, at refcount 0, from this CPU's cache. */
+static unsigned int pisa_pcp_pop(struct pisa_alloc *pa, unsigned int nr, struct page **out)
+{
+ struct pisa_pcp *pcp;
+ unsigned int got = 0;
+ unsigned long flags;
+
+ local_irq_save(flags);
+ pcp = this_cpu_ptr(pa->pcp);
+ spin_lock(&pcp->lock);
+ while (got < nr && pcp->count)
+ out[got++] = pcp->blocks[--pcp->count];
+ spin_unlock(&pcp->lock);
+ local_irq_restore(flags);
+
+ return got;
+}
+
+/* Return the blocks @cpu caches for @pa. Process context. */
+static void pisa_pcp_drain_cpu(struct pisa_alloc *pa, int cpu)
+{
+ struct pisa_pcp *pcp = per_cpu_ptr(pa->pcp, cpu);
+ unsigned long flags;
+
+ spin_lock_irqsave(&pcp->lock, flags);
+ if (pcp->count) {
+ pisa_free_bulk(pa, pcp->blocks, pcp->count);
+ pcp->count = 0;
+ }
+ spin_unlock_irqrestore(&pcp->lock, flags);
+}
+
+/* Drain every non-empty CPU cache of @pa. */
+void pisa_pcp_drain(struct pisa_alloc *pa)
+{
+ int cpu;
+
+ /* Pairs with smp_store_release() in pisa_enable_pcp(). */
+ if (!smp_load_acquire(&pa->pcp))
+ return;
+ for_each_possible_cpu(cpu) {
+ if (!READ_ONCE(per_cpu_ptr(pa->pcp, cpu)->count))
+ continue;
+ pisa_pcp_drain_cpu(pa, cpu);
+ }
+}
+
+/* Blocks in @pa's caches, for shrinker and debugfs. */
+static unsigned long pisa_pcp_count(struct pisa_alloc *pa)
+{
+ unsigned long n = 0;
+ int cpu;
+
+ if (!pa->pcp)
+ return 0;
+ for_each_possible_cpu(cpu) {
+ /* A racy sum is good enough for reclaim and statistics. */
+ n += data_race(per_cpu_ptr(pa->pcp, cpu)->count);
+ }
+ return n;
+}
+
+/**
+ * pisa_enable_pcp - Put per-CPU block caches in front of an allocator
+ * @pa: Allocator to cache
+ *
+ * Return: 0, or -ENOMEM; @pa works without the cache.
+ */
+int pisa_enable_pcp(struct pisa_alloc *pa)
+{
+ struct pisa_pcp __percpu *pcp;
+ int cpu;
+
+ if (!pa || pa->pcp)
+ return 0;
+ pcp = alloc_percpu(struct pisa_pcp);
+ if (!pcp)
+ return -ENOMEM;
+ for_each_possible_cpu(cpu)
+ spin_lock_init(&per_cpu_ptr(pcp, cpu)->lock);
+ /* Publish initialized locks; readers depend on the pointer. */
+ if (cmpxchg_release(&pa->pcp, NULL, pcp))
+ free_percpu(pcp);
+ return 0;
+}
+
/*
* Return the predefined per-node allocator for (@nid, @order, @ro), or NULL.
*/
@@ -412,7 +636,9 @@ bool pisa_has_free_blocks_rx(const struct device *dev, unsigned int order, int n
return false;
pa = pisa_node_pa(nid, order, false);
return pa &&
- (!list_empty_careful(&pa->partial) ||
+ /* Pairs with cmpxchg_release() in pisa_enable_pcp(). */
+ ((smp_load_acquire(&pa->pcp) && raw_cpu_read(pa->pcp->count)) ||
+ !list_empty_careful(&pa->partial) ||
!list_empty_careful(&pa->partial_dirty) ||
!list_empty_careful(&pa->idle));
}
@@ -532,6 +758,7 @@ static unsigned int pisa_scan_bitmap(struct pisa_meta *meta, bool dirty,
break;
}
*countp -= used;
+ pa->blocks_in_use += used;
return got;
}
@@ -638,6 +865,7 @@ static unsigned long __pisa_alloc_bulk(struct pisa_alloc *pa, gfp_t gfp, unsigne
meta->prot = pa->prot;
/* Set PISA_ACTIVE after @meta is fully set up. */
WRITE_ONCE(meta->flags, meta->flags | PISA_ACTIVE);
+ pa->stats.page_alloc_cnt++;
}
spin_unlock_irqrestore(&pa->lock, flags);
@@ -656,6 +884,49 @@ static unsigned long __pisa_alloc_bulk(struct pisa_alloc *pa, gfp_t gfp, unsigne
return n;
}
+/* Allocate @nr_pages blocks through this CPU's cache, refilling in batches on miss. */
+static unsigned long pisa_pcp_alloc(struct pisa_alloc *pa, gfp_t gfp, unsigned long nr_pages,
+ struct page **page_array, bool frozen)
+{
+ unsigned int pcp_batch = READ_ONCE(pisa_pcp_batch);
+ unsigned long got, n, i, j;
+
+ got = pisa_pcp_pop(pa, nr_pages, page_array);
+ if (got < nr_pages) {
+ if (nr_pages - got >= pcp_batch) {
+ got += __pisa_alloc_bulk(pa, gfp, nr_pages - got,
+ page_array + got, false, true);
+ } else {
+ struct page *batch[PISA_PCP_BATCH];
+
+ n = __pisa_alloc_bulk(pa, gfp, pcp_batch, batch, false, true);
+ for (i = 0; i < n && got < nr_pages; i++)
+ page_array[got++] = batch[i];
+ if (i < n)
+ pisa_pcp_push(pa, batch + i, n - i);
+ }
+ }
+
+ for (i = j = 0; i < got; i++) {
+ struct page *block = page_array[i];
+
+ page_array[i] = NULL;
+ /* Poisoned while cached: leak it, as pisa_scan_bitmap() does. */
+ if (pisa_block_poisoned(pa, block))
+ continue;
+ pisa_block_prep(block);
+ if (want_init_on_alloc(gfp)) {
+ memset(page_address(block), 0, PAGE_SIZE << pa->order);
+ if (frozen) /* Order zeroing before caller publishes the page. */
+ smp_wmb();
+ }
+ if (!frozen)
+ page_ref_unfreeze(block, 1);
+ page_array[j++] = block;
+ }
+ return j;
+}
+
/**
* pisa_alloc_bulk - Allocate up to @nr_pages blocks from @pa
* @pa: Allocator to allocate from
@@ -681,11 +952,16 @@ unsigned long pisa_alloc_bulk(struct pisa_alloc *pa, gfp_t gfp,
i++;
continue;
}
+
/* Fill the run of @n NULL entries at @i. */
for (n = 1; i + n < nr_pages && !page_array[i + n]; n++)
;
- got = __pisa_alloc_bulk(pa, gfp, n, page_array + i,
- want_init_on_alloc(gfp), frozen);
+ /* Pairs with smp_store_release() in pisa_enable_pcp(). */
+ if (smp_load_acquire(&pa->pcp))
+ got = pisa_pcp_alloc(pa, gfp, n, page_array + i, frozen);
+ else
+ got = __pisa_alloc_bulk(pa, gfp, n, page_array + i,
+ want_init_on_alloc(gfp), frozen);
i += got;
if (got < n)
break;
@@ -733,10 +1009,13 @@ static struct page *pisa_take_contig(struct pisa_meta *meta, unsigned int nblock
for (i = 0; i < nblocks; i++) {
if (!pisa_block_poisoned(pa, page + i))
continue;
- if (__test_and_clear_bit(off + i, meta->clean_bitmap))
+ if (__test_and_clear_bit(off + i, meta->clean_bitmap)) {
meta->nr_clean--;
- else if (__test_and_clear_bit(off + i, meta->dirty_bitmap))
+ pa->blocks_in_use++;
+ } else if (__test_and_clear_bit(off + i, meta->dirty_bitmap)) {
meta->nr_dirty--;
+ pa->blocks_in_use++;
+ }
__set_bit(off + i, busy);
poisoned = true;
off += i + 1;
@@ -760,6 +1039,7 @@ static struct page *pisa_take_contig(struct pisa_meta *meta, unsigned int nblock
pisa_block_prep(page + i);
page_ref_unfreeze(page + i, 1);
}
+ pa->blocks_in_use += nblocks;
pisa_place_meta(meta);
return page;
}
@@ -806,6 +1086,7 @@ void *pisa_alloc_coherent(struct page **pagep, struct device *dev,
pa = pisa_node_pa(dev_to_node(dev), 0, false);
if (!pa)
return NULL;
+ pisa_pcp_drain(pa);
nblocks = size >> PAGE_SHIFT;
align_mask = (1UL << get_order(size)) - 1;
@@ -830,6 +1111,7 @@ void *pisa_alloc_coherent(struct page **pagep, struct device *dev,
meta->pa = pa;
meta->prot = pa->prot;
WRITE_ONCE(meta->flags, meta->flags | PISA_ACTIVE);
+ pa->stats.page_alloc_cnt++;
page = pisa_take_contig(meta, nblocks, align_mask);
if (unlikely(!page))
should_release = pisa_place_meta(meta);
@@ -876,6 +1158,17 @@ bool __pisa_free_page(struct page *page)
if (want_init_on_free())
memset(page_address(page), 0, PAGE_SIZE << pa->order);
+ /* Pairs with cmpxchg_release() in pisa_enable_pcp(). */
+ if (smp_load_acquire(&pa->pcp) && pa->nid == numa_mem_id() &&
+ page_to_nid(page) == numa_mem_id()) {
+ if (pisa_page_can_recycle(page)) {
+ pisa_pcp_push(pa, &page, 1);
+ return true;
+ }
+ if (this_cpu_read(pa->pcp->count))
+ pisa_pcp_drain_cpu(pa, raw_smp_processor_id());
+ }
+
spin_lock_irqsave(&pa->lock, flags);
pisa_return_block_locked(pa, page, &should_release);
spin_unlock_irqrestore(&pa->lock, flags);
@@ -889,15 +1182,17 @@ bool __pisa_free_page(struct page *page)
/* Shrinker: releases reservoir pages first, then idle allocator pages. */
static unsigned long pisa_shrink_count(struct shrinker *shrink, struct shrink_control *sc)
{
- unsigned long nr = pisa_reservoir_count();
+ unsigned long nr = pisa_reservoir_count() << PISA_ORDER;
struct pisa_alloc *pa;
read_lock(&pisa_allocs_lock);
- list_for_each_entry(pa, &pisa_allocs, node)
- nr += READ_ONCE(pa->num_idle_pages);
+ list_for_each_entry(pa, &pisa_allocs, node) {
+ nr += (unsigned long)READ_ONCE(pa->num_idle_pages) << PISA_ORDER;
+ nr += pisa_pcp_count(pa) << pa->order;
+ }
read_unlock(&pisa_allocs_lock);
- return nr << PISA_ORDER;
+ return nr;
}
/* Detach PISA pages from @list onto @release_list up to @nr_to_scan base pages. */
@@ -930,6 +1225,8 @@ VISIBLE_IF_KUNIT unsigned long pisa_shrink_scan(struct shrinker *shrink, struct
if (freed < sc->nr_to_scan) {
read_lock(&pisa_allocs_lock);
list_for_each_entry(pa, &pisa_allocs, node) {
+ /* Cached blocks keep PISA pages from going idle. */
+ pisa_pcp_drain(pa);
freed += pisa_shrink_list(&pa->lock, &pa->idle,
&pa->num_idle_pages, &release_list,
sc->nr_to_scan - freed);
@@ -952,15 +1249,30 @@ VISIBLE_IF_KUNIT unsigned long pisa_shrink_scan(struct shrinker *shrink, struct
return freed ?: SHRINK_STOP;
}
+/* Return the blocks a dead CPU cached, which nothing else would. */
+static int pisa_cpu_dead(unsigned int cpu)
+{
+ struct pisa_alloc *pa;
+
+ read_lock(&pisa_allocs_lock);
+ list_for_each_entry(pa, &pisa_allocs, node)
+ /* Pairs with smp_store_release() in pisa_enable_pcp(). */
+ if (smp_load_acquire(&pa->pcp))
+ pisa_pcp_drain_cpu(pa, cpu);
+ read_unlock(&pisa_allocs_lock);
+ return 0;
+}
+
static int __init pisa_init(void)
{
const struct {
u8 order;
bool ro;
+ bool pcp;
} cfg[] = {
- { 0, false },
- { 2, true },
- { SKB_FRAG_PAGE_ORDER, true },
+ { 0, false, true },
+ { 2, true, false },
+ { SKB_FRAG_PAGE_ORDER, true, true },
};
struct shrinker *shrink;
int nid, i;
@@ -986,6 +1298,8 @@ static int __init pisa_init(void)
if (!pa)
continue;
pisa_set_name(pa, "pisa%s_o%u_n%d", ro ? "_ro" : "", order, nid);
+ if (cfg[i].pcp)
+ pisa_enable_pcp(pa);
pisa_nodes[nid].pa[ro][order] = pa;
}
}
@@ -995,6 +1309,10 @@ static int __init pisa_init(void)
if (!pisa_wq)
pr_warn("pisa: no reclaim workqueue, falling back to system_wq\n");
+ if (cpuhp_setup_state_nocalls(CPUHP_BP_PREPARE_DYN, "iommu/pisa:dead", NULL,
+ pisa_cpu_dead) < 0)
+ pr_warn("pisa: no CPU hotplug callback, offline CPUs keep cached blocks\n");
+
shrink = shrinker_alloc(0, "pisa");
if (!shrink) {
pr_warn("pisa: shrinker registration failed\n");
diff --git a/drivers/iommu/pisa-kunit.c b/drivers/iommu/pisa-kunit.c
index 72ee8986b4802..5bbb540eb505e 100644
--- a/drivers/iommu/pisa-kunit.c
+++ b/drivers/iommu/pisa-kunit.c
@@ -482,6 +482,82 @@ static void test_dev_pisa_attrs(struct kunit *test)
device_unregister(dev);
}
+static void test_pcp_cache(struct kunit *test)
+{
+ struct page *blocks[PISA_PCP_HIGH + 1];
+ struct pisa_alloc *pa;
+ struct page *p1, *p2;
+ unsigned int i, n = 0;
+
+ KUNIT_EXPECT_EQ(test, pisa_enable_pcp(NULL), 0);
+ pa = pisa_node_pa(first_memory_node, 0, false);
+ KUNIT_ASSERT_NOT_NULL(test, pa);
+ KUNIT_ASSERT_EQ(test, pisa_enable_pcp(pa), 0);
+
+ p1 = pisa_alloc(pa, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, p1);
+ __free_page(p1);
+ p2 = pisa_alloc(pa, GFP_KERNEL | __GFP_ZERO);
+ KUNIT_EXPECT_PTR_EQ(test, p2, p1);
+ if (p2)
+ __free_page(p2);
+
+ /* Overflowing PISA_PCP_HIGH flushes a batch back to the allocator. */
+ for (i = 0; i < ARRAY_SIZE(blocks); i++) {
+ blocks[i] = pisa_alloc(pa, GFP_KERNEL);
+ if (!blocks[i])
+ break;
+ n++;
+ }
+ KUNIT_EXPECT_EQ(test, n, (unsigned int)ARRAY_SIZE(blocks));
+ for (i = 0; i < n; i++)
+ __free_page(blocks[i]);
+}
+
+static void test_gfp_pisa(struct kunit *test)
+{
+ struct shrink_control sc = { .nr_to_scan = ULONG_MAX };
+ struct device dev = { .numa_node = first_memory_node };
+ struct page *p, *p_ro, *bulk[4] = {};
+ unsigned long n;
+ int i;
+
+ pisa_shrink_scan(NULL, &sc);
+ KUNIT_EXPECT_EQ(test, pisa_rx_gfp(NULL), 0);
+ KUNIT_EXPECT_EQ(test, pisa_rx_gfp(&dev), 0);
+ KUNIT_EXPECT_FALSE(test, pisa_has_free_blocks_rx(&dev, 0, first_memory_node));
+
+ dev.pisa_rx_bufs = true;
+ KUNIT_EXPECT_EQ(test, pisa_rx_gfp(&dev), __GFP_PISA_RW);
+
+ p = alloc_pages_node(first_memory_node, GFP_KERNEL | pisa_rx_gfp(&dev), 0);
+ KUNIT_ASSERT_NOT_NULL(test, p);
+ if (p) {
+ KUNIT_EXPECT_TRUE(test, is_pisa_page(p));
+ KUNIT_EXPECT_EQ(test, pisa_meta_of_pfn(page_to_pfn(p))->prot,
+ IOMMU_READ | IOMMU_WRITE);
+ __free_page(p);
+ KUNIT_EXPECT_TRUE(test, pisa_has_free_blocks_rx(&dev, 0, first_memory_node));
+ }
+
+ p_ro = alloc_pages(GFP_KERNEL | __GFP_COMP | __GFP_PISA_RO, 3);
+ KUNIT_ASSERT_NOT_NULL(test, p_ro);
+ if (p_ro) {
+ KUNIT_EXPECT_TRUE(test, is_pisa_page(p_ro));
+ KUNIT_EXPECT_EQ(test, pisa_meta_of_pfn(page_to_pfn(p_ro))->prot,
+ IOMMU_READ);
+ __free_pages(p_ro, 3);
+ }
+
+ n = alloc_pages_bulk_node(GFP_KERNEL | __GFP_PISA_RW, first_memory_node,
+ ARRAY_SIZE(bulk), bulk);
+ KUNIT_EXPECT_EQ(test, n, ARRAY_SIZE(bulk));
+ for (i = 0; i < n; i++) {
+ KUNIT_EXPECT_TRUE(test, is_pisa_page(bulk[i]));
+ __free_page(bulk[i]);
+ }
+}
+
static void test_reservoir_and_scrubber(struct kunit *test)
{
KUNIT_ASSERT_EQ(test, pisa_meta_init(), 0);
@@ -499,6 +575,8 @@ static struct kunit_case pisa_meta_test_cases[] = {
#endif
KUNIT_CASE(test_coherent_alloc_and_free),
KUNIT_CASE(test_dev_pisa_attrs),
+ KUNIT_CASE(test_pcp_cache),
+ KUNIT_CASE(test_gfp_pisa),
KUNIT_CASE(test_reservoir_and_scrubber),
{}
};
diff --git a/drivers/iommu/pisa-priv.h b/drivers/iommu/pisa-priv.h
index e5ca95d2654cd..8f87bdf7d4ee2 100644
--- a/drivers/iommu/pisa-priv.h
+++ b/drivers/iommu/pisa-priv.h
@@ -53,6 +53,7 @@ enum {
* Lock hierarchy:
* - pisa_allocs_lock (rwlock): outermost; protects pisa_allocs list.
* - pisa_meta_mutex (mutex): serializes pisa_meta_init() and page-table alloc.
+ * - pcp->lock (spinlock, irqsave): per-CPU block cache.
* - pa->lock (spinlock, irqsave): per-allocator lists, bitmaps, and stats.
* - pisa_domains[].map_lock (spinlock, irqsave): first-time iommu_map() in __pisa_map_phys().
* - pisa_domains_lock (spinlock, irqsave): leaf lock over pisa_domains[]/windows.
@@ -105,11 +106,28 @@ static inline unsigned int pisa_meta_avail(const struct pisa_meta *meta)
return meta->nr_clean + meta->nr_dirty;
}
+/* Per-CPU front cache of free blocks at refcount 0, see pisa_enable_pcp(). */
+#define PISA_PCP_HIGH 64U /* blocks cached per CPU at most */
+#define PISA_PCP_BATCH 32U /* blocks moved per refill or flush */
+
+struct pisa_pcp {
+ spinlock_t lock; /* by this CPU, or by a drain from any CPU */
+ unsigned int count;
+ struct page *blocks[PISA_PCP_HIGH];
+};
+
+/* Per-allocator statistics, see pisa_allocs_show(). */
+struct pisa_alloc_stats {
+ u64 page_alloc_cnt;
+ u64 page_free_cnt;
+};
+
/**
* struct pisa_alloc - Allocator managing 2MB-backed blocks of a fixed order
- * @lock: Spinlock protecting lists and block bitmaps
+ * @lock: Spinlock protecting lists, block bitmaps, and u64 @stats
* @order: Block order managed by this allocator (<= PISA_ORDER)
* @prot: IOMMU_READ and/or IOMMU_WRITE from @dir passed to pisa_create()
+ * @pcp: Per-CPU block caches, or NULL (see pisa_enable_pcp())
* @partial: Partially used PISA pages with @nr_clean > 0
* @partial_dirty: Partially used PISA pages with @nr_clean == 0 and @nr_dirty > 0
* @idle: Completely free PISA pages (reclaimable under pressure)
@@ -117,15 +135,18 @@ static inline unsigned int pisa_meta_avail(const struct pisa_meta *meta)
* @num_idle_pages: Length of @idle
* @max_idle_pages: High watermark of @idle before async release to the buddy allocator
* @idle_jiffies: Timestamp (jiffies) when @idle last exceeded @max_idle_pages
+ * @blocks_in_use: Blocks handed out and not yet freed
* @nid: NUMA node id
* @node: Node in the global pisa_allocs list
* @name: Debug name (defaults to caller of pisa_create())
+ * @stats: Per-allocator statistics
*/
struct pisa_alloc {
/* First cacheline: hot fields touched on block alloc and free. */
spinlock_t lock;
u8 order;
u8 prot;
+ struct pisa_pcp __percpu *pcp;
struct list_head partial;
struct list_head partial_dirty;
struct list_head idle;
@@ -133,12 +154,22 @@ struct pisa_alloc {
unsigned int num_idle_pages;
unsigned int max_idle_pages;
unsigned long idle_jiffies;
+ unsigned long blocks_in_use;
/* Cold. */
int nid;
struct list_head node;
char name[48];
+ struct pisa_alloc_stats stats;
} ____cacheline_aligned;
+/* Active non-idle PISA pages held by @pa, for soft compaction checks. */
+static inline long pisa_alloc_held_pages(const struct pisa_alloc *pa)
+{
+ return (long)(READ_ONCE(pa->stats.page_alloc_cnt) -
+ READ_ONCE(pa->stats.page_free_cnt)) -
+ READ_ONCE(pa->num_idle_pages);
+}
+
/* Per-NUMA-node PISA state. */
struct pisa_node {
unsigned long next_alloc; /* atomic alloc backoff jiffies */
@@ -202,6 +233,8 @@ struct pisa_alloc *pisa_create(int nid, unsigned int order, enum dma_data_direct
struct pisa_alloc *pisa_node_pa(int nid, unsigned int order, bool ro);
__printf(2, 3)
void pisa_set_name(struct pisa_alloc *pa, const char *fmt, ...);
+int pisa_enable_pcp(struct pisa_alloc *pa);
+void pisa_pcp_drain(struct pisa_alloc *pa);
void pisa_release_page(struct pisa_meta *meta);
unsigned long pisa_alloc_bulk(struct pisa_alloc *pa, gfp_t gfp,
unsigned long nr_pages, struct page **page_array, bool frozen);
diff --git a/include/linux/pisa.h b/include/linux/pisa.h
index a6556aa403a09..cdf176464d74a 100644
--- a/include/linux/pisa.h
+++ b/include/linux/pisa.h
@@ -100,6 +100,7 @@ unsigned long pisa_alloc_pages_bulk(gfp_t gfp, unsigned int order,
bool frozen);
bool pisa_has_free_blocks_rx(const struct device *dev, unsigned int order, int nid);
+bool pisa_page_can_recycle(const struct page *page);
static inline gfp_t pisa_rx_gfp(const struct device *dev)
{
@@ -139,6 +140,11 @@ static inline bool pisa_free_coherent(struct page *page, size_t size)
return false;
}
+static inline bool pisa_page_can_recycle(const struct page *page)
+{
+ return true;
+}
+
static inline unsigned long
pisa_alloc_pages_bulk(gfp_t gfp, unsigned int order, int preferred_nid,
nodemask_t *nodemask, int nr_pages,
--
2.56.0.542.g3ef89b7020-goog
next prev parent reply other threads:[~2026-11-09 18:25 UTC|newest]
Thread overview: 86+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-03 21:22 [RFC: DMA_PMD 00/22] DMA_PMD: PMD_SIZE-backed IO buffer pools Luigi Rizzo
2026-10-03 21:22 ` [RFC: DMA_PMD 01/22] iommu/dma: introduce CONFIG_DMA_PMD and metadata table Luigi Rizzo
2026-10-03 21:44 ` Randy Dunlap
2026-10-04 9:14 ` Luigi Rizzo
2026-10-03 21:22 ` [RFC: DMA_PMD 02/22] iommu/dma: add DMA_PMD pool lifecycle and page recycle hook Luigi Rizzo
2026-10-10 2:59 ` kernel test robot
2026-10-03 21:22 ` [RFC: DMA_PMD 03/22] mm: Add split_page_compound() Luigi Rizzo
2026-10-03 21:22 ` [RFC: DMA_PMD 04/22] iommu/dma: add DMA_PMD pool block allocation Luigi Rizzo
2026-10-03 21:22 ` [RFC: DMA_PMD 05/22] iommu/dma: Global cap and shrinker for DMA_PMD pool memory Luigi Rizzo
2026-10-03 21:22 ` [RFC: DMA_PMD 06/22] iommu/dma: reserve a per-domain IOVA window for DMA_PMD pages Luigi Rizzo
2026-10-03 21:22 ` [RFC: DMA_PMD 07/22] iommu/dma: release DMA_PMD domain mappings on domain teardown Luigi Rizzo
2026-10-03 21:22 ` [RFC: DMA_PMD 08/22] iommu/dma: use per-domain IOVA window to map DMA_PMD memory Luigi Rizzo
2026-10-03 21:22 ` [RFC: DMA_PMD 09/22] iommu/dma: Add DMA_PMD arena allocator Luigi Rizzo
2026-10-03 21:22 ` [RFC: DMA_PMD 10/22] driver core: Add per-device dma_pmd_* sysfs attributes Luigi Rizzo
2026-10-03 21:22 ` [RFC: DMA_PMD 11/22] dma-mapping: Use DMA_PMD arena for dma_alloc_attrs() Luigi Rizzo
2026-10-03 21:22 ` [RFC: DMA_PMD 12/22] net/core: Use per-CPU DMA_PMD pools for skb_page_frag_refill() Luigi Rizzo
2026-10-03 21:22 ` [RFC: DMA_PMD 13/22] net/core: Use DMA_PMD for page_pool memory Luigi Rizzo
2026-10-03 21:22 ` [RFC: DMA_PMD 14/22] iommu/dma: Support decrypted and pinned DMA_PMD pages Luigi Rizzo
2026-10-03 21:22 ` [RFC: DMA_PMD 15/22] iommu/dma: Add background page scrubber for DMA_PMD pools Luigi Rizzo
2026-10-03 21:22 ` [RFC: DMA_PMD 16/22] iommu/dma: Add per-NUMA-node PMD page reservoir Luigi Rizzo
2026-10-03 21:22 ` [RFC: DMA_PMD 17/22] net/gve: Use DMA_PMD memory for RX buffers Luigi Rizzo
2026-10-03 21:22 ` [RFC: DMA_PMD 18/22] net/gve: Use DMA_PMD memory for tx header bounce buffers Luigi Rizzo
2026-10-03 21:22 ` [RFC: DMA_PMD 19/22] net/mlx5e: " Luigi Rizzo
2026-10-14 17:22 ` Dragos Tatulea
2026-10-14 17:50 ` Dragos Tatulea
2026-10-14 18:28 ` Luigi Rizzo
2026-10-03 21:22 ` [RFC: DMA_PMD 20/22] net/idpf: " Luigi Rizzo
2026-10-03 21:22 ` [RFC: DMA_PMD 21/22] net/bnxt: " Luigi Rizzo
2026-10-03 21:22 ` [RFC: DMA_PMD 22/22] iommu/dma: Add DMA_PMD statistics and debugfs Luigi Rizzo
2026-10-11 9:02 ` [RFC: DMA_PMD 00/22] DMA_PMD: PMD_SIZE-backed IO buffer pools Pranjal Shrivastava
2026-10-12 13:51 ` David Hildenbrand (Arm)
2026-10-12 14:45 ` Luigi Rizzo
2026-10-12 19:43 ` David Hildenbrand (Arm)
2026-10-13 10:42 ` Robin Murphy
2026-10-14 10:47 ` David Hildenbrand (Arm)
2026-10-14 11:05 ` Luigi Rizzo
2026-10-17 17:11 ` Luigi Rizzo
2026-10-22 20:12 ` Jason Gunthorpe
2026-10-22 21:51 ` Luigi Rizzo
2026-10-23 14:01 ` Jason Gunthorpe
2026-10-23 14:30 ` Luigi Rizzo
2026-10-23 14:50 ` Jason Gunthorpe
2026-10-23 15:12 ` Luigi Rizzo
2026-10-23 17:27 ` Jakub Kicinski
2026-10-23 20:22 ` Luigi Rizzo
2026-11-03 8:09 ` Christoph Hellwig
2026-11-03 9:13 ` Luigi Rizzo
2026-11-03 15:10 ` Jason Gunthorpe
2026-11-06 17:21 ` Naman Gulati
2026-11-06 17:30 ` Jason Gunthorpe
2026-11-06 18:00 ` Naman Gulati
2026-11-06 18:07 ` Jason Gunthorpe
2026-11-06 18:17 ` Naman Gulati
2026-11-06 18:43 ` Jason Gunthorpe
2026-11-06 19:01 ` Luigi Rizzo
2026-11-06 19:11 ` Naman Gulati
2026-10-12 14:49 ` Mostafa Saleh
2026-10-12 15:02 ` Luigi Rizzo
2026-10-14 17:28 ` Dragos Tatulea
2026-10-14 18:14 ` Luigi Rizzo
2026-11-09 18:25 ` [RFC PATCH v2 00/25] PISA: Pre-mapped IOVA Superpage Allocator for DMA and CoCo Luigi Rizzo
2026-11-09 18:25 ` [RFC PATCH v2 01/25] iommu/dma: Introduce CONFIG_PISA and metadata table Luigi Rizzo
2026-11-09 18:25 ` [RFC PATCH v2 02/25] iommu/dma: Add PISA allocator lifecycle and page recycle hook Luigi Rizzo
2026-11-09 18:25 ` [RFC PATCH v2 03/25] iommu/dma: Add per-NUMA-node PISA page reservoir Luigi Rizzo
2026-11-09 18:25 ` [RFC PATCH v2 04/25] iommu/dma: Add PISA block allocation Luigi Rizzo
2026-11-09 18:25 ` [RFC PATCH v2 05/25] iommu/dma: Add global cap and shrinker for PISA pages Luigi Rizzo
2026-11-09 18:25 ` [RFC PATCH v2 06/25] iommu/dma: Reserve a per-domain IOVA window " Luigi Rizzo
2026-11-09 18:25 ` [RFC PATCH v2 07/25] iommu/dma: Release PISA domain mappings on domain teardown Luigi Rizzo
2026-11-09 18:25 ` [RFC PATCH v2 08/25] iommu/dma: Use per-domain IOVA window to map PISA memory Luigi Rizzo
2026-11-09 18:25 ` [RFC PATCH v2 09/25] driver core: Add per-device pisa_* sysfs attributes for PISA optin Luigi Rizzo
2026-11-09 18:25 ` [RFC PATCH v2 10/25] iommu/dma: Use PISA for coherent DMA allocations Luigi Rizzo
2026-11-09 18:25 ` Luigi Rizzo [this message]
2026-11-09 18:25 ` [RFC PATCH v2 12/25] net/core: Use per-node PISA allocators for skb_page_frag_refill() Luigi Rizzo
2026-11-09 18:25 ` [RFC PATCH v2 13/25] net/core: Use PISA for page_pool memory Luigi Rizzo
2026-11-09 18:25 ` [RFC PATCH v2 14/25] iommu/dma: Support decrypted PISA pages Luigi Rizzo
2026-11-09 18:25 ` [RFC PATCH v2 15/25] iommu/dma: Add background page scrubber for PISA allocators Luigi Rizzo
2026-11-09 18:25 ` [RFC PATCH v2 16/25] mm/slub: Add SLAB_CACHE_PISA_RO for PISA-backed slab caches Luigi Rizzo
2026-11-09 18:25 ` [RFC PATCH v2 17/25] net: Allocate small skb heads from PISA Luigi Rizzo
2026-11-09 18:25 ` [RFC PATCH v2 18/25] iommu/dma: Migrate user pages into PISA for TX zerocopy Luigi Rizzo
2026-11-09 18:25 ` [RFC PATCH v2 19/25] iommu/dma: Stop recycling old PISA pages when allocator waste is high Luigi Rizzo
2026-11-09 18:25 ` [RFC PATCH v2 20/25] net/libeth: Use GFP_KERNEL when pre-filling RX rings Luigi Rizzo
2026-11-09 18:25 ` [RFC PATCH v2 21/25] net/gve: " Luigi Rizzo
2026-11-09 18:25 ` [RFC PATCH v2 22/25] net/gve: Use PISA memory for RX buffers Luigi Rizzo
2026-11-09 18:25 ` [RFC PATCH v2 23/25] block, mm, iommu/dma: Support PISA for disk I/O and scrub migration Luigi Rizzo
2026-11-09 18:25 ` [RFC PATCH v2 24/25] Documentation: Add PISA architecture documentation Luigi Rizzo
2026-11-09 18:25 ` [RFC PATCH v2 25/25] iommu/dma: Add PISA statistics and debugfs Luigi Rizzo
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261109182533.3594984-12-lrizzo@google.com \
--to=lrizzo@google.com \
--cc=akpm@linux-foundation.org \
--cc=aleksander.lobakin@intel.com \
--cc=axboe@kernel.dk \
--cc=corbet@lwn.net \
--cc=dakr@kernel.org \
--cc=davem@davemloft.net \
--cc=david@kernel.org \
--cc=driver-core@lists.linux.dev \
--cc=edumazet@kernel.org \
--cc=gregkh@linuxfoundation.org \
--cc=hawk@kernel.org \
--cc=hch@lst.de \
--cc=hramamurthy@google.com \
--cc=ilias.apalodimas@linaro.org \
--cc=iommu@lists.linux.dev \
--cc=joro@8bytes.org \
--cc=joshwash@google.com \
--cc=kuba@kernel.org \
--cc=kuniyu@google.com \
--cc=linux-block@vger.kernel.org \
--cc=linux-doc@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=ljs@kernel.org \
--cc=m.szyprowski@samsung.com \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=rafael@kernel.org \
--cc=rizzo.unipi@gmail.com \
--cc=robin.murphy@arm.com \
--cc=vbabka@kernel.org \
--cc=will@kernel.org \
--cc=willemb@google.com \
--cc=willy@infradead.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®