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)
> > {
> > }
> >
>
>