[PATCH v8 10/10] iommu/vt-d: Handle IOASID notifications

From: Jacob Pan
Date: Mon Dec 16 2019 - 15:11:45 EST


IOASID/PASID are shared system resources that can be freed by software
components outside IOMMU subsystem. When status of an IOASID changes,
e.g. freed or suspended, notifications will be available to its users to
take proper action.

This patch adds a notification block such that when IOASID is freed by
other components such as VFIO, associated software and hardware context
can be cleaned.

Signed-off-by: Jacob Pan <jacob.jun.pan@xxxxxxxxxxxxxxx>
---
drivers/iommu/intel-svm.c | 52 +++++++++++++++++++++++++++++++++++++++++++++
include/linux/intel-iommu.h | 2 +-
2 files changed, 53 insertions(+), 1 deletion(-)

diff --git a/drivers/iommu/intel-svm.c b/drivers/iommu/intel-svm.c
index f580b7be63c5..a660e741551c 100644
--- a/drivers/iommu/intel-svm.c
+++ b/drivers/iommu/intel-svm.c
@@ -230,6 +230,48 @@ static LIST_HEAD(global_svm_list);
list_for_each_entry((sdev), &(svm)->devs, list) \
if ((d) != (sdev)->dev) {} else

+static int ioasid_status_change(struct notifier_block *nb,
+ unsigned long code, void *data)
+{
+ ioasid_t ioasid = *(ioasid_t *)data;
+ struct intel_svm_dev *sdev;
+ struct intel_svm *svm;
+
+ if (code == IOASID_FREE) {
+ /*
+ * Unbind all devices associated with this PASID which is
+ * being freed by other users such as VFIO.
+ */
+ svm = ioasid_find(NULL, ioasid, NULL);
+ if (!svm || !svm->iommu)
+ return NOTIFY_DONE;
+
+ if (IS_ERR(svm))
+ return NOTIFY_BAD;
+
+ list_for_each_entry(sdev, &svm->devs, list) {
+ list_del_rcu(&sdev->list);
+ intel_pasid_tear_down_entry(svm->iommu, sdev->dev,
+ svm->pasid);
+ kfree_rcu(sdev, rcu);
+
+ if (list_empty(&svm->devs)) {
+ list_del(&svm->list);
+ ioasid_set_data(ioasid, NULL);
+ kfree(svm);
+ }
+ }
+
+ return NOTIFY_OK;
+ }
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block svm_ioasid_notifier = {
+ .notifier_call = ioasid_status_change,
+};
+
int intel_svm_bind_gpasid(struct iommu_domain *domain,
struct device *dev,
struct iommu_gpasid_bind_data *data)
@@ -319,6 +361,13 @@ int intel_svm_bind_gpasid(struct iommu_domain *domain,
svm->gpasid = data->gpasid;
svm->flags |= SVM_FLAG_GUEST_PASID;
}
+ /* Get notified when IOASID is freed by others, e.g. VFIO */
+ ret = ioasid_add_notifier(data->hpasid, &svm_ioasid_notifier);
+ if (ret) {
+ mmput(svm->mm);
+ kfree(svm);
+ goto out;
+ }
ioasid_set_data(data->hpasid, svm);
INIT_LIST_HEAD_RCU(&svm->devs);
INIT_LIST_HEAD(&svm->list);
@@ -432,6 +481,9 @@ int intel_svm_unbind_gpasid(struct device *dev, int pasid)
* that PASID allocated by one guest cannot be
* used by another.
*/
+ ioasid_remove_notifier(pasid,
+ &svm_ioasid_notifier);
+
ioasid_set_data(pasid, NULL);
kfree(svm);
}
diff --git a/include/linux/intel-iommu.h b/include/linux/intel-iommu.h
index 8c30b23bd838..e2a33c794e8d 100644
--- a/include/linux/intel-iommu.h
+++ b/include/linux/intel-iommu.h
@@ -711,7 +711,7 @@ struct intel_svm_dev {
struct intel_svm {
struct mmu_notifier notifier;
struct mm_struct *mm;
-
+ struct notifier_block *nb;
struct intel_iommu *iommu;
int flags;
int pasid;
--
2.7.4