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

Mikko Perttunen mperttunen at nvidia.com
Thu Oct 22 22:12:17 PDT 2026


On Thursday, October 22, 2026 10:16 PM Robin Murphy wrote:
> On 16/10/2026 7:46 am, Mikko Perttunen wrote:
> > 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 at nvidia.com>
> > ---
> >   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;
> 
> Do we strictly need this, or could we in fact already tell the 
> difference via iommu_is_dma_domain()? (Since ARM-API-owned domains via 
> iommu_paging_domain_alloc() will be IOMMU_DOMAIN_UNMANAGED)

We don't, I've removed it.

> 
> > +
> >   	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 4284950a9246..f2d3e8ec3487 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.
> 
> Bit of a corner case, but I think that might technically break on 
> stage-2-only arm-smmu implementations like MMU-400 where the default 
> aperture will be 40 bits. It's probably better to explicitly cap our 
> request to 4GB here to preserve the current behaviour.

Yes, fixed.

> 
> > + */
> > +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;
> 
> It feels like we should actually set the dma_ops here for a real default 
> domain. Sure it might make things a little more complex in the interim, 
> but it'll be more logical when we come to remove the other path.

I've done a bunch of re-arranging: iommu_get_dma_cookie still allocates 
the mapping, but the bitmaps are only initialized here in 
iommu_setup_dma_ops because the domain geometry may not be available 
earlier. And I moved set_dma_ops here as well. Those of course only 
apply to the default domain case. I've also added upper bound limiting 
to alloc_iova to not have the range checks in arm_setup_iommu_dma_ops, 
and because the range information may not be available in 
iommu_setup_dma_ops.

> 
> > +}
> > +
> > +/**
> > + * 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))
> 
> As mentioned, if we can't reasonably hide the proper default domains 
> from external users of to_dma_iommu_mapping(), then I don't think we 
> should warn here, just treat it as a quiet no-op - the reason for the 
> "kick out the ARM domain" idiom is that it's in the way of attaching to 
> a driver-owned domain (whether that be a bare iommu_domain or another 
> ARM dma_iommu_mapping), but a real default domain is inherently out of 
> the way already. That way we can freely convert the rest of the drivers 
> to use default domains without impacting their users, then clean up the 
> detach pattern from the client drivers separately afterwards.

Yep, true. This ends up cleaning up the series nicely as well.

Thank you!
Mikko

> 
> Thanks,
> Robin.
> 
> > +		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)
> >   {
> >   }
> > 
> 
> 







More information about the linux-arm-kernel mailing list