[RFC: DMA_PMD 06/22] iommu/dma: reserve a per-domain IOVA window for DMA_PMD pages
flat view
From: Luigi Rizzo <hidden>
Date: 2026-10-03 21:22:56
Also in:
driver-core, linux-doc, linux-iommu, linux-mm, lkml
Subsystem:
iommu dma-api layer, iommu subsystem, the rest · Maintainers:
Robin Murphy, Joerg Roedel, Will Deacon, Linus Torvalds
Embed struct dma_pmd_window in struct iommu_dma_cookie to record the per-domain IOVA range reserved for DMA_PMD mappings, and expose the cookie accessors (dma_pmd_dma_window(), dma_pmd_dma_iovad(), dma_pmd_window_owns()) and dma_info_to_prot() needed by the DMA_PMD mapping layer in subsequent patches. No functional change. Signed-off-by: Luigi Rizzo <redacted> --- drivers/iommu/dma-iommu.c | 39 ++++++++++++++++++- drivers/iommu/dma-iommu.h | 8 ++++ drivers/iommu/dma-pmd-kunit.c | 19 +++++++++ drivers/iommu/dma-pmd-priv.h | 72 +++++++++++++++++++++++++++++++++++ 4 files changed, 137 insertions(+), 1 deletion(-)
diff --git a/drivers/iommu/dma-iommu.c b/drivers/iommu/dma-iommu.c
index 58c624513cd43..70aee7a3020c8 100644
--- a/drivers/iommu/dma-iommu.c
+++ b/drivers/iommu/dma-iommu.c@@ -18,6 +18,7 @@ #include <linux/gfp.h> #include <linux/huge_mm.h> #include <linux/iommu.h> +#include <linux/dma-pmd.h> #include <linux/iommu-dma.h> #include <linux/iova.h> #include <linux/irq.h>
@@ -36,6 +37,7 @@ #include <trace/events/swiotlb.h> #include "dma-iommu.h" +#include "dma-pmd-priv.h" #include "iommu-pages.h" struct iommu_dma_msi_page {
@@ -75,6 +77,8 @@ struct iommu_dma_cookie { struct iommu_domain *fq_domain; /* Options for dma-iommu use */ struct iommu_dma_options options; + /* IOVA window reserved for DMA_PMD pages. Nothing else can go here. */ + struct dma_pmd_window win_dma_pmd; }; struct iommu_dma_msi_cookie {
@@ -732,7 +736,7 @@ static int iommu_dma_init_domain(struct iommu_domain *domain, struct device *dev * * Return: corresponding IOMMU API page protection flags */ -static int dma_info_to_prot(enum dma_data_direction dir, bool coherent, +int dma_info_to_prot(enum dma_data_direction dir, bool coherent, unsigned long attrs) { int prot;
@@ -1214,6 +1218,39 @@ static inline size_t iova_unaligned(struct iova_domain *iovad, phys_addr_t phys, return iova_offset(iovad, phys | size); } +/** + * dma_pmd_dma_window - This domain's IOVA window for DMA_PMD + * @domain: Domain to look at + * + * For the DMA_PMD code's slow paths, which need a window but do not have the + * cookie layout. The DMA map path is handed the pointer by its caller instead, + * so this is never called at map frequency. + * + * Return: the window, or NULL if @domain is NULL or does not carry a DMA-IOVA + * cookie. The cookie shares a union with the MSI, iommufd and fault-handler + * ones, so the type has to be tested rather than the pointer. An identity, + * passthrough, or MSI-only domain has no window, and a device can be moved to + * one after its pool was created (e.g. via sysfs domain type changes or VFIO + * attachment). + */ +struct dma_pmd_window *dma_pmd_dma_window(struct iommu_domain *domain) +{ + /* + * Also decline in kdump kernels (iommu_deferred_attach_enabled); + * dma_pmd_dma_iovad() uses this check so win->size stays 0. + */ + if (static_branch_unlikely(&iommu_deferred_attach_enabled) || + !domain || domain->cookie_type != IOMMU_COOKIE_DMA_IOVA) + return NULL; + + return &domain->iova_cookie->win_dma_pmd; +} + +struct iova_domain *dma_pmd_dma_iovad(struct iommu_domain *domain) +{ + return dma_pmd_dma_window(domain) ? &domain->iova_cookie->iovad : NULL; +} + dma_addr_t iommu_dma_map_phys(struct device *dev, phys_addr_t phys, size_t size, enum dma_data_direction dir, unsigned long attrs) {
diff --git a/drivers/iommu/dma-iommu.h b/drivers/iommu/dma-iommu.h
index 040d002525632..241f8de002e50 100644
--- a/drivers/iommu/dma-iommu.h
+++ b/drivers/iommu/dma-iommu.h@@ -7,6 +7,9 @@ #include <linux/iommu.h> +struct dma_pmd_window; +struct iova_domain; + #ifdef CONFIG_IOMMU_DMA void iommu_setup_dma_ops(struct device *dev, struct iommu_domain *domain);
@@ -22,6 +25,11 @@ void iommu_dma_get_resv_regions(struct device *dev, struct list_head *list); int iommu_dma_sw_msi(struct iommu_domain *domain, struct msi_desc *desc, phys_addr_t msi_addr); +int dma_info_to_prot(enum dma_data_direction dir, bool coherent, unsigned long attrs); + +struct iova_domain *dma_pmd_dma_iovad(struct iommu_domain *domain); +struct dma_pmd_window *dma_pmd_dma_window(struct iommu_domain *domain); + extern bool iommu_dma_forcedac; #else /* CONFIG_IOMMU_DMA */
diff --git a/drivers/iommu/dma-pmd-kunit.c b/drivers/iommu/dma-pmd-kunit.c
index 4e38cc5970433..16312014b8976 100644
--- a/drivers/iommu/dma-pmd-kunit.c
+++ b/drivers/iommu/dma-pmd-kunit.c@@ -140,11 +140,30 @@ static void test_pool_alloc_and_recycle(struct kunit *test) dma_pmd_pool_destroy(pool_wm); } +static void test_window_helpers(struct kunit *test) +{ + struct dma_pmd_window win = {}; + + KUNIT_ASSERT_EQ(test, dma_pmd_meta_init(), 0); + + /* Empty window (size == 0) never owns any IOVA. */ + KUNIT_EXPECT_FALSE(test, dma_pmd_window_owns(&win, 0)); + KUNIT_EXPECT_FALSE(test, dma_pmd_window_owns(&win, SZ_4G)); + + win.base = SZ_4G; + win.size = SZ_2G; + KUNIT_EXPECT_FALSE(test, dma_pmd_window_owns(&win, SZ_4G - 1)); + KUNIT_EXPECT_TRUE(test, dma_pmd_window_owns(&win, SZ_4G)); + KUNIT_EXPECT_TRUE(test, dma_pmd_window_owns(&win, SZ_4G + SZ_2G - 1)); + KUNIT_EXPECT_FALSE(test, dma_pmd_window_owns(&win, SZ_4G + SZ_2G)); +} + static struct kunit_case dma_pmd_meta_test_cases[] = { KUNIT_CASE(test_meta_init_and_roundtrip), KUNIT_CASE(test_meta_invalid_phys), KUNIT_CASE(test_meta_pooled_toggle), KUNIT_CASE(test_pool_alloc_and_recycle), + KUNIT_CASE(test_window_helpers), {} };
diff --git a/drivers/iommu/dma-pmd-priv.h b/drivers/iommu/dma-pmd-priv.h
index 39295d0add51d..8104897572921 100644
--- a/drivers/iommu/dma-pmd-priv.h
+++ b/drivers/iommu/dma-pmd-priv.h@@ -9,10 +9,46 @@ #include <linux/spinlock.h> #include <linux/types.h> +/** + * struct dma_pmd_window - A domain's IOVA window reserved for DMA_PMD pages + * @base: First IOVA of the window. A page at @phys is mapped, in every domain + * that has a window, at @base + @phys. + * @size: Window size in bytes, or 0 if this domain has no window, either + * because nothing has pooled through it yet, or because it could not + * find a free range that large. 0 must make both the map and the unmap + * side decline, see dma_pmd_window_owns(). + * @domain_idx: Dense domain index + DMA_PMD_IDX_FIRST, used for per-PMD-page + * presence (bit @domain_idx - DMA_PMD_IDX_FIRST), or one of + * DMA_PMD_IDX_NONE, DMA_PMD_IDX_NOHUGE or DMA_PMD_IDX_NOSPACE while + * @size is 0. Of the three sentinels, only DMA_PMD_IDX_NONE is retried. + * + * Lives in the domain's iommu_dma_cookie. The window is a real allocation out + * of the domain's iova_domain, so no ordinary IOVA can ever fall inside it, + * which is what lets the unmap path recognise a pooled IOVA by range alone. + */ +struct dma_pmd_window { + dma_addr_t base; + u64 size; + u16 domain_idx; +}; + #ifdef CONFIG_DMA_PMD #define DMA_PMD_BLOCKS(order) (1U << (PMD_ORDER - (order))) +/* + * Sentinel values of dma_pmd_window.domain_idx below DMA_PMD_IDX_FIRST: + * never attempted (0, matching kzalloc), or permanently declined. A real + * domain index is >= DMA_PMD_IDX_FIRST (subtract DMA_PMD_IDX_FIRST for + * the bitmap bit position). + */ +enum { + DMA_PMD_IDX_NONE, /* not attempted yet */ + DMA_PMD_IDX_NOHUGE, /* @domain can never pool */ + DMA_PMD_IDX_NOSPACE, /* no index or no IOVA range */ + DMA_PMD_IDX_FIRST, /* first valid domain index */ +}; + /* * Locking * -------
@@ -167,5 +203,41 @@ struct dma_pmd_meta *dma_pmd_meta_from_phys(phys_addr_t pa); unsigned long dma_pmd_meta_to_pfn(const struct dma_pmd_meta *m); phys_addr_t dma_pmd_meta_to_phys(const struct dma_pmd_meta *m); +static inline bool dma_is_pmd_phys(phys_addr_t phys) +{ + return dma_is_pmd_page(phys >> PAGE_SHIFT); +} + +/** + * dma_pmd_window_owns - Was @dma handed out by a DMA_PMD mapping? + * @win: The unmapping domain's window + * @dma: IOVA being unmapped + * + * Only DMA_PMD pages can be mapped within the reserved IOVA window + * so a range check suffices. + * A domain that has no window has @size 0, so the unsigned subtraction + * underflows to a huge value and the test is always false. That is the same + * check that stops the map path from using a window the domain does not own. + */ +static inline bool dma_pmd_window_owns(const struct dma_pmd_window *win, dma_addr_t dma) +{ + /* Acquire pairs with the release of @size in dma_pmd_window_assign(). */ + u64 size = smp_load_acquire(&win->size); + + return size && dma - win->base < size; +} + +#else /* !CONFIG_DMA_PMD */ + +static inline bool dma_is_pmd_phys(phys_addr_t phys) +{ + return false; +} + +static inline bool dma_pmd_window_owns(const struct dma_pmd_window *win, dma_addr_t dma) +{ + return false; +} + #endif /* CONFIG_DMA_PMD */ #endif /* _DRIVERS_IOMMU_DMA_PMD_PRIV_H */
--
2.56.0.rc1.315.gc6ed9934b7-goog