[PATCH 04/11] ARM: dma-mapping: Implement and expose DMA setup functions

From: Mikko Perttunen

Date: Mon Sep 14 2026 - 22:16:53 EST


ARM DMA domains are currently allocated by the ARM DMA code, with struct
dma_iommu_mapping owning the domain and the IOVA bookkeeping. To allow
the IOMMU core to own DMA domains as is the case with other
architectures, we have to allow that code to allocate the domain and get
the cookie from the ARM DMA code.

Implement ARM versions of iommu_get/put_dma_cookie that create a struct
dma_iommu_mapping that doesn't have its own domain, as well as
iommu_setup/teardown_dma_ops that set the device's archdata.mapping,
allowing ARM DMA ops to seamlessly work on these devices.

The code is protected by CONFIG_ARM_DMA_USE_IOMMU and mutually exclusive
with the same-named functions in the dma-iommu code; and not reachable
yet.

Signed-off-by: Mikko Perttunen <mperttunen@xxxxxxxxxx>
---
arch/arm/include/asm/dma-iommu.h | 17 ++++++
arch/arm/mm/dma-mapping.c | 118 ++++++++++++++++++++++++++++++++++-----
drivers/iommu/dma-iommu.h | 50 +++++++++++------
3 files changed, 154 insertions(+), 31 deletions(-)

diff --git a/arch/arm/include/asm/dma-iommu.h b/arch/arm/include/asm/dma-iommu.h
index 2ce4c5683e6d..85cf9b05faab 100644
--- a/arch/arm/include/asm/dma-iommu.h
+++ b/arch/arm/include/asm/dma-iommu.h
@@ -19,6 +19,14 @@ struct dma_iommu_mapping {
size_t bits; /* per bitmap */
dma_addr_t base;

+ /*
+ * Set for mappings created by arm_iommu_create_mapping(), where
+ * @domain is owned by the mapping.
+ * Clear for mappings that are a cookie of a domain owned by the IOMMU
+ * core.
+ */
+ bool owns_domain;
+
spinlock_t lock;
struct kref kref;
};
@@ -32,5 +40,14 @@ int arm_iommu_attach_device(struct device *dev,
struct dma_iommu_mapping *mapping);
void arm_iommu_detach_device(struct device *dev);

+#ifdef CONFIG_ARM_DMA_USE_IOMMU
+struct iommu_domain;
+
+void iommu_setup_dma_ops(struct device *dev, struct iommu_domain *domain);
+void iommu_teardown_dma_ops(struct device *dev);
+int iommu_get_dma_cookie(struct iommu_domain *domain);
+void iommu_put_dma_cookie(struct iommu_domain *domain);
+#endif
+
#endif /* __KERNEL__ */
#endif
diff --git a/arch/arm/mm/dma-mapping.c b/arch/arm/mm/dma-mapping.c
index a7ad194960ce..e0d58778c5f5 100644
--- a/arch/arm/mm/dma-mapping.c
+++ b/arch/arm/mm/dma-mapping.c
@@ -1531,6 +1531,79 @@ static void __arm_iommu_free_mapping(struct dma_iommu_mapping *mapping)
kfree(mapping);
}

+static int __arm_iommu_init_cookie(struct iommu_domain *domain,
+ dma_addr_t base, u64 size)
+{
+ struct dma_iommu_mapping *mapping;
+
+ if (domain->cookie_type != IOMMU_COOKIE_NONE)
+ return -EEXIST;
+
+ mapping = __arm_iommu_alloc_mapping(base, size);
+ if (IS_ERR(mapping))
+ return PTR_ERR(mapping);
+
+ mapping->domain = domain;
+ domain->cookie_type = IOMMU_COOKIE_ARM_DMA;
+ domain->arm_cookie = mapping;
+
+ return 0;
+}
+
+/**
+ * iommu_get_dma_cookie - Set up a domain cookie for ARM IOMMU-based DMA API
+ * @domain: IOMMU_DOMAIN_DMA domain being configured
+ *
+ * Allocates IOVA bookkeeping for the domain based on the domain's specified
+ * geometry. The aperture can currently be at most 4GB in size, otherwise
+ * fails with -ERANGE.
+ */
+int iommu_get_dma_cookie(struct iommu_domain *domain)
+{
+ dma_addr_t base = domain->geometry.aperture_start;
+ u64 size;
+
+ if (base > domain->geometry.aperture_end)
+ return -EINVAL;
+
+ size = (u64)domain->geometry.aperture_end - base + 1;
+
+ return __arm_iommu_init_cookie(domain, base, size);
+}
+
+/**
+ * iommu_put_dma_cookie - Free domain cookie for ARM IOMMU-based DMA API
+ * @domain: the domain being freed
+ */
+void iommu_put_dma_cookie(struct iommu_domain *domain)
+{
+ __arm_iommu_free_mapping(domain->arm_cookie);
+}
+
+/**
+ * iommu_setup_dma_ops - Record a device's DMA API domain cookie
+ * @dev: the device
+ * @domain: the group's new default domain
+ *
+ * Records the cookie of @domain on @dev, or clears it if @domain is not a DMA
+ * API domain. The DMA ops themselves are per driver binding and are installed
+ * by arch_setup_dma_ops().
+ */
+void iommu_setup_dma_ops(struct device *dev, struct iommu_domain *domain)
+{
+ to_dma_iommu_mapping(dev) = iommu_is_dma_domain(domain) ?
+ domain->arm_cookie : NULL;
+}
+
+/**
+ * iommu_teardown_dma_ops - Clear a device's DMA API domain cookie
+ * @dev: the device
+ */
+void iommu_teardown_dma_ops(struct device *dev)
+{
+ to_dma_iommu_mapping(dev) = NULL;
+}
+
/**
* arm_iommu_create_mapping
* @dev: pointer to the client device (for IOMMU calls)
@@ -1541,28 +1614,32 @@ static void __arm_iommu_free_mapping(struct dma_iommu_mapping *mapping)
* IO address ranges, which is required to perform memory allocation and
* mapping with IOMMU aware functions.
*
+ * Unlike a default domain set up by the IOMMU core, the returned mapping owns
+ * the domain it describes and releases it along with the last reference.
+ *
* The client device need to be attached to the mapping with
* arm_iommu_attach_device function.
*/
struct dma_iommu_mapping *
arm_iommu_create_mapping(struct device *dev, dma_addr_t base, u64 size)
{
- struct dma_iommu_mapping *mapping;
-
- mapping = __arm_iommu_alloc_mapping(base, size);
- if (IS_ERR(mapping))
- return mapping;
+ struct iommu_domain *domain;
+ int err;

- mapping->domain = iommu_paging_domain_alloc(dev);
- if (IS_ERR(mapping->domain)) {
- int err = PTR_ERR(mapping->domain);
+ domain = iommu_paging_domain_alloc(dev);
+ if (IS_ERR(domain))
+ return ERR_CAST(domain);

- __arm_iommu_free_mapping(mapping);
+ err = __arm_iommu_init_cookie(domain, base, size);
+ if (err) {
+ iommu_domain_free(domain);
return ERR_PTR(err);
}

- kref_init(&mapping->kref);
- return mapping;
+ domain->arm_cookie->owns_domain = true;
+ kref_init(&domain->arm_cookie->kref);
+
+ return domain->arm_cookie;
}
EXPORT_SYMBOL_GPL(arm_iommu_create_mapping);

@@ -1571,8 +1648,8 @@ static void release_iommu_mapping(struct kref *kref)
struct dma_iommu_mapping *mapping =
container_of(kref, struct dma_iommu_mapping, kref);

+ /* Frees mapping too, via iommu_put_dma_cookie(). */
iommu_domain_free(mapping->domain);
- __arm_iommu_free_mapping(mapping);
}

static int extend_iommu_mapping(struct dma_iommu_mapping *mapping)
@@ -1595,8 +1672,13 @@ static int extend_iommu_mapping(struct dma_iommu_mapping *mapping)

void arm_iommu_release_mapping(struct dma_iommu_mapping *mapping)
{
- if (mapping)
- kref_put(&mapping->kref, release_iommu_mapping);
+ if (!mapping)
+ return;
+
+ if (WARN_ON(!mapping->owns_domain))
+ return;
+
+ kref_put(&mapping->kref, release_iommu_mapping);
}
EXPORT_SYMBOL_GPL(arm_iommu_release_mapping);

@@ -1660,6 +1742,14 @@ void arm_iommu_detach_device(struct device *dev)
return;
}

+ /*
+ * This pairs with arm_iommu_attach_device() on a driver-owned mapping.
+ * A mapping which is merely the cookie of a core-owned default domain
+ * must not be torn down from here.
+ */
+ if (WARN_ON(!mapping->owns_domain))
+ return;
+
iommu_detach_device(mapping->domain, dev);
kref_put(&mapping->kref, release_iommu_mapping);
to_dma_iommu_mapping(dev) = NULL;
diff --git a/drivers/iommu/dma-iommu.h b/drivers/iommu/dma-iommu.h
index 349d6a57e5a0..71b23cb698c0 100644
--- a/drivers/iommu/dma-iommu.h
+++ b/drivers/iommu/dma-iommu.h
@@ -7,25 +7,24 @@

#include <linux/iommu.h>

-#ifdef CONFIG_IOMMU_DMA
+/*
+ * Both dma-iommu.c and arch/arm/mm/dma-mapping.c implement functions to set up
+ * IOMMU_DOMAIN_DMA default domains. Only one provider can be compiled in at a
+ * time.
+ */
+#if defined(CONFIG_ARM_DMA_USE_IOMMU)
+
+#include <asm/dma-iommu.h>
+
+#elif defined(CONFIG_IOMMU_DMA)

void iommu_setup_dma_ops(struct device *dev, struct iommu_domain *domain);
void iommu_teardown_dma_ops(struct device *dev);

int iommu_get_dma_cookie(struct iommu_domain *domain);
void iommu_put_dma_cookie(struct iommu_domain *domain);
-void iommu_put_msi_cookie(struct iommu_domain *domain);

-int iommu_dma_init_fq(struct iommu_domain *domain);
-
-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);
-
-extern bool iommu_dma_forcedac;
-
-#else /* CONFIG_IOMMU_DMA */
+#else

static inline void iommu_setup_dma_ops(struct device *dev,
struct iommu_domain *domain)
@@ -36,11 +35,6 @@ static inline void iommu_teardown_dma_ops(struct device *dev)
{
}

-static inline int iommu_dma_init_fq(struct iommu_domain *domain)
-{
- return -EINVAL;
-}
-
static inline int iommu_get_dma_cookie(struct iommu_domain *domain)
{
return -ENODEV;
@@ -50,6 +44,28 @@ static inline void iommu_put_dma_cookie(struct iommu_domain *domain)
{
}

+#endif
+
+#ifdef CONFIG_IOMMU_DMA
+
+void iommu_put_msi_cookie(struct iommu_domain *domain);
+
+int iommu_dma_init_fq(struct iommu_domain *domain);
+
+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);
+
+extern bool iommu_dma_forcedac;
+
+#else /* CONFIG_IOMMU_DMA */
+
+static inline int iommu_dma_init_fq(struct iommu_domain *domain)
+{
+ return -EINVAL;
+}
+
static inline void iommu_put_msi_cookie(struct iommu_domain *domain)
{
}

--
2.55.0