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 08/25] iommu/dma: Use per-domain IOVA window to map PISA memory
Date: Mon, 9 Nov 2026 18:25:16 +0000 [thread overview]
Message-ID: <20261109182533.3594984-9-lrizzo@google.com> (raw)
In-Reply-To: <20261003212241.3432303-1-lrizzo@google.com>
Intercept the DMA mapping and unmapping functions so that PISA
buffers are mapped within the reserved IOVA range.
This is implemented as follows:
- each domain that enables PISA (via per-device optin) is dynamically
assigned an IOVA window and a compact index the first time it maps a
PISA page.
This way, one bit in pisa_meta suffices to record whether the domain
has already mapped the page.
- The page is mapped in the domain at win->base + slot * 2MB on first
use, and subsequent mappings reuse that translation.
Signed-off-by: Luigi Rizzo <lrizzo@google.com>
---
drivers/iommu/Kconfig | 1 +
drivers/iommu/dma-iommu.c | 15 +++
drivers/iommu/iommu.c | 5 +-
drivers/iommu/pisa-alloc.c | 5 +-
drivers/iommu/pisa-kunit.c | 133 +++++++++++++++++++++++++++
drivers/iommu/pisa-map.c | 184 +++++++++++++++++++++++++++++++++++++
drivers/iommu/pisa-priv.h | 35 ++++++-
include/linux/pisa.h | 3 +
8 files changed, 377 insertions(+), 4 deletions(-)
diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig
index 15e06eaed0924..52cce0963525b 100644
--- a/drivers/iommu/Kconfig
+++ b/drivers/iommu/Kconfig
@@ -428,6 +428,7 @@ config PISA
bool "Pre-mapped IOVA Superpage Allocator"
depends on X86_64 || ARM64
depends on !PREEMPT_RT
+ select NEED_SG_DMA_FLAGS
default y
help
PISA is a specialized memory allocator with an API similar to
diff --git a/drivers/iommu/dma-iommu.c b/drivers/iommu/dma-iommu.c
index 025bceebb2262..eb920ad77c783 100644
--- a/drivers/iommu/dma-iommu.c
+++ b/drivers/iommu/dma-iommu.c
@@ -837,6 +837,8 @@ static void __iommu_dma_unmap(struct device *dev, dma_addr_t dma_addr,
struct iommu_iotlb_gather iotlb_gather;
size_t unmapped;
+ if (is_pisa_iova(&cookie->win_pisa, dma_addr))
+ return;
dma_addr -= iova_off;
size = iova_align(iovad, size + iova_off);
iommu_iotlb_gather_init(&iotlb_gather);
@@ -863,6 +865,8 @@ static dma_addr_t __iommu_dma_map(struct device *dev, phys_addr_t phys,
iommu_deferred_attach(dev, domain))
return DMA_MAPPING_ERROR;
+ if (!pisa_map_phys(&iova, dev, domain, phys, size, prot))
+ return iova;
/* If anyone ever wants this we'd need support in the IOVA allocator */
if (dev_WARN_ONCE(dev, dma_get_min_align_mask(dev) > iova_mask(iovad),
"Unsupported alignment constraint\n"))
@@ -1277,6 +1281,10 @@ void iommu_dma_unmap_phys(struct device *dev, dma_addr_t dma_handle,
{
phys_addr_t phys;
+ /* PISA mapped buffer: nothing to unmap or release. */
+ if (is_pisa_iova(&iommu_get_dma_domain(dev)->iova_cookie->win_pisa, dma_handle))
+ return;
+
if (attrs & (DMA_ATTR_MMIO | DMA_ATTR_REQUIRE_COHERENT)) {
__iommu_dma_unmap(dev, dma_handle, size);
return;
@@ -1478,6 +1486,13 @@ int iommu_dma_map_sg(struct device *dev, struct scatterlist *sg, int nents,
*/
break;
case PCI_P2PDMA_MAP_NONE:
+ /* Map PISA pages and mark as bus addresses to skip IOVA alloc and unmap. */
+ if (!pisa_map_phys(&s->dma_address, dev, domain,
+ sg_phys(s), s_length, prot)) {
+ sg_dma_len(s) = s_length;
+ sg_dma_mark_bus_address(s);
+ continue;
+ }
break;
case PCI_P2PDMA_MAP_BUS_ADDR:
/*
diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index dfba3d5c277c2..068eaf262c7f6 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c
@@ -2867,8 +2867,9 @@ ssize_t iommu_map_sg(struct iommu_domain *domain, unsigned long iova,
while (i <= nents) {
phys_addr_t s_phys = sg_phys(sg);
+ bool is_bus = i < nents && sg_dma_is_bus_address(sg);
- if (len && s_phys != start + len) {
+ if (len && (is_bus || s_phys != start + len)) {
ret = iommu_map_nosync(domain, iova + mapped, start,
len, prot, gfp);
if (ret)
@@ -2878,7 +2879,7 @@ ssize_t iommu_map_sg(struct iommu_domain *domain, unsigned long iova,
len = 0;
}
- if (sg_dma_is_bus_address(sg))
+ if (is_bus)
goto next;
if (len) {
diff --git a/drivers/iommu/pisa-alloc.c b/drivers/iommu/pisa-alloc.c
index dd90aad296edf..f98142882519c 100644
--- a/drivers/iommu/pisa-alloc.c
+++ b/drivers/iommu/pisa-alloc.c
@@ -299,9 +299,11 @@ struct pisa_alloc *pisa_create(int nid, unsigned int order, enum dma_data_direct
spin_lock_init(&pa->lock);
pa->order = order;
- pa->nid = pisa_sanitize_nid(nid);
+ pa->prot = (dir != DMA_FROM_DEVICE ? IOMMU_READ : 0) |
+ (dir != DMA_TO_DEVICE ? IOMMU_WRITE : 0);
pa->max_idle_pages = READ_ONCE(pisa_max_idle) ? : 1;
pisa_set_name(pa, "%ps", (void *)_RET_IP_);
+ pa->nid = pisa_sanitize_nid(nid);
INIT_LIST_HEAD(&pa->partial);
INIT_LIST_HEAD(&pa->partial_dirty);
INIT_LIST_HEAD(&pa->idle);
@@ -607,6 +609,7 @@ static unsigned long __pisa_alloc_bulk(struct pisa_alloc *pa, gfp_t gfp, unsigne
goto out;
spin_lock_irqsave(&pa->lock, flags);
meta->pa = pa;
+ meta->prot = pa->prot;
/* Set PISA_ACTIVE after @meta is fully set up. */
WRITE_ONCE(meta->flags, meta->flags | PISA_ACTIVE);
}
diff --git a/drivers/iommu/pisa-kunit.c b/drivers/iommu/pisa-kunit.c
index f0775aa45a058..e16268d55f3de 100644
--- a/drivers/iommu/pisa-kunit.c
+++ b/drivers/iommu/pisa-kunit.c
@@ -6,6 +6,7 @@
#include <linux/gfp.h>
#include <linux/pisa.h>
#include <linux/iommu.h>
+#include <linux/iova.h>
#include <linux/mm.h>
#include <linux/shrinker.h>
#include <linux/skbuff.h>
@@ -184,8 +185,10 @@ static void test_alloc_and_recycle(struct kunit *test)
#ifdef CONFIG_IOMMU_DMA
static void test_window_helpers(struct kunit *test)
{
+ struct device dev = { .numa_node = NUMA_NO_NODE };
struct iommu_domain dummy_domain = {};
struct pisa_window win = {};
+ dma_addr_t d;
KUNIT_ASSERT_EQ(test, pisa_meta_init(), 0);
@@ -212,10 +215,139 @@ static void test_window_helpers(struct kunit *test)
win.idx_gen--;
KUNIT_EXPECT_EQ(test, pisa_window_declined(&win), 0);
+ /* A device without an IOMMU group has no PISA window. */
+ KUNIT_EXPECT_NE(test, pisa_map_phys(&d, NULL, NULL, 0, PAGE_SIZE, 0), 0);
+ KUNIT_EXPECT_NE(test, pisa_map_phys(&d, &dev, NULL, 0, PAGE_SIZE, 0), 0);
/* Releasing an unregistered domain with an active allocator is a safe no-op. */
pisa_domain_release(&dummy_domain);
}
+/*
+ * Fake DMA domain for the mapping and coherent allocation tests, backed by a
+ * real iommu_dma_cookie and no-op page table ops.
+ */
+static int fake_map_pages(struct iommu_domain *domain, unsigned long iova,
+ phys_addr_t paddr, size_t pgsize, size_t pgcount,
+ int prot, gfp_t gfp, size_t *mapped)
+{
+ *mapped = pgsize * pgcount;
+ return 0;
+}
+
+static size_t fake_unmap_pages(struct iommu_domain *domain, unsigned long iova,
+ size_t pgsize, size_t pgcount,
+ struct iommu_iotlb_gather *iotlb_gather)
+{
+ return pgsize * pgcount;
+}
+
+static const struct iommu_domain_ops fake_domain_ops = {
+ .map_pages = fake_map_pages,
+ .unmap_pages = fake_unmap_pages,
+};
+
+static struct iommu_domain fake_domain = {
+ .type = IOMMU_DOMAIN_DMA,
+ .pgsize_bitmap = SZ_4K | PISA_SIZE,
+ .ops = &fake_domain_ops,
+};
+
+static void fake_domain_teardown(void *unused)
+{
+ iommu_put_dma_cookie(&fake_domain);
+ fake_domain.cookie_type = IOMMU_COOKIE_NONE;
+ fake_domain.iova_cookie = NULL;
+ iova_cache_put();
+}
+
+static int fake_domain_setup(struct kunit *test)
+{
+ int ret = iova_cache_get();
+
+ if (ret)
+ return ret;
+ ret = iommu_get_dma_cookie(&fake_domain);
+ if (ret) {
+ iova_cache_put();
+ return ret;
+ }
+ init_iova_domain(pisa_dma_iovad(&fake_domain), SZ_4K, 1);
+ return kunit_add_action_or_reset(test, fake_domain_teardown, NULL);
+}
+
+static void test_map_and_domain_lifecycle(struct kunit *test)
+{
+ struct device dev = { .init_name = "pisa_map_kunit", .numa_node = NUMA_NO_NODE,
+ .coherent_dma_mask = DMA_BIT_MASK(64) };
+ struct shrink_control sc = { .nr_to_scan = ULONG_MAX };
+ struct pisa_window *win;
+ struct pisa_meta *meta;
+ struct pisa_alloc *pa;
+ unsigned int domain_idx;
+ dma_addr_t d1, d2;
+ struct page *p;
+ phys_addr_t phys;
+
+ dev.dma_mask = &dev.coherent_dma_mask;
+ set_dev_node(&dev, first_memory_node);
+#if defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_DEVICE) || \
+ defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU) || \
+ defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU_ALL)
+ dev.dma_coherent = true;
+#endif
+ KUNIT_ASSERT_EQ(test, fake_domain_setup(test), 0);
+ win = pisa_dma_window(&fake_domain);
+
+ pa = pisa_node_pa(first_memory_node, 2, true);
+ KUNIT_ASSERT_NOT_NULL(test, pa);
+ p = pisa_alloc(pa, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, p);
+
+ phys = page_to_phys(p);
+ meta = pisa_meta_of_pfn(page_to_pfn(p));
+ KUNIT_EXPECT_TRUE(test, is_pisa_phys(phys));
+
+ /* TX-only allocator declines IOMMU_WRITE mappings. */
+ KUNIT_EXPECT_EQ(test,
+ pisa_map_phys(&d1, &dev, &fake_domain, phys, PAGE_SIZE,
+ IOMMU_READ | IOMMU_WRITE | IOMMU_CACHE), -EACCES);
+
+ /* First map assigns the window and installs the 2MB PTE. */
+ KUNIT_ASSERT_EQ(test,
+ pisa_map_phys(&d1, &dev, &fake_domain, phys, PAGE_SIZE,
+ IOMMU_READ | IOMMU_CACHE), 0);
+ KUNIT_EXPECT_TRUE(test, is_pisa_iova(win, d1));
+ domain_idx = win->domain_idx - PISA_IDX_FIRST;
+ KUNIT_EXPECT_TRUE(test, test_bit(domain_idx, meta->domains_mapped));
+
+ /* Second map at an offset hits the lockless fast path. */
+ KUNIT_ASSERT_EQ(test,
+ pisa_map_phys(&d2, &dev, &fake_domain, phys + 128, 256,
+ IOMMU_READ | IOMMU_CACHE), 0);
+ KUNIT_EXPECT_EQ(test, d2, d1 + 128);
+
+ /* Quiescing the domain unmaps the PISA page but keeps the window. */
+ pisa_domain_quiesce(&fake_domain);
+ KUNIT_EXPECT_FALSE(test, test_bit(domain_idx, meta->domains_mapped));
+ KUNIT_ASSERT_EQ(test,
+ pisa_map_phys(&d2, &dev, &fake_domain, phys, PAGE_SIZE,
+ IOMMU_READ | IOMMU_CACHE), 0);
+ KUNIT_EXPECT_EQ(test, d2, d1);
+ KUNIT_EXPECT_TRUE(test, test_bit(domain_idx, meta->domains_mapped));
+
+ /* Releasing the domain forgets the mapping bit across allocators. */
+ pisa_domain_release(&fake_domain);
+ KUNIT_EXPECT_FALSE(test, test_bit(domain_idx, meta->domains_mapped));
+
+ /* Re-map into a fresh window so shrinking exercises pisa_unmap_all(). */
+ *win = (struct pisa_window){};
+ KUNIT_EXPECT_EQ(test,
+ pisa_map_phys(&d1, &dev, &fake_domain, phys, PAGE_SIZE,
+ IOMMU_READ | IOMMU_CACHE), 0);
+
+ __free_pages(p, 2);
+ pisa_shrink_scan(NULL, &sc);
+}
#endif /* CONFIG_IOMMU_DMA */
static void test_reservoir_and_scrubber(struct kunit *test)
@@ -231,6 +363,7 @@ static struct kunit_case pisa_meta_test_cases[] = {
KUNIT_CASE(test_alloc_and_recycle),
#ifdef CONFIG_IOMMU_DMA
KUNIT_CASE(test_window_helpers),
+ KUNIT_CASE(test_map_and_domain_lifecycle),
#endif
KUNIT_CASE(test_reservoir_and_scrubber),
{}
diff --git a/drivers/iommu/pisa-map.c b/drivers/iommu/pisa-map.c
index 2ba977bb68734..2c481e514575c 100644
--- a/drivers/iommu/pisa-map.c
+++ b/drivers/iommu/pisa-map.c
@@ -12,6 +12,8 @@
#include <linux/pisa.h>
#include <linux/export.h>
#include <linux/iommu.h>
+#include <linux/iova.h>
+#include <linux/memblock.h>
#include <linux/mm.h>
#include <linux/moduleparam.h>
#include <linux/slab.h>
@@ -103,6 +105,7 @@ void __init pisa_slots_init(void)
static struct {
struct iommu_domain *domain;
dma_addr_t base;
+ spinlock_t map_lock; /* serialises iommu_map() in __pisa_map_phys() */
} pisa_domains[PISA_MAX_DOMAINS] __read_mostly;
static DEFINE_SPINLOCK(pisa_domains_lock);
@@ -300,3 +303,184 @@ void pisa_domain_quiesce(struct iommu_domain *domain)
pr_debug("pisa: unmapped %u PISA pages from idle domain %p (index %d)\n",
done, domain, idx);
}
+
+/* Allocate a PISA_SIZE-aligned IOVA window of @size bytes from @domain. */
+static dma_addr_t pisa_dma_window_alloc(struct device *dev, struct iommu_domain *domain, u64 size)
+{
+ u64 limit = min_not_zero(dma_get_mask(dev), dev->bus_dma_limit);
+ struct iova_domain *iovad = pisa_dma_iovad(domain);
+ unsigned long shift, iova_len;
+ struct iova *new_iova;
+
+ limit = min_not_zero(limit, dev->coherent_dma_mask);
+
+ if (!iovad)
+ return 0;
+
+ if (domain->geometry.force_aperture)
+ limit = min_t(u64, limit, domain->geometry.aperture_end);
+
+ /* Ignore devices with too many constraints. */
+ if (limit <= DMA_BIT_MASK(32) || size > limit - PISA_SIZE)
+ return 0;
+
+ shift = iova_shift(iovad);
+ iova_len = (size + PISA_SIZE) >> shift;
+
+ /* Ignore advisory dev->iommu->pci_32bit_workaround preference. */
+ new_iova = alloc_iova(iovad, iova_len, limit >> shift, false);
+ if (!new_iova)
+ return 0;
+
+ return ALIGN((dma_addr_t)new_iova->pfn_lo << shift, PISA_SIZE);
+}
+
+/*
+ * Assign @domain an IOVA window and dense domain index on first use, recording
+ * permanent or retryable failures in @win->domain_idx.
+ */
+int pisa_window_assign(struct device *dev, struct iommu_domain *domain, struct pisa_window *win)
+{
+ u64 limit = min_not_zero(dma_get_mask(dev), dev->bus_dma_limit);
+ u64 size = pisa_window_size;
+ unsigned long flags;
+ dma_addr_t base = 0;
+ int idx, ret;
+
+ limit = min_not_zero(limit, dev->coherent_dma_mask);
+
+ if (!dev_is_dma_coherent(dev))
+ return -EOPNOTSUPP;
+
+ if (limit <= DMA_BIT_MASK(32) || size > limit - PISA_SIZE)
+ return -ENOSPC;
+
+ if (!(domain->pgsize_bitmap & PISA_SIZE)) {
+ if (cmpxchg(&win->domain_idx, PISA_IDX_NONE, PISA_IDX_NOHUGE) == PISA_IDX_NONE)
+ dev_warn_ratelimited(dev, "pisa: domain lacks a %luKB page size, not PISA\n",
+ (unsigned long)(PISA_SIZE >> 10));
+ return -EOPNOTSUPP;
+ }
+
+ spin_lock_irqsave(&pisa_domains_lock, flags);
+
+ /* Bail if another CPU got here first, or if declined earlier. */
+ ret = win->base ? 0 : pisa_window_declined(win);
+ if (win->base || ret) {
+ spin_unlock_irqrestore(&pisa_domains_lock, flags);
+ return ret;
+ }
+
+ for (idx = 0; idx < PISA_MAX_DOMAINS; idx++) {
+ if (!pisa_domains[idx].domain && !pisa_domains[idx].base)
+ break;
+ }
+ if (idx == PISA_MAX_DOMAINS) {
+ win->idx_gen = pisa_idx_gen;
+ WRITE_ONCE(win->domain_idx, PISA_IDX_NOIDX);
+ ret = -EBUSY;
+ } else {
+ base = pisa_dma_window_alloc(dev, domain, size);
+ if (!base) {
+ WRITE_ONCE(win->domain_idx, PISA_IDX_NOWINDOW);
+ ret = -ENOSPC;
+ } else {
+ pisa_domains[idx].domain = domain;
+ pisa_domains[idx].base = base;
+ spin_lock_init(&pisa_domains[idx].map_lock);
+ WRITE_ONCE(win->domain_idx, idx + PISA_IDX_FIRST);
+ /* Publish .domain_idx before .base (paired with is_pisa_iova()). */
+ smp_store_release(&win->base, base);
+ }
+ }
+ spin_unlock_irqrestore(&pisa_domains_lock, flags);
+
+ if (ret == -EBUSY)
+ pr_warn_ratelimited("pisa: all %d domain indices in use, not PISA for domain %p until one is released\n",
+ PISA_MAX_DOMAINS, domain);
+ else if (ret == -ENOSPC)
+ dev_warn_ratelimited(dev,
+ "pisa: no free %llu MB IOVA range for a PISA window\n",
+ size >> 20);
+ else
+ dev_info(dev, "pisa: domain %p index %d window %pad + %llu MB\n",
+ domain, idx, &base, size >> 20);
+ return ret;
+}
+
+/*
+ * Compute the window IOVA of active address @phys for @dev and store it in
+ * *@iova. On first use in a domain, reserves @win via alloc_iova(), and on
+ * first use of a PISA page in that domain, installs its 2MB leaf PTE via
+ * iommu_map(..., GFP_ATOMIC) under spinlocks.
+ * Returns 0 on success, or negative errno if @phys/@prot/@dev cannot use the window.
+ */
+int __pisa_map_phys(dma_addr_t *iova, struct device *dev,
+ struct iommu_domain *domain, phys_addr_t phys,
+ size_t size, int prot)
+{
+ struct pisa_window *win = pisa_dma_window(domain);
+ unsigned long pfn = phys >> PAGE_SHIFT, flags;
+ struct pisa_meta *meta;
+ unsigned int domain_idx;
+ int block_prot, ret;
+ dma_addr_t addr;
+ u64 limit;
+
+ if (unlikely(!iova || !dev || !win || !dev_is_dma_coherent(dev) ||
+ (phys & (PISA_SIZE - 1)) + size > PISA_SIZE))
+ return -EINVAL;
+
+ meta = pisa_meta_of_pfn(pfn);
+
+ /* Every leaf PTE of the page carries IOMMU_CACHE | @meta->prot. */
+ block_prot = IOMMU_CACHE | meta->prot;
+ if (unlikely(prot & ~block_prot))
+ return -EACCES;
+
+ /* Acquire pairs with the .base release in pisa_window_assign(). */
+ if (unlikely(!smp_load_acquire(&win->base))) {
+ /* Test cached decline in @domain_idx before taking @pisa_domains_lock. */
+ ret = pisa_window_declined(win);
+ if (likely(!ret))
+ ret = pisa_window_assign(dev, domain, win);
+
+ if (ret)
+ return ret;
+ }
+
+ addr = pisa_phys_to_iova(win, meta, phys);
+
+ /* Check @dev's own DMA mask and bus limit against the window IOVA. */
+ limit = min_not_zero(dma_get_mask(dev), dev->bus_dma_limit);
+ limit = min_not_zero(limit, dev->coherent_dma_mask);
+ if (unlikely(addr + size - 1 > limit))
+ return -ENOSPC;
+
+ domain_idx = win->domain_idx - PISA_IDX_FIRST;
+ if (likely(test_bit(domain_idx, meta->domains_mapped))) {
+ *iova = addr;
+ return 0;
+ }
+
+ /* First use of this PISA page in this domain: install the leaf PTE. */
+ ret = 0;
+ spin_lock_irqsave(&pisa_domains[domain_idx].map_lock, flags);
+ if (!test_bit(domain_idx, meta->domains_mapped)) {
+ phys_addr_t pisa_phys = phys & PISA_MASK;
+
+ ret = iommu_map(domain, pisa_phys_to_iova(win, meta, pisa_phys),
+ pisa_phys, PISA_SIZE, block_prot, GFP_ATOMIC);
+ if (likely(!ret)) {
+ /* Ensure leaf PTE is visible before setting the domain bit. */
+ smp_mb__before_atomic();
+ set_bit(domain_idx, meta->domains_mapped);
+ }
+ }
+ spin_unlock_irqrestore(&pisa_domains[domain_idx].map_lock, flags);
+ if (unlikely(ret))
+ return ret;
+
+ *iova = addr;
+ return 0;
+}
diff --git a/drivers/iommu/pisa-priv.h b/drivers/iommu/pisa-priv.h
index f97f69a13d1a9..8d83ec09c19a9 100644
--- a/drivers/iommu/pisa-priv.h
+++ b/drivers/iommu/pisa-priv.h
@@ -2,7 +2,7 @@
#ifndef _DRIVERS_IOMMU_PISA_PRIV_H
#define _DRIVERS_IOMMU_PISA_PRIV_H
-#include <linux/dma-direction.h>
+#include <linux/dma-mapping.h>
#include <linux/pisa.h>
#include <linux/jiffies.h>
#include <linux/list.h>
@@ -13,6 +13,7 @@
#include <linux/types.h>
#include <linux/workqueue.h>
+struct device;
struct iommu_domain;
struct iommu_iotlb_gather;
@@ -53,6 +54,7 @@ enum {
* - pisa_allocs_lock (rwlock): outermost; protects pisa_allocs list.
* - pisa_meta_mutex (mutex): serializes pisa_meta_init() and page-table alloc.
* - 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.
* - pisa_srcu: protects pisa_domains[] readers against pisa_domain_release().
*/
@@ -60,6 +62,7 @@ enum {
/**
* struct pisa_meta - Metadata for a single PISA page in @pisa_meta_array
* @flags: PISA_ACTIVE / PISA_QUARANTINED
+ * @prot: Owning allocator's IOMMU_READ/WRITE protection bits
* @nr_clean: Zeroed available blocks in @clean_bitmap
* @nr_dirty: Dirty available blocks in @dirty_bitmap
* @on_list: Current placement in @pa (enum pisa_meta_list)
@@ -74,6 +77,7 @@ enum {
*/
struct pisa_meta {
u8 flags;
+ u8 prot;
u16 nr_clean;
u16 nr_dirty;
u8 on_list;
@@ -105,6 +109,7 @@ static inline unsigned int pisa_meta_avail(const struct pisa_meta *meta)
* struct pisa_alloc - Allocator managing 2MB-backed blocks of a fixed order
* @lock: Spinlock protecting lists and block bitmaps
* @order: Block order managed by this allocator (<= PISA_ORDER)
+ * @prot: IOMMU_READ and/or IOMMU_WRITE from @dir passed to pisa_create()
* @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)
@@ -120,6 +125,7 @@ struct pisa_alloc {
/* First cacheline: hot fields touched on block alloc and free. */
spinlock_t lock;
u8 order;
+ u8 prot;
struct list_head partial;
struct list_head partial_dirty;
struct list_head idle;
@@ -148,6 +154,14 @@ struct pisa_node {
/* Per-NUMA-node PISA state (ring/TX/user allocators, reservoir). */
extern struct pisa_node pisa_nodes[MAX_NUMNODES];
+/* IOVA of @phys inside active RAM page @meta in @win. */
+static inline dma_addr_t pisa_phys_to_iova(const struct pisa_window *win,
+ const struct pisa_meta *meta, phys_addr_t phys)
+{
+ return win->base + ((dma_addr_t)meta->slot << PISA_SHIFT) +
+ (phys & (PISA_SIZE - 1));
+}
+
/* Offset of @meta from the start of a domain's window. */
static inline dma_addr_t pisa_meta_win_offset(const struct pisa_meta *meta)
{
@@ -228,6 +242,18 @@ void pisa_slots_init(void);
int pisa_slot_alloc(struct pisa_meta *meta);
void pisa_slot_free(unsigned int slot);
int pisa_unmap_all(struct pisa_meta *meta);
+int pisa_window_assign(struct device *dev, struct iommu_domain *domain, struct pisa_window *win);
+int __pisa_map_phys(dma_addr_t *iova, struct device *dev,
+ struct iommu_domain *domain, phys_addr_t phys,
+ size_t size, int prot);
+
+static inline int pisa_map_phys(dma_addr_t *iova, struct device *dev,
+ struct iommu_domain *domain, phys_addr_t phys,
+ size_t size, int prot)
+{
+ return is_pisa_phys(phys) ?
+ __pisa_map_phys(iova, dev, domain, phys, size, prot) : -EINVAL;
+}
/* Return negative errno if @win's domain was permanently/currently declined, or 0. */
static inline int pisa_window_declined(const struct pisa_window *win)
@@ -265,6 +291,13 @@ static inline int pisa_slot_alloc(struct pisa_meta *meta) { return 0; }
static inline void pisa_slot_free(unsigned int slot) {}
static inline int pisa_unmap_all(struct pisa_meta *meta) { return 0; }
+static inline int pisa_map_phys(dma_addr_t *iova, struct device *dev,
+ struct iommu_domain *domain, phys_addr_t phys,
+ size_t size, int prot)
+{
+ return -EOPNOTSUPP;
+}
+
static inline int pisa_window_declined(const struct pisa_window *win)
{
return -EOPNOTSUPP;
diff --git a/include/linux/pisa.h b/include/linux/pisa.h
index ab1c32e065bbb..c64689b81befe 100644
--- a/include/linux/pisa.h
+++ b/include/linux/pisa.h
@@ -5,11 +5,14 @@
#include <linux/types.h>
#include <linux/bitops.h>
+#include <linux/dma-mapping.h>
#include <linux/jump_label.h>
#include <linux/mm.h>
#include <linux/numa.h>
#include <linux/rcupdate.h>
+struct device;
+
#ifdef CONFIG_PISA
#define PISA_SHIFT 21U
--
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 ` Luigi Rizzo [this message]
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 ` [RFC PATCH v2 11/25] iommu/dma: Add per-CPU block caches to PISA allocators Luigi Rizzo
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-9-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®