[PATCH v2 2/6] iommu/sun50i: Track masters attached to the domain

From: Samuel Holland
Date: Mon Jan 02 2023 - 20:09:55 EST


The refcount logic here is broken. The refcount is initialized to 1 with
no devices attached, so it will be decremented back to 1 when the last
device is detached. However, refcount_dec_and_test() returns true only
when the refcount gets decremented to zero, so the domain will never be
cleaned up, and the hardware will never be disabled.

Replace the refcount with a mask of masters attached to the domain. The
domain can be cleaned up when the last master detaches. This mask is
also necessary to correctly set the bypass register.

Fixes: 4100b8c229b3 ("iommu: Add Allwinner H6 IOMMU driver")
Signed-off-by: Samuel Holland <samuel@xxxxxxxxxxxx>
---

Changes in v2:
- New patch for v2

drivers/iommu/sun50i-iommu.c | 28 +++++++++++++---------------
1 file changed, 13 insertions(+), 15 deletions(-)

diff --git a/drivers/iommu/sun50i-iommu.c b/drivers/iommu/sun50i-iommu.c
index 5b585eace3d4..3757d5a18318 100644
--- a/drivers/iommu/sun50i-iommu.c
+++ b/drivers/iommu/sun50i-iommu.c
@@ -114,8 +114,8 @@ struct sun50i_iommu {
struct sun50i_iommu_domain {
struct iommu_domain domain;

- /* Number of devices attached to the domain */
- refcount_t refcnt;
+ /* Mask of masters attached to this domain */
+ atomic_t masters;

/* L1 Page Table */
u32 *dt;
@@ -684,8 +684,6 @@ static struct iommu_domain *sun50i_iommu_domain_alloc(unsigned type)
if (!sun50i_domain->dt)
goto err_free_domain;

- refcount_set(&sun50i_domain->refcnt, 1);
-
sun50i_domain->domain.geometry.aperture_start = 0;
sun50i_domain->domain.geometry.aperture_end = DMA_BIT_MASK(32);
sun50i_domain->domain.geometry.force_aperture = true;
@@ -760,23 +758,27 @@ static void sun50i_iommu_detach_domain(struct sun50i_iommu *iommu,
static void sun50i_iommu_detach_device(struct iommu_domain *domain,
struct device *dev)
{
+ struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev);
struct sun50i_iommu_domain *sun50i_domain = to_sun50i_domain(domain);
struct sun50i_iommu *iommu = dev_iommu_priv_get(dev);
+ int masters = 0;

dev_dbg(dev, "Detaching from IOMMU domain\n");

- if (iommu->domain != domain)
- return;
+ for (unsigned int i = 0; i < fwspec->num_ids; i++)
+ masters |= BIT(fwspec->ids[i]);

- if (refcount_dec_and_test(&sun50i_domain->refcnt))
+ if (atomic_fetch_andnot(masters, &sun50i_domain->masters) == masters)
sun50i_iommu_detach_domain(iommu, sun50i_domain);
}

static int sun50i_iommu_attach_device(struct iommu_domain *domain,
struct device *dev)
{
+ struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev);
struct sun50i_iommu_domain *sun50i_domain = to_sun50i_domain(domain);
struct sun50i_iommu *iommu;
+ int masters = 0;

iommu = sun50i_iommu_from_dev(dev);
if (!iommu)
@@ -784,15 +786,11 @@ static int sun50i_iommu_attach_device(struct iommu_domain *domain,

dev_dbg(dev, "Attaching to IOMMU domain\n");

- refcount_inc(&sun50i_domain->refcnt);
-
- if (iommu->domain == domain)
- return 0;
-
- if (iommu->domain)
- sun50i_iommu_detach_device(iommu->domain, dev);
+ for (unsigned int i = 0; i < fwspec->num_ids; i++)
+ masters |= BIT(fwspec->ids[i]);

- sun50i_iommu_attach_domain(iommu, sun50i_domain);
+ if (atomic_fetch_or(masters, &sun50i_domain->masters) == 0)
+ sun50i_iommu_attach_domain(iommu, sun50i_domain);

return 0;
}
--
2.37.4