[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