iommu/arm-smmu-v3: Link domains and devices
authorJean-Philippe Brucker <jean-philippe.brucker@arm.com>
Wed, 17 Apr 2019 18:24:46 +0000 (19:24 +0100)
committerWill Deacon <will.deacon@arm.com>
Tue, 23 Apr 2019 11:23:13 +0000 (12:23 +0100)
When removing a mapping from a domain, we need to send an invalidation to
all devices that might have stored it in their Address Translation Cache
(ATC). In addition when updating the context descriptor of a live domain,
we'll need to send invalidations for all devices attached to it.

Maintain a list of devices in each domain, protected by a spinlock. It is
updated every time we attach or detach devices to and from domains.

It needs to be a spinlock because we'll invalidate ATC entries from
within hardirq-safe contexts, but it may be possible to relax the read
side with RCU later.

Signed-off-by: Jean-Philippe Brucker <jean-philippe.brucker@arm.com>
Signed-off-by: Will Deacon <will.deacon@arm.com>
drivers/iommu/arm-smmu-v3.c

index 7b42548..3e7198e 100644 (file)
@@ -581,6 +581,7 @@ struct arm_smmu_device {
 struct arm_smmu_master {
        struct arm_smmu_device          *smmu;
        struct arm_smmu_domain          *domain;
+       struct list_head                domain_head;
        u32                             *sids;
        unsigned int                    num_sids;
 };
@@ -607,6 +608,9 @@ struct arm_smmu_domain {
        };
 
        struct iommu_domain             domain;
+
+       struct list_head                devices;
+       spinlock_t                      devices_lock;
 };
 
 struct arm_smmu_option_prop {
@@ -1504,6 +1508,9 @@ static struct iommu_domain *arm_smmu_domain_alloc(unsigned type)
        }
 
        mutex_init(&smmu_domain->init_mutex);
+       INIT_LIST_HEAD(&smmu_domain->devices);
+       spin_lock_init(&smmu_domain->devices_lock);
+
        return &smmu_domain->domain;
 }
 
@@ -1721,9 +1728,16 @@ static void arm_smmu_install_ste_for_dev(struct arm_smmu_master *master)
 
 static void arm_smmu_detach_dev(struct arm_smmu_master *master)
 {
-       if (!master->domain)
+       unsigned long flags;
+       struct arm_smmu_domain *smmu_domain = master->domain;
+
+       if (!smmu_domain)
                return;
 
+       spin_lock_irqsave(&smmu_domain->devices_lock, flags);
+       list_del(&master->domain_head);
+       spin_unlock_irqrestore(&smmu_domain->devices_lock, flags);
+
        master->domain = NULL;
        arm_smmu_install_ste_for_dev(master);
 }
@@ -1731,6 +1745,7 @@ static void arm_smmu_detach_dev(struct arm_smmu_master *master)
 static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev)
 {
        int ret = 0;
+       unsigned long flags;
        struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev);
        struct arm_smmu_device *smmu;
        struct arm_smmu_domain *smmu_domain = to_smmu_domain(domain);
@@ -1764,6 +1779,10 @@ static int arm_smmu_attach_dev(struct iommu_domain *domain, struct device *dev)
 
        master->domain = smmu_domain;
 
+       spin_lock_irqsave(&smmu_domain->devices_lock, flags);
+       list_add(&master->domain_head, &smmu_domain->devices);
+       spin_unlock_irqrestore(&smmu_domain->devices_lock, flags);
+
        if (smmu_domain->stage == ARM_SMMU_DOMAIN_S1)
                arm_smmu_write_ctx_desc(smmu, &smmu_domain->s1_cfg);