[PATCH v4 13/18] iommu/vt-d: Preserve PASID table of preserved device
Samiullah Khawaja <[email protected]>
| Newsgroups | dev.linux.lists.iommu,org.kernel.vger.kvm,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
In scalable mode the PASID table is used to fetch the io page tables. Preserve and restore the PASID table of the preserved devices. Signed-off-by: Samiullah Khawaja <[email protected]> --- drivers/iommu/intel/liveupdate.c | 130 +++++++++++++++++++++++++++++++ drivers/iommu/intel/pasid.c | 10 ++- drivers/iommu/intel/pasid.h | 8 ++ include/linux/kho/abi/iommu.h | 13 ++++ 4 files changed, 159 insertions(+), 2 deletions(-) diff --git a/drivers/iommu/intel/liveupdate.c b/drivers/iommu/intel/liveupdate.c index 05dea3893399..b324697aac92 100644 --- a/drivers/iommu/intel/liveupdate.c +++ b/drivers/iommu/intel/liveupdate.c @@ -15,6 +15,7 @@ #include <linux/pci-ats.h> #include "iommu.h" +#include "pasid.h" #include "../iommu-pages.h" /* 2 tables per bus in scalable mode with upper table at odd bit */ @@ -462,6 +463,69 @@ int intel_iommu_restore_device(struct iommu_domain *domain, return ret; } +enum pasid_lu_op { + PASID_LU_OP_PRESERVE = 1, + PASID_LU_OP_UNPRESERVE, + PASID_LU_OP_RESTORE, +}; + +static int pasid_lu_do_op(void *table, enum pasid_lu_op op) +{ + int ret = 0; + + switch (op) { + case PASID_LU_OP_PRESERVE: + ret = iommu_preserve_pages(table); + break; + case PASID_LU_OP_UNPRESERVE: + iommu_unpreserve_pages(table); + break; + case PASID_LU_OP_RESTORE: + iommu_restore_pages(virt_to_phys(table)); + break; + } + + return ret; +} + +static int pasid_lu_handle_pd(struct pasid_dir_entry *dir, + u32 max_pasid, enum pasid_lu_op op) +{ + int max_pde = max_pasid >> PASID_PDE_SHIFT; + struct pasid_entry *table; + int i, ret; + + for (i = 0; i < max_pde; i++) { + table = get_pasid_table_from_pde(&dir[i]); + if (!table) + continue; + + ret = pasid_lu_do_op(table, op); + if (ret) + goto err; + } + + ret = pasid_lu_do_op(dir, op); + if (ret) + goto err; + + return 0; + +err: + if (op != PASID_LU_OP_PRESERVE) + return ret; + + while (i > 0) { + table = get_pasid_table_from_pde(&dir[--i]); + if (!table) + continue; + + pasid_lu_do_op(table, PASID_LU_OP_UNPRESERVE); + } + + return ret; +} + /** * intel_iommu_preserve_device() - Intel IOMMU callback to preserve device state * @dev: Target device @@ -473,6 +537,7 @@ int intel_iommu_preserve_device(struct device *dev, struct iommu_device_ser *device_ser) { struct device_domain_info *info = dev_iommu_priv_get(dev); + struct pasid_table *pasid_table; int ret; if (!dev_is_pci(dev)) { @@ -492,6 +557,22 @@ int intel_iommu_preserve_device(struct device *dev, device_ser->domain_iommu_ser.attachment_id = domain_id_iommu(info->domain, info->iommu); + + if (!sm_supported(info->iommu)) + return 0; + + pasid_table = intel_pasid_get_table(dev); + if (!pasid_table) + return -EINVAL; + + ret = pasid_lu_handle_pd(pasid_table->table, + pasid_table->max_pasid, + PASID_LU_OP_PRESERVE); + if (ret) + return ret; + + device_ser->intel.pasid_table = virt_to_phys(pasid_table->table); + device_ser->intel.max_pasid = pasid_table->max_pasid; return 0; } @@ -503,6 +584,28 @@ int intel_iommu_preserve_device(struct device *dev, void intel_iommu_unpreserve_device(struct device *dev, struct iommu_device_ser *device_ser) { + struct device_domain_info *info = dev_iommu_priv_get(dev); + struct pasid_table *pasid_table; + + if (!dev_is_pci(dev)) + return; + + if (!info) + return; + + if (!sm_supported(info->iommu)) + return; + + if (!device_ser->intel.pasid_table) + return; + + pasid_table = intel_pasid_get_table(dev); + if (!pasid_table) + return; + + pasid_lu_handle_pd(pasid_table->table, + pasid_table->max_pasid, + PASID_LU_OP_UNPRESERVE); } /** @@ -547,3 +650,30 @@ void intel_iommu_unpreserve(struct iommu_device *iommu_dev, unpreserve_iommu_context_tables(iommu, ser); iommu_unpreserve_pages(iommu->root_entry); } + +/** + * intel_pasid_restore_table() - Restore preserved PASID table for a device + * @dev: Restored device + * @max_pasid: Maximum supported PASID + * + * Return: Pointer to restored PASID table directory, or NULL if not preserved. + */ +void *intel_pasid_restore_table(struct device *dev, u64 max_pasid) +{ + struct iommu_device_ser *ser = dev_iommu_restored_state(dev); + + if (!ser || !ser->intel.pasid_table) + return NULL; + + /* + * MAX PASID of a device should not change as it is read from + * capabilities. + */ + BUG_ON(ser->intel.max_pasid != max_pasid); + + BUG_ON(pasid_lu_handle_pd(phys_to_virt(ser->intel.pasid_table), + ser->intel.max_pasid, + PASID_LU_OP_RESTORE)); + + return phys_to_virt(ser->intel.pasid_table); +} diff --git a/drivers/iommu/intel/pasid.c b/drivers/iommu/intel/pasid.c index 81353fd46b37..ae09d47ca8a6 100644 --- a/drivers/iommu/intel/pasid.c +++ b/drivers/iommu/intel/pasid.c @@ -13,6 +13,7 @@ #include <linux/cpufeature.h> #include <linux/dmar.h> #include <linux/iommu.h> +#include <linux/iommu-liveupdate.h> #include <linux/memory.h> #include <linux/pci.h> #include <linux/pci-ats.h> @@ -60,8 +61,13 @@ int intel_pasid_alloc_table(struct device *dev) size = max_pasid >> (PASID_PDE_SHIFT - 3); order = size ? get_order(size) : 0; - dir = iommu_alloc_pages_node_sz(info->iommu->node, GFP_KERNEL, - 1 << (order + PAGE_SHIFT)); + + max_pasid = 1 << (order + PAGE_SHIFT + 3); + if (dev_iommu_restored_state(dev)) + dir = intel_pasid_restore_table(dev, max_pasid); + else + dir = iommu_alloc_pages_node_sz(info->iommu->node, GFP_KERNEL, + 1 << (order + PAGE_SHIFT)); if (!dir) { kfree(pasid_table); return -ENOMEM; diff --git a/drivers/iommu/intel/pasid.h b/drivers/iommu/intel/pasid.h index 48d3bb6b68de..801768cdea16 100644 --- a/drivers/iommu/intel/pasid.h +++ b/drivers/iommu/intel/pasid.h @@ -301,6 +301,14 @@ static inline void pasid_set_eafe(struct pasid_entry *pe) extern unsigned int intel_pasid_max_id; int intel_pasid_alloc_table(struct device *dev); +#ifdef CONFIG_IOMMU_LIVEUPDATE +void *intel_pasid_restore_table(struct device *dev, u64 max_pasid); +#else +static inline void *intel_pasid_restore_table(struct device *dev, u64 max_pasid) +{ + return NULL; +} +#endif void intel_pasid_free_table(struct device *dev); struct pasid_table *intel_pasid_get_table(struct device *dev); int intel_pasid_setup_first_level(struct intel_iommu *iommu, struct device *dev, diff --git a/include/linux/kho/abi/iommu.h b/include/linux/kho/abi/iommu.h index 430c42bf9561..f87a92ff6a38 100644 --- a/include/linux/kho/abi/iommu.h +++ b/include/linux/kho/abi/iommu.h @@ -129,6 +129,16 @@ struct iommu_dev_map_ser { u64 iommu_phys; } __packed; +/** + * struct iommu_device_intel_ser - Intel specific state of serialized device + * @pasid_table: Physical address of pasid table + * @max_pasid: Maximum supported pasid + */ +struct iommu_device_intel_ser { + u64 pasid_table; + u64 max_pasid; +} __packed; + /** * struct iommu_device_ser - Serialized state of a device * @hdr: Common object header @@ -141,6 +151,9 @@ struct iommu_device_ser { u32 devid; u32 pci_domain_nr; struct iommu_dev_map_ser domain_iommu_ser; + union { + struct iommu_device_intel_ser intel; + }; } __packed; -- 2.55.0.679.g6767b8d81c-goog