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 <[email protected]>
> > ---
> >   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)
> >   {
> >   }
> > 
> 
> 




Reply via email to