---
drivers/iommu/intel/iommu.c | 92 ++++++++++++++++++++++++++++++++-----
include/linux/intel-iommu.h | 11 ++++-
2 files changed, 90 insertions(+), 13 deletions(-)
diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c
index a49afa11673c..4274b4acc325 100644
--- a/drivers/iommu/intel/iommu.c
+++ b/drivers/iommu/intel/iommu.c
@@ -1881,6 +1881,7 @@ static struct dmar_domain *alloc_domain(int flags)
domain->flags |= DOMAIN_FLAG_USE_FIRST_LEVEL;
domain->has_iotlb_device = false;
INIT_LIST_HEAD(&domain->devices);
+ INIT_LIST_HEAD(&domain->sub_devices);
return domain;
}
@@ -5172,33 +5173,79 @@ is_aux_domain(struct device *dev, struct iommu_domain
*domain)
domain->type == IOMMU_DOMAIN_UNMANAGED;
}
+static inline
+void _auxiliary_link_device(struct dmar_domain *domain,
+ struct subdevice_domain_info *subinfo,
+ struct device *dev)
+{
+ subinfo->users++;
+}
+
+static inline
+int _auxiliary_unlink_device(struct dmar_domain *domain,
+ struct subdevice_domain_info *subinfo,
+ struct device *dev)
+{
+ subinfo->users--;
+ return subinfo->users;
+}
+
static void auxiliary_link_device(struct dmar_domain *domain,
struct device *dev)
{
struct device_domain_info *info = get_domain_info(dev);
+ struct subdevice_domain_info *subinfo;
assert_spin_locked(&device_domain_lock);
if (WARN_ON(!info))
return;
+ subinfo = kzalloc(sizeof(*subinfo), GFP_ATOMIC);
+ if (!subinfo)
+ return;
+
+ subinfo->domain = domain;
+ subinfo->dev = dev;
+ list_add(&subinfo->link_domain, &info->auxiliary_domains);
+ list_add(&subinfo->link_phys, &domain->sub_devices);
+ _auxiliary_link_device(domain, subinfo, dev);
domain->auxd_refcnt++;
- list_add(&domain->auxd, &info->auxiliary_domains);
}
-static void auxiliary_unlink_device(struct dmar_domain *domain,
- struct device *dev)
+static struct subdevice_domain_info *
+subdevice_domain_info_lookup(struct dmar_domain *domain, struct device *dev)
+{
+ struct subdevice_domain_info *subinfo;
+
+ assert_spin_locked(&device_domain_lock);
+
+ list_for_each_entry(subinfo, &domain->sub_devices, link_phys)
+ if (subinfo->dev == dev)
+ return subinfo;
+
+ return NULL;
+}
+
+static int auxiliary_unlink_device(struct dmar_domain *domain,
+ struct subdevice_domain_info *subinfo,
+ struct device *dev)
{
struct device_domain_info *info = get_domain_info(dev);
+ int ret;
assert_spin_locked(&device_domain_lock);
if (WARN_ON(!info))
- return;
+ return -EINVAL;
- list_del(&domain->auxd);
+ ret = _auxiliary_unlink_device(domain, subinfo, dev);
+ if (ret == 0) {
+ list_del(&subinfo->link_domain);
+ list_del(&subinfo->link_phys);
+ kfree(subinfo);
+ }
domain->auxd_refcnt--;
- if (!domain->auxd_refcnt && domain->default_pasid > 0)
- ioasid_free(domain->default_pasid);
+ return ret;
}
static int aux_domain_add_dev(struct dmar_domain *domain,
@@ -5207,6 +5254,8 @@ static int aux_domain_add_dev(struct dmar_domain *domain,
int ret;
unsigned long flags;
struct intel_iommu *iommu;
+ struct device_domain_info *info = get_domain_info(dev);
+ struct subdevice_domain_info *subinfo;
iommu = device_to_iommu(dev, NULL, NULL);
if (!iommu)
@@ -5227,6 +5276,12 @@ static int aux_domain_add_dev(struct dmar_domain *domain,
}
spin_lock_irqsave(&device_domain_lock, flags);
+ subinfo = subdevice_domain_info_lookup(domain, dev);
+ if (subinfo) {
+ _auxiliary_link_device(domain, subinfo, dev);
+ spin_unlock_irqrestore(&device_domain_lock, flags);
+ return 0;
+ }
/*
* iommu->lock must be held to attach domain to iommu and setup the
* pasid entry for second level translation.
@@ -5270,6 +5325,7 @@ static void aux_domain_remove_dev(struct dmar_domain
*domain,
struct device_domain_info *info;
struct intel_iommu *iommu;
unsigned long flags;
+ struct subdevice_domain_info *subinfo;
if (!is_aux_domain(dev, &domain->domain))
return;
@@ -5278,14 +5334,26 @@ static void aux_domain_remove_dev(struct dmar_domain
*domain,
info = get_domain_info(dev);
iommu = info->iommu;
- auxiliary_unlink_device(domain, dev);
+ subinfo = subdevice_domain_info_lookup(domain, dev);
+ if (!subinfo) {
+ spin_unlock_irqrestore(&device_domain_lock, flags);
+ return;
+ }
- spin_lock(&iommu->lock);
- intel_pasid_tear_down_entry(iommu, dev, domain->default_pasid, false);
- domain_detach_iommu(domain, iommu);
- spin_unlock(&iommu->lock);
+ if (auxiliary_unlink_device(domain, subinfo, dev) == 0) {
+ spin_lock(&iommu->lock);
+ intel_pasid_tear_down_entry(iommu,
+ dev,
+ domain->default_pasid,
+ false);
+ domain_detach_iommu(domain, iommu);
+ spin_unlock(&iommu->lock);
+ }
spin_unlock_irqrestore(&device_domain_lock, flags);
+
+ if (!domain->auxd_refcnt && domain->default_pasid > 0)
+ ioasid_free(domain->default_pasid);
}
static int prepare_domain_attach_device(struct iommu_domain *domain,
diff --git a/include/linux/intel-iommu.h b/include/linux/intel-iommu.h
index 94522685a0d9..1fb3d6ab719a 100644
--- a/include/linux/intel-iommu.h
+++ b/include/linux/intel-iommu.h
@@ -537,7 +537,7 @@ struct dmar_domain {
bool has_iotlb_device;
struct list_head devices; /* all devices' list */
- struct list_head auxd; /* link to device's auxiliary list */
+ struct list_head sub_devices; /* all devices' list attached via
aux-attach */
struct iova_domain iovad; /* iova's that belong to this domain */
struct dma_pte *pgd; /* virtual address */
@@ -636,6 +636,15 @@ struct device_domain_info {
struct pasid_table *pasid_table; /* pasid table */
};
+/* Aux attach device domain info */
+struct subdevice_domain_info {
+ struct device *dev;
+ struct dmar_domain *domain;
+ struct list_head link_phys; /* link to phys device siblings */
+ struct list_head link_domain; /* link to domain siblings */
+ int users;
+};
+
static inline void __iommu_flush_cache(
struct intel_iommu *iommu, void *addr, int size)
{