IOMMU and VT-d driver support for Shared Virtual Address (SVA)
Subsystems (3)
v4 cover IOMMU and VT-d driver support for Shared Virtual Address (SVA)
Hi All,
Shared virtual address (SVA), a.k.a, Shared virtual memory (SVM) on Intel platforms allow address space sharing between device DMA and applications. SVA can reduce programming complexity and enhance security. To enable SVA in the guest, i.e. shared guest application address space and physical device DMA address, IOMMU driver must provide some new functionalities.
This patchset is a follow-up on the discussions held at LPC 2017 VFIO/IOMMU/PCI track. Slides and notes can be found here:
https://linuxplumbersconf.org/2017/ocw/events/LPC2017/tracks/636
The complete guest SVA support also involves changes in QEMU and VFIO, which has been posted earlier.
https://www.spinics.net/lists/kvm/msg148798.html
This is the IOMMU portion follow up of the more complete series of the kernel changes to support vSVA. Please refer to the link below for more details. https://www.spinics.net/lists/kvm/msg148819.html
Generic APIs are introduced in addition to Intel VT-d specific changes, the goal is to have common interfaces across IOMMU and device types for both VFIO and other in-kernel users.
At the top level, new IOMMU interfaces are introduced as follows:
- bind guest PASID table
- passdown invalidations of translation caches
- IOMMU device fault reporting including page request/response and non-recoverable faults.
For IOMMU detected device fault reporting, struct device is extended to provide callback and tracking at device level. The original proposal was discussed here "Error handling for I/O memory management units"
(https://lwn.net/Articles/608914/). I have experimented two alternative solutions:
1. use a shared group notifier, this does not scale well also causes unwanted notification traffic when group sibling device is reported with faults.
2. place fault callback at device IOMMU arch data, e.g. device_domain_info in Intel/FSL IOMMU driver. This will cause code duplication, since per device fault reporting is generic.
The additional patches are Intel VT-d specific, which either implements or replaces existing private interfaces with the generic ones.
This patchset is based on the work and ideas from many people, especially:
Ashok Raj <[email protected]> Liu, Yi L <yi.l.liu-VuQAYsv1563Yd54FQh9/[email protected]> Jean-Philippe Brucker <[email protected]>
Thanks,
Jacob
V4
- Futher integrate feedback for iommu_param and iommu_fault_param from Jean and others.
- Handle fault reporting error and race conditions. Keep tracking per device pending page requests such that page group response can be sanitized.
- Added a timer to handle irresponsive guest who does not send page response on time.
- Use a workqueue for VT-d non-recorverable IRQ fault handling.
- Added trace events for invalidation and fault reporting.
V3
- Consolidated fault reporting data format based on discussions on v2, including input from ARM and AMD.
- Renamed invalidation APIs from svm to sva based on discussions on v2
- Use a parent pointer under struct device for all iommu per device data
- Simplified device fault callback, allow driver private data to be registered. This might make it easy to replace domain fault handler.
V2
- Replaced hybrid interface data model (generic data + vendor specific data) with all generic data. This will have the security benefit where data passed from user space can be sanitized by all software layers if needed.
- Addressed review comments from V1
- Use per device fault report data
- Support page request/response communications between host IOMMU and guest or other in-kernel users.
- Added unrecoverable fault reporting to DMAR
- Use threaded IRQ function for DMAR fault interrupt and fault reporting
Jacob Pan (21):
iommu: introduce bind_pasid_table API function iommu/vt-d: move device_domain_info to header iommu/vt-d: add a flag for pasid table bound status iommu/vt-d: add bind_pasid_table function iommu/vt-d: add definitions for PFSID iommu/vt-d: fix dev iotlb pfsid use
iommu/vt-d: support flushing more translation cache types iommu/vt-d: add svm/sva invalidate function
iommu: introduce device fault data
driver core: add per device iommu param
iommu: introduce device fault report API
iommu: introduce page response function
iommu: handle page response timeout iommu/config: add build dependency for dmar iommu/vt-d: report non-recoverable faults to device iommu/intel-svm: report device page request iommu/intel-svm: replace dev ops with fault report API iommu/intel-svm: do not flush iotlb for viommu iommu/vt-d: add intel iommu page response function trace/iommu: add sva trace events
iommu: use sva invalidate and device fault trace event
Liu, Yi L (1):
iommu: introduce iommu invalidate API function
drivers/iommu/Kconfig | 1 + drivers/iommu/dmar.c | 207 ++++++++++++++++++++++- drivers/iommu/intel-iommu.c | 376 +++++++++++++++++++++++++++++++++++++++--- drivers/iommu/intel-svm.c | 82 +++++++-- drivers/iommu/iommu.c | 284 ++++++++++++++++++++++++++++++- include/linux/device.h | 3 + include/linux/dma_remapping.h | 1 + include/linux/dmar.h | 2 +- include/linux/intel-iommu.h | 52 +++++- include/linux/intel-svm.h | 20 +-- include/linux/iommu.h | 226 ++++++++++++++++++++++++- include/trace/events/iommu.h | 112 +++++++++++++ include/uapi/linux/iommu.h | 111 +++++++++++++ 13 files changed, 1405 insertions(+), 72 deletions(-) create mode 100644 include/uapi/linux/iommu.h
Signature · 1 line
2.7.4
PATCH v4 1/22 iommu: introduce bind_pasid_table API function
Virtual IOMMU was proposed to support Shared Virtual Memory (SVM) use in the guest:
https://lists.gnu.org/archive/html/qemu-devel/2016-11/msg05311.html
As part of the proposed architecture, when an SVM capable PCI device is assigned to a guest, nested mode is turned on. Guest owns the first level page tables (request with PASID) which performs GVA->GPA translation. Second level page tables are owned by the host for GPA->HPA translation for both request with and without PASID.
A new IOMMU driver interface is therefore needed to perform tasks as follows:
* Enable nested translation and appropriate translation type
* Assign guest PASID table pointer (in GPA) and size to host IOMMU
This patch introduces new API functions to perform bind/unbind guest PASID tables. Based on common data, model specific IOMMU drivers can be extended to perform the specific steps for binding pasid table of assigned devices.
--- drivers/iommu/iommu.c | 19 +++++++++++++++++++ include/linux/iommu.h | 24 ++++++++++++++++++++++++ include/uapi/linux/iommu.h | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+) create mode 100644 include/uapi/linux/iommu.h
drivers/iommu/iommu.c +19 -0
@@ -1325,6 +1325,25 @@ int iommu_attach_device(struct iommu_domain *domain, struct device *dev) }EXPORT_SYMBOL_GPL (iommu_attach_device); +int iommu_bind_pasid_table (struct iommu_domain *domain,struct device *dev, +struct pasid_table_config *pasidt_binfo) +{ +if (unlikely (!domain->ops->bind_pasid_table)) +return -ENODEV ; + +return domain->ops->bind_pasid_table (domain, dev, pasidt_binfo); +} +EXPORT_SYMBOL_GPL (iommu_bind_pasid_table); + +void iommu_unbind_pasid_table (struct iommu_domain *domain,struct device *dev) +{ +if (unlikely (!domain->ops->unbind_pasid_table)) +return ; + + domain->ops->unbind_pasid_table (domain, dev); +} +EXPORT_SYMBOL_GPL (iommu_unbind_pasid_table); +static void __iommu_detach_device (struct iommu_domain *domain,struct device *dev) {
include/linux/iommu.h +24 -0
@@ -25,6 +25,7 @@#include <linux/errno.h> #include <linux/err.h> #include <linux/of.h> +#include <uapi/linux/iommu.h> #define IOMMU_READ (1 << 0)#define IOMMU_WRITE (1 << 1)
@@ -187,6 +188,8 @@ struct iommu_resv_region {* @domain_get_windows: Return the number of windows for a domain * @of_xlate: add OF master IDs to iommu grouping * @pgsize_bitmap: bitmap of all possible supported page sizes +* @bind_pasid_table: bind pasid table pointer for guest SVM +* @unbind_pasid_table: unbind pasid table pointer and restore defaults */ struct iommu_ops {bool (*capable)(enum iommu_cap );
@@ -233,8 +236,14 @@ struct iommu_ops {u32 (*domain_get_windows)(struct iommu_domain *domain);int (*of_xlate)(struct device *dev,struct of_phandle_args *args); +bool (*is_attach_deferred)(struct iommu_domain *domain,struct device *dev); +int (*bind_pasid_table)(struct iommu_domain *domain,struct device *dev, +struct pasid_table_config *pasidt_binfo); +void (*unbind_pasid_table)(struct iommu_domain *domain, +struct device *dev); +unsigned long pgsize_bitmap; };
@@ -296,6 +305,10 @@ extern int iommu_attach_device(struct iommu_domain *domain, struct device *dev);extern void iommu_detach_device (struct iommu_domain *domain,struct device *dev); +extern int iommu_bind_pasid_table (struct iommu_domain *domain, +struct device *dev,struct pasid_table_config *pasidt_binfo); +extern void iommu_unbind_pasid_table (struct iommu_domain *domain, +struct device *dev);extern struct iommu_domain *iommu_get_domain_for_dev (struct device *dev);extern int iommu_map (struct iommu_domain *domain,unsigned long iova,phys_addr_t paddr,size_t size,int prot);
@@ -696,6 +709,17 @@ const struct iommu_ops *iommu_ops_from_fwnode(struct fwnode_handle *fwnode)return NULL ; } +static inline +int iommu_bind_pasid_table (struct iommu_domain *domain,struct device *dev, +struct pasid_table_config *pasidt_binfo) +{ +return -EINVAL ; +} +static inline +void iommu_unbind_pasid_table (struct iommu_domain *domain,struct device *dev) +{ +} +#endif /* CONFIG_IOMMU_API */ #endif /* __LINUX_IOMMU_H */
include/uapi/linux/iommu.h new file +32 -0
@@ -0,0 +1,32 @@ +/* +* IOMMU user API definitions +* +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 2 as +* published by the Free Software Foundation. +*/ + +#ifndef _UAPI_IOMMU_H +#define _UAPI_IOMMU_H + +#include <linux/types.h> + +/** +* PASID table data used to bind guest PASID table to the host IOMMU. This will +* enable guest managed first level page tables. +* @version: for future extensions and identification of the data format +* @bytes: size of this structure +* @base_ptr: PASID table pointer +* @pasid_bits: number of bits supported in the guest PASID table, must be less +* or equal than the host supported PASID size. +*/ +struct pasid_table_config { +__u32 version; +#define PASID_TABLE_CFG_VERSION 1 +__u32 bytes; +__u64 base_ptr; +__u8 pasid_bits; +}; + +#endif /* _UAPI_IOMMU_H */
Signature · 1 line
2.7.4
PATCH v4 2/22 iommu/vt-d: move device_domain_info to header
In reply to Jacob PanAllow both intel-iommu.c and dmar.c to access device_domain_info. Prepare for additional per device arch data used in TLB flush function
--- drivers/iommu/intel-iommu.c | 18 ------------------ include/linux/intel-iommu.h | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+), 18 deletions(-)
drivers/iommu/intel-iommu.c +0 -18
@@ -414,24 +414,6 @@ struct dmar_domain {iommu core */ }; -/* PCI domain-device relationship */ -struct device_domain_info { -struct list_head link;/* link to domain siblings */ -struct list_head global;/* link to global list */ -u8 bus;/* PCI bus number */ -u8 devfn;/* PCI devfn number */ -u8 pasid_supported:3 ; -u8 pasid_enabled:1 ; -u8 pri_supported:1 ; -u8 pri_enabled:1 ; -u8 ats_supported:1 ; -u8 ats_enabled:1 ; -u8 ats_qdep; -struct device *dev;/* it's NULL for PCIe-to-PCI bridge */ -struct intel_iommu *iommu;/* IOMMU used by this device */ -struct dmar_domain *domain;/* pointer to domain */ -}; -struct dmar_rmrr_unit {struct list_head list;/* list of rmrr units */ struct acpi_dmar_header *hdr;/* ACPI header */
include/linux/intel-iommu.h +19 -0
@@ -434,6 +434,25 @@ struct intel_iommu {u32 flags;/* Software defined flags */ }; +/* PCI domain-device relationship */ +struct device_domain_info { +struct list_head link;/* link to domain siblings */ +struct list_head global;/* link to global list */ +u8 bus;/* PCI bus number */ +u8 devfn;/* PCI devfn number */ +u8 pasid_supported:3 ; +u8 pasid_enabled:1 ; +u8 pri_supported:1 ; +u8 pri_enabled:1 ; +u8 ats_supported:1 ; +u8 ats_enabled:1 ; +u8 ats_qdep; +u64 fault_mask;/* selected IOMMU faults to be reported */ +struct device *dev;/* it's NULL for PCIe-to-PCI bridge */ +struct intel_iommu *iommu;/* IOMMU used by this device */ +struct dmar_domain *domain;/* pointer to domain */ +}; +static inline void __iommu_flush_cache (struct intel_iommu *iommu,void *addr,int size) {
Signature · 1 line
2.7.4
PATCH v4 3/22 iommu/vt-d: add a flag for pasid table bound status
In reply to Jacob PanAdding a flag in device domain into to track whether a guest or user PASID table is bound to a device.
--- include/linux/intel-iommu.h | 1 + 1 file changed, 1 insertion(+)
include/linux/intel-iommu.h +1 -0
@@ -446,6 +446,7 @@ struct device_domain_info { u8 pri_enabled:1; u8 ats_supported:1; u8 ats_enabled:1; + u8 pasid_table_bound:1;u8 ats_qdep;u64 fault_mask;/* selected IOMMU faults to be reported */ struct device *dev;/* it's NULL for PCIe-to-PCI bridge */
Signature · 1 line
2.7.4
PATCH v4 4/22 iommu/vt-d: add bind_pasid_table function
In reply to Jacob PanAdd Intel VT-d ops to the generic iommu_bind_pasid_table API functions.
The primary use case is for direct assignment of SVM capable device. Originated from emulated IOMMU in the guest, the request goes through many layers (e.g. VFIO). Upon calling host IOMMU driver, caller passes guest PASID table pointer (GPA) and size.
Device context table entry is modified by Intel IOMMU specific bind_pasid_table function. This will turn on nesting mode and matching translation type.
The unbind operation restores default context mapping.
--- drivers/iommu/intel-iommu.c | 119 ++++++++++++++++++++++++++++++++++++++++++ include/linux/dma_remapping.h | 1 + 2 files changed, 120 insertions(+)
drivers/iommu/intel-iommu.c +119 -0
@@ -2432,6 +2432,7 @@ static struct dmar_domain *dmar_insert_one_dev_info(struct intel_iommu *iommu, info->ats_supported = info->pasid_supported = info->pri_supported =0 ; info->ats_enabled = info->pasid_enabled = info->pri_enabled =0 ; info->ats_qdep =0 ; + info->pasid_table_bound =0 ; info->dev = dev; info->domain = domain; info->iommu = iommu;
@@ -5189,6 +5190,7 @@ static void intel_iommu_put_resv_regions(struct device *dev,#ifdef CONFIG_INTEL_IOMMU_SVM #define MAX_NR_PASID_BITS (20) +#define MIN_NR_PASID_BITS (5)static inline unsigned long intel_iommu_get_pts (struct intel_iommu *iommu) {/*
@@ -5315,6 +5317,119 @@ struct intel_iommu *intel_svm_device_to_iommu(struct device *dev)return iommu; } + +static int intel_iommu_bind_pasid_table (struct iommu_domain *domain, +struct device *dev,struct pasid_table_config *pasidt_binfo) +{ +struct intel_iommu *iommu; +struct context_entry *context; +struct dmar_domain *dmar_domain =to_dmar_domain (domain); +struct device_domain_info *info; +struct pci_dev *pdev; +u8 bus, devfn, host_table_pasid_bits; +u16 did, sid; +int ret =0 ; +unsigned long flags; +u64 ctx_lo; + + iommu =device_to_iommu (dev, &bus, &devfn); +if (!iommu) +return -ENODEV ; +/* VT-d spec section 9.4 says pasid table size is encoded as 2^(x+5) */ + host_table_pasid_bits =intel_iommu_get_pts (iommu) +MIN_NR_PASID_BITS ; +if (!pasidt_binfo || pasidt_binfo->pasid_bits > host_table_pasid_bits || + pasidt_binfo->pasid_bits <MIN_NR_PASID_BITS ) { +pr_err ("Invalid gPASID bits %d, host range %d - %d\n" , + pasidt_binfo->pasid_bits, +MIN_NR_PASID_BITS , host_table_pasid_bits); +return -ERANGE ; + } +if (!ecap_nest (iommu->ecap)) { +dev_err (dev,"Cannot bind PASID table, no nested translation\n" ); + ret = -EINVAL ; + goto out; + } + pdev =to_pci_dev (dev); + sid =PCI_DEVID (bus, devfn); + info = dev->archdata.iommu; + +if (!info) { +dev_err (dev,"Invalid device domain info\n" ); + ret = -EINVAL ; + goto out; + } +if (info->pasid_table_bound) { +dev_err (dev,"Device PASID table already bound\n" ); + ret = -EBUSY ; + goto out; + } +if (!info->pasid_enabled) { + ret =pci_enable_pasid (pdev, info->pasid_supported & ~1 ); +if (ret) { +dev_err (dev,"Failed to enable PASID\n" ); + goto out; + } + } +spin_lock_irqsave (&iommu->lock, flags); + context =iommu_context_addr (iommu, bus, devfn,0 ); +if (!context_present (context)) { +dev_err (dev,"Context not present\n" ); + ret = -EINVAL ; + goto out_unlock; + } + +/* Anticipate guest to use SVM and owns the first level, so we turn +* nested mode on +*/ + ctx_lo = context[0 ].lo; + ctx_lo |=CONTEXT_NESTE |CONTEXT_PRS |CONTEXT_PASIDE ; + ctx_lo &= ~CONTEXT_TT_MASK ; + ctx_lo |=CONTEXT_TT_DEV_IOTLB <<2 ; + context[0 ].lo = ctx_lo; + +/* Assign guest PASID table pointer and size order */ + ctx_lo = (pasidt_binfo->base_ptr &VTD_PAGE_MASK ) | + (pasidt_binfo->pasid_bits -MIN_NR_PASID_BITS ); + context[1 ].lo = ctx_lo; +/* make sure context entry is updated before flushing */ +wmb (); + did = dmar_domain->iommu_did[iommu->seq_id]; + iommu->flush.flush_context (iommu, did, + (((u16 )bus) <<8 ) | devfn, +DMA_CCMD_MASK_NOBIT , +DMA_CCMD_DEVICE_INVL ); + iommu->flush.flush_iotlb (iommu, did,0 ,0 ,DMA_TLB_DSI_FLUSH ); + info->pasid_table_bound =1 ; +out_unlock: +spin_unlock_irqrestore (&iommu->lock, flags); +out: +return ret; +} + +static void intel_iommu_unbind_pasid_table (struct iommu_domain *domain, +struct device *dev) +{ +struct intel_iommu *iommu; +struct dmar_domain *dmar_domain =to_dmar_domain (domain); +struct device_domain_info *info; +u8 bus, devfn; + + info = dev->archdata.iommu; +if (!info) { +dev_err (dev,"Invalid device domain info\n" ); +return ; + } + iommu =device_to_iommu (dev, &bus, &devfn); +if (!iommu) { +dev_err (dev,"No IOMMU for device to unbind PASID table\n" ); +return ; + } + +domain_context_clear (iommu, dev); + +domain_context_mapping_one (dmar_domain, iommu, bus, devfn); + info->pasid_table_bound =0 ; +}#endif /* CONFIG_INTEL_IOMMU_SVM */ const struct iommu_ops intel_iommu_ops = {
@@ -5323,6 +5438,10 @@ const struct iommu_ops intel_iommu_ops = { .domain_free = intel_iommu_domain_free, .attach_dev = intel_iommu_attach_device, .detach_dev = intel_iommu_detach_device, +#ifdef CONFIG_INTEL_IOMMU_SVM + .bind_pasid_table = intel_iommu_bind_pasid_table, + .unbind_pasid_table = intel_iommu_unbind_pasid_table, +#endif .map = intel_iommu_map, .unmap = intel_iommu_unmap, .map_sg = default_iommu_map_sg,
include/linux/dma_remapping.h +1 -0
@@ -28,6 +28,7 @@#define CONTEXT_DINVE (1ULL << 8)#define CONTEXT_PRS (1ULL << 9) +#define CONTEXT_NESTE (1ULL << 10)#define CONTEXT_PASIDE (1ULL << 11)struct intel_iommu ;
Signature · 1 line
2.7.4
PATCH v4 5/22 iommu: introduce iommu invalidate API function
In reply to Jacob PanFrom: "Liu, Yi L" <yi.l.liu-VuQAYsv1563Yd54FQh9/[email protected]>
When an SVM capable device is assigned to a guest, the first level page tables are owned by the guest and the guest PASID table pointer is linked to the device context entry of the physical IOMMU.
Host IOMMU driver has no knowledge of caching structure updates unless the guest invalidation activities are passed down to the host. The primary usage is derived from emulated IOMMU in the guest, where QEMU can trap invalidation activities before passing them down to the host/physical IOMMU.
Since the invalidation data are obtained from user space and will be written into physical IOMMU, we must allow security check at various layers. Therefore, generic invalidation data format are proposed here, model specific IOMMU drivers need to convert them into their own format.
--- drivers/iommu/iommu.c | 14 ++++++++ include/linux/iommu.h | 12 +++++++ include/uapi/linux/iommu.h | 79 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+)
drivers/iommu/iommu.c +14 -0
@@ -1344,6 +1344,20 @@ void iommu_unbind_pasid_table(struct iommu_domain *domain, struct device *dev) }EXPORT_SYMBOL_GPL (iommu_unbind_pasid_table); +int iommu_sva_invalidate (struct iommu_domain *domain, +struct device *dev,struct tlb_invalidate_info *inv_info) +{ +int ret =0 ; + +if (unlikely (!domain->ops->sva_invalidate)) +return -ENODEV ; + + ret = domain->ops->sva_invalidate (domain, dev, inv_info); + +return ret; +} +EXPORT_SYMBOL_GPL (iommu_sva_invalidate); +static void __iommu_detach_device (struct iommu_domain *domain,struct device *dev) {
include/linux/iommu.h +12 -0
@@ -190,6 +190,7 @@ struct iommu_resv_region {* @pgsize_bitmap: bitmap of all possible supported page sizes * @bind_pasid_table: bind pasid table pointer for guest SVM * @unbind_pasid_table: unbind pasid table pointer and restore defaults +* @sva_invalidate: invalidate translation caches of shared virtual address */ struct iommu_ops {bool (*capable)(enum iommu_cap );
@@ -243,6 +244,8 @@ struct iommu_ops { struct pasid_table_config *pasidt_binfo);void (*unbind_pasid_table)(struct iommu_domain *domain,struct device *dev); +int (*sva_invalidate)(struct iommu_domain *domain, +struct device *dev,struct tlb_invalidate_info *inv_info);unsigned long pgsize_bitmap; };
@@ -309,6 +312,9 @@ extern int iommu_bind_pasid_table(struct iommu_domain *domain, struct device *dev, struct pasid_table_config *pasidt_binfo);extern void iommu_unbind_pasid_table (struct iommu_domain *domain,struct device *dev); +extern int iommu_sva_invalidate (struct iommu_domain *domain, +struct device *dev,struct tlb_invalidate_info *inv_info); +extern struct iommu_domain *iommu_get_domain_for_dev (struct device *dev);extern int iommu_map (struct iommu_domain *domain,unsigned long iova,phys_addr_t paddr,size_t size,int prot);
@@ -720,6 +726,12 @@ void iommu_unbind_pasid_table(struct iommu_domain *domain, struct device *dev) { } +static inline int iommu_sva_invalidate (struct iommu_domain *domain, +struct device *dev,struct tlb_invalidate_info *inv_info) +{ +return -EINVAL ; +} +#endif /* CONFIG_IOMMU_API */ #endif /* __LINUX_IOMMU_H */
include/uapi/linux/iommu.h +79 -0
83 lines not on this page · raw message
Signature · 1 line
2.7.4
PATCH v4 6/22 iommu/vt-d: add definitions for PFSID
In reply to Jacob PanWhen SRIOV VF device IOTLB is invalidated, we need to provide the PF source ID such that IOMMU hardware can gauge the depth of invalidation queue which is shared among VFs. This is needed when device invalidation throttle (DIT) capability is supported.
This patch adds bit definitions for checking and tracking PFSID.
--- include/linux/intel-iommu.h | 4 ++++ 1 file changed, 4 insertions(+)
include/linux/intel-iommu.h +4 -0
@@ -114,6 +114,7 @@* Extended Capability Register */ +#define ecap_dit (e) ((e >> 41) & 0x1)#define ecap_pasid (e) ((e >> 40) & 0x1)#define ecap_pss (e) ((e >> 35) & 0x1f)#define ecap_eafs (e) ((e >> 34) & 0x1)
@@ -284,6 +285,7 @@ enum {#define QI_DEV_IOTLB_SID (sid) ((u64)((sid) & 0xffff) << 32)#define QI_DEV_IOTLB_QDEP (qdep) (((qdep) & 0x1f) << 16)#define QI_DEV_IOTLB_ADDR (addr) ((u64)(addr) & VTD_PAGE_MASK) +#define QI_DEV_IOTLB_PFSID (pfsid) (((u64)(pfsid & 0xf) << 12) | ((u64)(pfsid & 0xff0) << 48))#define QI_DEV_IOTLB_SIZE 1#define QI_DEV_IOTLB_MAX_INVS 32
@@ -308,6 +310,7 @@ enum {#define QI_DEV_EIOTLB_PASID (p) (((u64)p) << 32)#define QI_DEV_EIOTLB_SID (sid) ((u64)((sid) & 0xffff) << 16)#define QI_DEV_EIOTLB_QDEP (qd) ((u64)((qd) & 0x1f) << 4) +#define QI_DEV_EIOTLB_PFSID (pfsid) (((u64)(pfsid & 0xf) << 12) | ((u64)(pfsid & 0xff0) << 48))#define QI_DEV_EIOTLB_MAX_INVS 32#define QI_PGRP_IDX (idx) (((u64)(idx)) << 55)
@@ -440,6 +443,7 @@ struct device_domain_info {struct list_head global;/* link to global list */ u8 bus;/* PCI bus number */ u8 devfn;/* PCI devfn number */ +u16 pfsid;/* SRIOV physical function source ID */ u8 pasid_supported:3; u8 pasid_enabled:1; u8 pri_supported:1;
Signature · 1 line
2.7.4
PATCH v4 7/22 iommu/vt-d: fix dev iotlb pfsid use
In reply to Jacob PanPFSID should be used in the invalidation descriptor for flushing device IOTLBs on SRIOV VFs.
--- drivers/iommu/dmar.c | 6 +++--- drivers/iommu/intel-iommu.c | 16 +++++++++++++++- include/linux/intel-iommu.h | 5 ++--- 3 files changed, 20 insertions(+), 7 deletions(-)
drivers/iommu/dmar.c +3 -3
@@ -1339,8 +1339,8 @@ void qi_flush_iotlb(struct intel_iommu *iommu, u16 did, u64 addr,qi_submit_sync (&desc, iommu); } -void qi_flush_dev_iotlb (struct intel_iommu *iommu,u16 sid,u16 qdep, -u64 addr,unsigned mask) +void qi_flush_dev_iotlb (struct intel_iommu *iommu,u16 sid,u16 pfsid, +u16 qdep,u64 addr,unsigned mask) {struct qi_desc desc;
@@ -1355,7 +1355,7 @@ void qi_flush_dev_iotlb(struct intel_iommu *iommu, u16 sid, u16 qdep, qdep =0 ; desc.low =QI_DEV_IOTLB_SID (sid) |QI_DEV_IOTLB_QDEP (qdep) | -QI_DIOTLB_TYPE ; +QI_DIOTLB_TYPE |QI_DEV_IOTLB_PFSID (pfsid);qi_submit_sync (&desc, iommu); }
drivers/iommu/intel-iommu.c +15 -1
@@ -1482,6 +1482,19 @@ static void iommu_enable_dev_iotlb(struct device_domain_info *info)return ; pdev =to_pci_dev (info->dev); +/* For IOMMU that supports device IOTLB throttling (DIT), we assign +* PFSID to the invalidation desc of a VF such that IOMMU HW can gauge +* queue depth at PF level. If DIT is not set, PFSID will be treated as +* reserved, which should be set to 0. +*/ +if (!ecap_dit (info->iommu->ecap)) + info->pfsid =0 ; +else if (pdev && pdev->is_virtfn) { +if (ecap_dit (info->iommu->ecap)) +dev_warn (&pdev->dev,"SRIOV VF device IOTLB enabled without flow control\n" ); + info->pfsid =PCI_DEVID (pdev->physfn->bus->number, pdev->physfn->devfn); + }else + info->pfsid =PCI_DEVID (info->bus, info->devfn);#ifdef CONFIG_INTEL_IOMMU_SVM /* The PCIe spec, in its wisdom, declares that the behaviour of
@@ -1547,7 +1560,8 @@ static void iommu_flush_dev_iotlb(struct dmar_domain *domain, sid = info->bus <<8 | info->devfn; qdep = info->ats_qdep; -qi_flush_dev_iotlb (info->iommu, sid, qdep, addr, mask); +qi_flush_dev_iotlb (info->iommu, sid, info->pfsid, + qdep, addr, mask); }spin_unlock_irqrestore (&device_domain_lock, flags); }
include/linux/intel-iommu.h +2 -3
@@ -477,9 +477,8 @@ extern void qi_flush_context(struct intel_iommu *iommu, u16 did, u16 sid, u8 fm, u64 type);extern void qi_flush_iotlb (struct intel_iommu *iommu,u16 did,u64 addr,unsigned int size_order,u64 type); -extern void qi_flush_dev_iotlb (struct intel_iommu *iommu,u16 sid,u16 qdep, -u64 addr,unsigned mask); - +extern void qi_flush_dev_iotlb (struct intel_iommu *iommu,u16 sid,u16 pfsid, +u16 qdep,u64 addr,unsigned mask);extern int qi_submit_sync (struct qi_desc *desc,struct intel_iommu *iommu);extern int dmar_ir_support (void );
Signature · 1 line
2.7.4
PATCH v4 8/22 iommu/vt-d: support flushing more translation cache types
In reply to Jacob PanWhen Shared Virtual Memory is exposed to a guest via vIOMMU, extended IOTLB invalidation may be passed down from outside IOMMU subsystems. This patch adds invalidation functions that can be used for additional translation cache types.
--- drivers/iommu/dmar.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ include/linux/intel-iommu.h | 21 +++++++++++++++++++-- 2 files changed, 63 insertions(+), 2 deletions(-)
drivers/iommu/dmar.c +44 -0
@@ -1339,6 +1339,18 @@ void qi_flush_iotlb(struct intel_iommu *iommu, u16 did, u64 addr,qi_submit_sync (&desc, iommu); } +void qi_flush_eiotlb (struct intel_iommu *iommu,u16 did,u64 addr,u32 pasid, +unsigned int size_order,u64 granu,bool global) +{ +struct qi_desc desc; + + desc.low =QI_EIOTLB_PASID (pasid) |QI_EIOTLB_DID (did) | +QI_EIOTLB_GRAN (granu) |QI_EIOTLB_TYPE ; + desc.high =QI_EIOTLB_ADDR (addr) |QI_EIOTLB_GL (global) | +QI_EIOTLB_IH (0 ) |QI_EIOTLB_AM (size_order); +qi_submit_sync (&desc, iommu); +} +void qi_flush_dev_iotlb (struct intel_iommu *iommu,u16 sid,u16 pfsid,u16 qdep,u64 addr,unsigned mask) {
@@ -1360,6 +1372,38 @@ void qi_flush_dev_iotlb(struct intel_iommu *iommu, u16 sid, u16 pfsid,qi_submit_sync (&desc, iommu); } +void qi_flush_dev_eiotlb (struct intel_iommu *iommu,u16 sid, +u32 pasid,u16 qdep,u64 addr,unsigned size,u64 granu) +{ +struct qi_desc desc; + + desc.low =QI_DEV_EIOTLB_PASID (pasid) |QI_DEV_EIOTLB_SID (sid) | +QI_DEV_EIOTLB_QDEP (qdep) |QI_DEIOTLB_TYPE ; + desc.high |=QI_DEV_EIOTLB_GLOB (granu); + +/* If S bit is 0, we only flush a single page. If S bit is set, +* The least significant zero bit indicates the size. VT-d spec +* 6.5.2.6 +*/ +if (!size) + desc.high =QI_DEV_EIOTLB_ADDR (addr) & ~QI_DEV_EIOTLB_SIZE ; +else { +unsigned long mask =1UL << (VTD_PAGE_SHIFT + size); + + desc.high =QI_DEV_EIOTLB_ADDR (addr & ~mask) |QI_DEV_EIOTLB_SIZE ; + } +qi_submit_sync (&desc, iommu); +} + +void qi_flush_pasid (struct intel_iommu *iommu,u16 did,u64 granu,int pasid) +{ +struct qi_desc desc; + + desc.high =0 ; + desc.low =QI_PC_TYPE |QI_PC_DID (did) |QI_PC_GRAN (granu) |QI_PC_PASID (pasid); + +qi_submit_sync (&desc, iommu); +}/* * Disable Queued Invalidation interface. */
include/linux/intel-iommu.h +19 -2
@@ -262,6 +262,10 @@ enum {#define QI_PGRP_RESP_TYPE 0x9#define QI_PSTRM_RESP_TYPE 0xa +#define QI_DID (did) (((u64)did & 0xffff) << 16) +#define QI_DID_MASK GENMASK(31, 16) +#define QI_TYPE_MASK GENMASK(3, 0) +#define QI_IEC_SELECTIVE (((u64)1) << 4)#define QI_IEC_IIDEX (idx) (((u64)(idx & 0xffff) << 32))#define QI_IEC_IM (m) (((u64)(m & 0x1f) << 27))
@@ -293,8 +297,9 @@ enum {#define QI_PC_DID (did) (((u64)did) << 16)#define QI_PC_GRAN (gran) (((u64)gran) << 4) -#define QI_PC_ALL_PASIDS (QI_PC_TYPE | QI_PC_GRAN(0)) -#define QI_PC_PASID_SEL (QI_PC_TYPE | QI_PC_GRAN(1)) +/* PASID cache invalidation granu */ +#define QI_PC_ALL_PASIDS 0 +#define QI_PC_PASID_SEL 1#define QI_EIOTLB_ADDR (addr) ((u64)(addr) & VTD_PAGE_MASK)#define QI_EIOTLB_GL (gl) (((u64)gl) << 7)
@@ -304,6 +309,10 @@ enum {#define QI_EIOTLB_DID (did) (((u64)did) << 16)#define QI_EIOTLB_GRAN (gran) (((u64)gran) << 4) +/* QI Dev-IOTLB inv granu */ +#define QI_DEV_IOTLB_GRAN_ALL 0 +#define QI_DEV_IOTLB_GRAN_PASID_SEL 1 +#define QI_DEV_EIOTLB_ADDR (a) ((u64)(a) & VTD_PAGE_MASK)#define QI_DEV_EIOTLB_SIZE (((u64)1) << 11)#define QI_DEV_EIOTLB_GLOB (g) ((u64)g)
@@ -332,6 +341,7 @@ enum {#define QI_RESP_INVALID 0x1#define QI_RESP_FAILURE 0xf +/* QI EIOTLB inv granu */ #define QI_GRAN_ALL_ALL 0#define QI_GRAN_NONG_ALL 1#define QI_GRAN_NONG_PASID 2
@@ -477,8 +487,15 @@ extern void qi_flush_context(struct intel_iommu *iommu, u16 did, u16 sid, u8 fm, u64 type);extern void qi_flush_iotlb (struct intel_iommu *iommu,u16 did,u64 addr,unsigned int size_order,u64 type); +extern void qi_flush_eiotlb (struct intel_iommu *iommu,u16 did,u64 addr, +u32 pasid,unsigned int size_order,u64 type,bool global);extern void qi_flush_dev_iotlb (struct intel_iommu *iommu,u16 sid,u16 pfsid,u16 qdep,u64 addr,unsigned mask); + +extern void qi_flush_dev_eiotlb (struct intel_iommu *iommu,u16 sid, +u32 pasid,u16 qdep,u64 addr,unsigned size,u64 granu); +extern void qi_flush_pasid (struct intel_iommu *iommu,u16 did,u64 granu,int pasid); +extern int qi_submit_sync (struct qi_desc *desc,struct intel_iommu *iommu);extern int dmar_ir_support (void );
Signature · 1 line
2.7.4
PATCH v4 9/22 iommu/vt-d: add svm/sva invalidate function
In reply to Jacob PanWhen Shared Virtual Address (SVA) is enabled for a guest OS via vIOMMU, we need to provide invalidation support at IOMMU API and driver level. This patch adds Intel VT-d specific function to implement iommu passdown invalidate API for shared virtual address.
The use case is for supporting caching structure invalidation of assigned SVM capable devices. Emulated IOMMU exposes queue invalidation capability and passes down all descriptors from the guest to the physical IOMMU.
The assumption is that guest to host device ID mapping should be resolved prior to calling IOMMU driver. Based on the device handle, host IOMMU driver can replace certain fields before submit to the invalidation queue.
--- drivers/iommu/intel-iommu.c | 170 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 170 insertions(+)
drivers/iommu/intel-iommu.c +170 -0
@@ -5026,6 +5026,175 @@ static void intel_iommu_detach_device(struct iommu_domain *domain,dmar_remove_one_dev_info (to_dmar_domain (domain), dev); } +/* +* 3D array for converting IOMMU generic type-granularity to VT-d granularity +* X indexed by enum iommu_inv_type +* Y indicates request without and with PASID +* Z indexed by enum iommu_inv_granularity +* +* For an example, if we want to find the VT-d granularity encoding for IOTLB +* type, DMA request with PASID, and page selective. The look up indices are: +* [1][1][8], where +* 1: IOMMU_INV_TYPE_TLB +* 1: with PASID +* 8: IOMMU_INV_GRANU_PAGE_PASID +* +* Granu_map array indicates validity of the table. 1: valid, 0: invalid +* +*/ +const static int inv_type_granu_map[IOMMU_INV_NR_TYPE ][2 ][IOMMU_INV_NR_GRANU ] = { +/* extended dev IOTLBs, for dev-IOTLB, only global is valid, +for dev-EXIOTLB, two valid granu */ + { + {1 }, + {0 ,0 ,0 ,0 ,1 ,1 ,0 ,0 ,0 } + }, +/* IOTLB and EIOTLB */ + { + {1 ,1 ,0 ,1 ,0 ,0 ,0 ,0 ,0 }, + {0 ,0 ,0 ,0 ,1 ,0 ,1 ,1 ,1 } + }, +/* PASID cache */ + { + {0 }, + {0 ,0 ,0 ,0 ,1 ,1 ,0 ,0 ,0 } + }, +/* context cache */ + { + {1 ,1 ,1 } + } +}; + +const static u64 inv_type_granu_table[IOMMU_INV_NR_TYPE ][2 ][IOMMU_INV_NR_GRANU ] = { +/* extended dev IOTLBs, only global is valid */ + { + {QI_DEV_IOTLB_GRAN_ALL }, + {0 ,0 ,0 ,0 ,QI_DEV_IOTLB_GRAN_ALL ,QI_DEV_IOTLB_GRAN_PASID_SEL ,0 ,0 ,0 } + }, +/* IOTLB and EIOTLB */ + { + {DMA_TLB_GLOBAL_FLUSH ,DMA_TLB_DSI_FLUSH ,0 ,DMA_TLB_PSI_FLUSH }, + {0 ,0 ,0 ,0 ,QI_GRAN_ALL_ALL ,0 ,QI_GRAN_NONG_ALL ,QI_GRAN_NONG_PASID ,QI_GRAN_PSI_PASID } + }, +/* PASID cache */ + { + {0 }, + {0 ,0 ,0 ,0 ,QI_PC_ALL_PASIDS ,QI_PC_PASID_SEL } + }, +/* context cache */ + { + {DMA_CCMD_GLOBAL_INVL ,DMA_CCMD_DOMAIN_INVL ,DMA_CCMD_DEVICE_INVL } + } +}; + +static inline int to_vtd_granularity (int type,int granu,int with_pasid,u64 *vtd_granu) +{ +if (type >=IOMMU_INV_NR_TYPE || granu >=IOMMU_INV_NR_GRANU || with_pasid >1 ) +return -EINVAL ; + +if (inv_type_granu_map[type][with_pasid][granu] ==0 ) +return -EINVAL ; + + *vtd_granu = inv_type_granu_table[type][with_pasid][granu]; + +return 0 ; +} + +static int intel_iommu_sva_invalidate (struct iommu_domain *domain, +struct device *dev,struct tlb_invalidate_info *inv_info) +{ +struct intel_iommu *iommu; +struct dmar_domain *dmar_domain =to_dmar_domain (domain); +struct device_domain_info *info; +u16 did, sid; +u8 bus, devfn; +int ret =0 ; +u64 granu; +unsigned long flags; + +if (!inv_info || !dmar_domain) +return -EINVAL ; + + iommu =device_to_iommu (dev, &bus, &devfn); +if (!iommu) +return -ENODEV ; + +if (!dev || !dev_is_pci (dev)) +return -ENODEV ; + + did = dmar_domain->iommu_did[iommu->seq_id]; + sid =PCI_DEVID (bus, devfn); + ret =to_vtd_granularity (inv_info->hdr.type, inv_info->granularity, + !!(inv_info->flags &IOMMU_INVALIDATE_PASID_TAGGED ), &granu); +if (ret) { +pr_err ("Invalid range type %d, granu %d\n" , inv_info->hdr.type, + inv_info->granularity); +return ret; + } + +spin_lock (&iommu->lock); +spin_lock_irqsave (&device_domain_lock, flags); + +switch (inv_info->hdr.type) { +case IOMMU_INV_TYPE_CONTEXT : + iommu->flush.flush_context (iommu, did, sid, +DMA_CCMD_MASK_NOBIT , granu); +break ; +case IOMMU_INV_TYPE_TLB : +/* We need to deal with two scenarios: +* - IOTLB for request w/o PASID +* - extended IOTLB for request with PASID. +*/ +if (inv_info->size && + (inv_info->addr & ((1 << (VTD_PAGE_SHIFT + inv_info->size)) -1 ))) { +pr_err ("Addr out of range, addr 0x%llx, size order %d\n" , + inv_info->addr, inv_info->size); + ret = -ERANGE ; + goto out_unlock; + } + +if (inv_info->flags &IOMMU_INVALIDATE_PASID_TAGGED ) +qi_flush_eiotlb (iommu, did,mm_to_dma_pfn (inv_info->addr), + inv_info->pasid, + inv_info->size, granu, + inv_info->flags &IOMMU_INVALIDATE_GLOBAL_PAGE ); +else +qi_flush_iotlb (iommu, did,mm_to_dma_pfn (inv_info->addr), + inv_info->size, granu); +/** +* Always flush device IOTLB if ATS is enabled since guest +* vIOMMU exposes CM = 1, no device IOTLB flush will be passed +* down. +*/ + info =iommu_support_dev_iotlb (dmar_domain, iommu, bus, devfn); +if (info && info->ats_enabled) { +if (inv_info->flags &IOMMU_INVALIDATE_PASID_TAGGED ) +qi_flush_dev_eiotlb (iommu, sid, + inv_info->pasid, info->ats_qdep, + inv_info->addr, inv_info->size, + granu); +else +qi_flush_dev_iotlb (iommu, sid, info->pfsid, + info->ats_qdep, inv_info->addr, + inv_info->size); + } +break ; +case IOMMU_INV_TYPE_PASID : +qi_flush_pasid (iommu, did, granu, inv_info->pasid); + +break ; +default : +dev_err (dev,"Unknown IOMMU invalidation type %d\n" , + inv_info->hdr.type); + ret = -EINVAL ; + } +out_unlock: +spin_unlock (&iommu->lock); +spin_unlock_irqrestore (&device_domain_lock, flags); + +return ret; +} +static int intel_iommu_map (struct iommu_domain *domain,unsigned long iova,phys_addr_t hpa,size_t size,int iommu_prot)
@@ -5455,6 +5624,7 @@ const struct iommu_ops intel_iommu_ops = {#ifdef CONFIG_INTEL_IOMMU_SVM .bind_pasid_table = intel_iommu_bind_pasid_table, .unbind_pasid_table = intel_iommu_unbind_pasid_table, + .sva_invalidate = intel_iommu_sva_invalidate, #endif .map = intel_iommu_map, .unmap = intel_iommu_unmap,
Signature · 1 line
2.7.4
PATCH v4 10/22 iommu: introduce device fault data
In reply to Jacob PanDevice faults detected by IOMMU can be reported outside IOMMU subsystem for further processing. This patch intends to provide a generic device fault data such that device drivers can be communicated with IOMMU faults without model specific knowledge.
The proposed format is the result of discussion at:
https://lkml.org/lkml/2017/11/10/291
Part of the code is based on Jean-Philippe Brucker's patchset
(https://patchwork.kernel.org/patch/9989315/).
The assumption is that model specific IOMMU driver can filter and handle most of the internal faults if the cause is within IOMMU driver control. Therefore, the fault reasons can be reported are grouped and generalized based common specifications such as PCI ATS.
--- include/linux/iommu.h | 102 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 100 insertions(+), 2 deletions(-)
include/linux/iommu.h +100 -2
@@ -49,13 +49,17 @@ struct bus_type;struct device ;struct iommu_domain ;struct notifier_block ; +struct iommu_fault_event ;/* iommu fault flags */ -#define IOMMU_FAULT_READ 0x0 -#define IOMMU_FAULT_WRITE 0x1 +#define IOMMU_FAULT_READ (1 << 0) +#define IOMMU_FAULT_WRITE (1 << 1) +#define IOMMU_FAULT_EXEC (1 << 2) +#define IOMMU_FAULT_PRIV (1 << 3)typedef int (*iommu_fault_handler_t )(struct iommu_domain *,struct device *,unsigned long ,int ,void *); +typedef int (*iommu_dev_fault_handler_t )(struct iommu_fault_event *,void *);struct iommu_domain_geometry {dma_addr_t aperture_start;/* First address that can be mapped */
@@ -264,6 +268,99 @@ struct iommu_device {struct device *dev; }; +/* Generic fault types, can be expanded IRQ remapping fault */ +enum iommu_fault_type { +IOMMU_FAULT_DMA_UNRECOV =1 ,/* unrecoverable fault */ +IOMMU_FAULT_PAGE_REQ ,/* page request fault */ +}; + +enum iommu_fault_reason { +IOMMU_FAULT_REASON_UNKNOWN =0 , + +/* IOMMU internal error, no specific reason to report out */ +IOMMU_FAULT_REASON_INTERNAL , + +/* Could not access the PASID table */ +IOMMU_FAULT_REASON_PASID_FETCH , + +/* +* PASID is out of range (e.g. exceeds the maximum PASID +* supported by the IOMMU) or disabled. +*/ +IOMMU_FAULT_REASON_PASID_INVALID , + +/* Could not access the page directory (Invalid PASID entry) */ +IOMMU_FAULT_REASON_PGD_FETCH , + +/* Could not access the page table entry (Bad address) */ +IOMMU_FAULT_REASON_PTE_FETCH , + +/* Protection flag check failed */ +IOMMU_FAULT_REASON_PERMISSION , +}; + +/** +* struct iommu_fault_event - Generic per device fault data +* +* - PCI and non-PCI devices +* - Recoverable faults (e.g. page request), information based on PCI ATS +* and PASID spec. +* - Un-recoverable faults of device interest +* - DMA remapping and IRQ remapping faults + +* @type contains fault type. +* @reason fault reasons if relevant outside IOMMU driver, IOMMU driver internal +* faults are not reported +* @addr: tells the offending page address +* @pasid: contains process address space ID, used in shared virtual memory(SVM) +* @rid: requestor ID +* @page_req_group_id: page request group index +* @last_req: last request in a page request group +* @pasid_valid: indicates if the PRQ has a valid PASID +* @prot: page access protection flag, e.g. IOMMU_FAULT_READ, IOMMU_FAULT_WRITE +* @device_private: if present, uniquely identify device-specific +* private data for an individual page request. +* @iommu_private: used by the IOMMU driver for storing fault-specific +* data. Users should not modify this field before +* sending the fault response. +*/ +struct iommu_fault_event { +enum iommu_fault_type type; +enum iommu_fault_reason reason; +u64 addr; +u32 pasid; +u32 page_req_group_id; +u32 last_req :1 ; +u32 pasid_valid :1 ; +u32 prot; +u64 device_private; +u64 iommu_private; +}; + +/** +* struct iommu_fault_param - per-device IOMMU fault data +* @dev_fault_handler: Callback function to handle IOMMU faults at device level +* @data: handler private data +* +*/ +struct iommu_fault_param { +iommu_dev_fault_handler_t handler; +void *data; +}; + +/** +* struct iommu_param - collection of per-device IOMMU data +* +* @fault_param: IOMMU detected device fault reporting data +* +* TODO: migrate other per device data pointers under iommu_dev_data, e.g. +* struct iommu_group *iommu_group; +* struct iommu_fwspec *iommu_fwspec; +*/ +struct iommu_param { +struct iommu_fault_param *fault_param; +}; +int iommu_device_register (struct iommu_device *iommu);void iommu_device_unregister (struct iommu_device *iommu); int iommu_device_sysfs_add(struct iommu_device *iommu,
@@ -437,6 +534,7 @@ struct iommu_ops {};struct iommu_group {};struct iommu_fwspec {};struct iommu_device {}; +struct iommu_fault_param {};static inline bool iommu_present (struct bus_type *bus) {
Signature · 1 line
2.7.4
PATCH v4 11/22 driver core: add per device iommu param
In reply to Jacob PanDMA faults can be detected by IOMMU at device level. Adding a pointer to struct device allows IOMMU subsystem to report relevant faults back to the device driver for further handling.
For direct assigned device (or user space drivers), guest OS holds responsibility to handle and respond per device IOMMU fault. Therefore we need fault reporting mechanism to propagate faults beyond IOMMU subsystem.
There are two other IOMMU data pointers under struct device today, here we introduce iommu_param as a parent pointer such that all device IOMMU data can be consolidated here. The idea was suggested here by Greg KH and Joerg. The name iommu_param is chosen here since iommu_data has been used.
--- include/linux/device.h | 3 +++ 1 file changed, 3 insertions(+)
include/linux/device.h +3 -0
@@ -41,6 +41,7 @@ struct iommu_ops;struct iommu_group ;struct iommu_fwspec ;struct dev_pin_info ; +struct iommu_param ;struct bus_attribute {struct attribute attr;
@@ -872,6 +873,7 @@ struct dev_links_info {* device (i.e. the bus driver that discovered the device). * @iommu_group: IOMMU group the device belongs to. * @iommu_fwspec: IOMMU-specific properties supplied by firmware. +* @iommu_param: Per device generic IOMMU runtime data * * @offline_disabled: If set, the device is permanently online. * @offline: Set after successful invocation of bus type's .offline().
@@ -961,6 +963,7 @@ struct device {void (*release)(struct device *dev);struct iommu_group *iommu_group;struct iommu_fwspec *iommu_fwspec; +struct iommu_param *iommu_param; bool offline_disabled:1; bool offline:1;
Signature · 1 line
2.7.4
Re: [PATCH v4 11/22] driver core: add per device iommu param
Quoted 13 lines · Jacob Pan
DMA faults can be detected by IOMMU at device level. Adding a pointer to struct device allows IOMMU subsystem to report relevant faults back to the device driver for further handling.
For direct assigned device (or user space drivers), guest OS holds responsibility to handle and respond per device IOMMU fault. Therefore we need fault reporting mechanism to propagate faults beyond IOMMU subsystem.There are two other IOMMU data pointers under struct device today, here we introduce
iommu_paramas a parent pointer such that all device IOMMU data can be consolidated here. The idea was suggested here by Greg KH and Joerg. The nameiommu_paramis chosen here sinceiommu_datahas been used.
Quoted 4 lines
Suggested-by Greg Kroah-Hartman <[email protected]>Signed-off-by Jean-Philippe Brucker <[email protected]>
PATCH v4 12/22 iommu: introduce device fault report API
In reply to Jacob PanTraditionally, device specific faults are detected and handled within their own device drivers. When IOMMU is enabled, faults such as DMA related transactions are detected by IOMMU. There is no generic reporting mechanism to report faults back to the in-kernel device driver or the guest OS in case of assigned devices.
Faults detected by IOMMU is based on the transaction's source ID which can be reported at per device basis, regardless of the device type is a PCI device or not.
The fault types include recoverable (e.g. page request) and unrecoverable faults(e.g. access error). In most cases, faults can be handled by IOMMU drivers internally. The primary use cases are as follows:
1. page request fault originated from an SVM capable device that is assigned to guest via vIOMMU. In this case, the first level page tables are owned by the guest. Page request must be propagated to the guest to let guest OS fault in the pages then send page response. In this mechanism, the direct receiver of IOMMU fault notification is VFIO, which can relay notification events to QEMU or other user space software.
2. faults need more subtle handling by device drivers. Other than simply invoke reset function, there are needs to let device driver handle the fault with a smaller impact.
This patchset is intended to create a generic fault report API such that it can scale as follows:
- all IOMMU types
- PCI and non-PCI devices
- recoverable and unrecoverable faults
- VFIO and other other in kernel users
- DMA & IRQ remapping (TBD)
The original idea was brought up by David Woodhouse and discussions summarized at https://lwn.net/Articles/608914/.
--- drivers/iommu/iommu.c | 147 +++++++++++++++++++++++++++++++++++++++++++++++++- include/linux/iommu.h | 35 +++++++++++- 2 files changed, 179 insertions(+), 3 deletions(-)
drivers/iommu/iommu.c +146 -1
@@ -581,6 +581,13 @@ int iommu_group_add_device(struct iommu_group *group, struct device *dev) goto err_free_name; } + dev->iommu_param =kzalloc (sizeof (*dev->iommu_param),GFP_KERNEL ); +if (!dev->iommu_param) { + ret = -ENOMEM ; + goto err_free_name; + } +mutex_init (&dev->iommu_param->lock); +kobject_get (group->devices_kobj); dev->iommu_group = group;
@@ -611,6 +618,7 @@ int iommu_group_add_device(struct iommu_group *group, struct device *dev)mutex_unlock (&group->mutex); dev->iommu_group =NULL ;kobject_put (group->devices_kobj); +kfree (dev->iommu_param); err_free_name:kfree (device->name); err_remove_link:
@@ -657,7 +665,7 @@ void iommu_group_remove_device(struct device *dev)sysfs_remove_link (&dev->kobj,"iommu_group" );trace_remove_device_from_group (group->id, dev); - +kfree (dev->iommu_param);kfree (device->name);kfree (device); dev->iommu_group =NULL ;
@@ -792,6 +800,143 @@ int iommu_group_unregister_notifier(struct iommu_group *group,EXPORT_SYMBOL_GPL (iommu_group_unregister_notifier);/** +* iommu_register_device_fault_handler() - Register a device fault handler +* @dev: the device +* @handler: the fault handler +* @data: private data passed as argument to the handler +* +* When an IOMMU fault event is received, call this handler with the fault event +* and data as argument. The handler should return 0. If the fault is +* recoverable (IOMMU_FAULT_PAGE_REQ), the handler must also complete +* the fault by calling iommu_page_response() with one of the following +* response code: +* - IOMMU_PAGE_RESP_SUCCESS: retry the translation +* - IOMMU_PAGE_RESP_INVALID: terminate the fault +* - IOMMU_PAGE_RESP_FAILURE: terminate the fault and stop reporting +* page faults if possible. +* +* Return 0 if the fault handler was installed successfully, or an error. +*/ +int iommu_register_device_fault_handler (struct device *dev, +iommu_dev_fault_handler_t handler, +void *data) +{ +struct iommu_param *param = dev->iommu_param; + +/* +* Device iommu_param should have been allocated when device is +* added to its iommu_group. +*/ +if (!param) +return -EINVAL ; + +/* Only allow one fault handler registered for each device */ +if (param->fault_param) +return -EBUSY ; + +mutex_lock (¶m->lock); +get_device (dev); + param->fault_param = +kzalloc (sizeof (struct iommu_fault_param ),GFP_ATOMIC ); +if (!param->fault_param) { +put_device (dev); +mutex_unlock (¶m->lock); +return -ENOMEM ; + } +mutex_init (¶m->fault_param->lock); + param->fault_param->handler = handler; + param->fault_param->data = data; +INIT_LIST_HEAD (¶m->fault_param->faults); + +mutex_unlock (¶m->lock); + +return 0 ; +} +EXPORT_SYMBOL_GPL (iommu_register_device_fault_handler); + +/** +* iommu_unregister_device_fault_handler() - Unregister the device fault handler +* @dev: the device +* +* Remove the device fault handler installed with +* iommu_register_device_fault_handler(). +* +* Return 0 on success, or an error. +*/ +int iommu_unregister_device_fault_handler (struct device *dev) +{ +struct iommu_param *param = dev->iommu_param; +int ret =0 ; + +if (!param) +return -EINVAL ; + +mutex_lock (¶m->lock); +/* we cannot unregister handler if there are pending faults */ +if (list_empty (¶m->fault_param->faults)) { + ret = -EBUSY ; + goto unlock; + } + +list_del (¶m->fault_param->faults); +kfree (param->fault_param); + param->fault_param =NULL ; +put_device (dev); + +unlock: +mutex_unlock (¶m->lock); + +return 0 ; +} +EXPORT_SYMBOL_GPL (iommu_unregister_device_fault_handler); + + +/** +* iommu_report_device_fault() - Report fault event to device +* @dev: the device +* @evt: fault event data +* +* Called by IOMMU model specific drivers when fault is detected, typically +* in a threaded IRQ handler. +* +* Return 0 on success, or an error. +*/ +int iommu_report_device_fault (struct device *dev,struct iommu_fault_event *evt) +{ +int ret =0 ; +struct iommu_fault_event *evt_pending; +struct iommu_fault_param *fparam; + +/* iommu_param is allocated when device is added to group */ +if (!dev->iommu_param | !evt) +return -EINVAL ; +/* we only report device fault if there is a handler registered */ +mutex_lock (&dev->iommu_param->lock); +if (!dev->iommu_param->fault_param || + !dev->iommu_param->fault_param->handler) { + ret = -EINVAL ; + goto done_unlock; + } + fparam = dev->iommu_param->fault_param; +if (evt->type ==IOMMU_FAULT_PAGE_REQ && evt->last_req) { + evt_pending =kzalloc (sizeof (*evt_pending),GFP_ATOMIC ); +if (!evt_pending) { + ret = -ENOMEM ; + goto done_unlock; + } +memcpy (evt_pending, evt,sizeof (struct iommu_fault_event )); +mutex_lock (&fparam->lock); +list_add_tail (&evt_pending->list, &fparam->faults); +mutex_unlock (&fparam->lock); + } + ret = fparam->handler (evt, fparam->data); +done_unlock: +mutex_unlock (&dev->iommu_param->lock); +return ret; +} +EXPORT_SYMBOL_GPL (iommu_report_device_fault); + +/** * iommu_group_id - Return ID for a group * @group: the group to ID *
include/linux/iommu.h +33 -2
@@ -307,7 +307,8 @@ enum iommu_fault_reason {* and PASID spec. * - Un-recoverable faults of device interest * - DMA remapping and IRQ remapping faults - +* +* @list pending fault event list, used for tracking responses * @type contains fault type. * @reason fault reasons if relevant outside IOMMU driver, IOMMU driver internal * faults are not reported
@@ -325,6 +326,7 @@ enum iommu_fault_reason {* sending the fault response. */ struct iommu_fault_event { +struct list_head list;enum iommu_fault_type type;enum iommu_fault_reason reason;u64 addr;
@@ -341,10 +343,13 @@ struct iommu_fault_event {* struct iommu_fault_param - per-device IOMMU fault data * @dev_fault_handler: Callback function to handle IOMMU faults at device level * @data: handler private data -* +* @faults: holds the pending faults which needs response, e.g. page response. +* @lock: protect pending PRQ event list */ struct iommu_fault_param {iommu_dev_fault_handler_t handler; +struct list_head faults; +struct mutex lock;void *data; };
@@ -358,6 +363,7 @@ struct iommu_fault_param {* struct iommu_fwspec *iommu_fwspec; */ struct iommu_param { +struct mutex lock;struct iommu_fault_param *fault_param; };
@@ -457,6 +463,14 @@ extern int iommu_group_register_notifier(struct iommu_group *group, struct notifier_block *nb);extern int iommu_group_unregister_notifier (struct iommu_group *group,struct notifier_block *nb); +extern int iommu_register_device_fault_handler (struct device *dev, +iommu_dev_fault_handler_t handler, +void *data); + +extern int iommu_unregister_device_fault_handler (struct device *dev); + +extern int iommu_report_device_fault (struct device *dev,struct iommu_fault_event *evt); +extern int iommu_group_id (struct iommu_group *group);extern struct iommu_group *iommu_group_get_for_dev (struct device *dev);extern struct iommu_domain *iommu_group_default_domain (struct iommu_group *);
@@ -728,6 +742,23 @@ static inline int iommu_group_unregister_notifier(struct iommu_group *group,return 0 ; } +static inline int iommu_register_device_fault_handler (struct device *dev, +iommu_dev_fault_handler_t handler, +void *data) +{ +return 0 ; +} + +static inline int iommu_unregister_device_fault_handler (struct device *dev) +{ +return 0 ; +} + +static inline int iommu_report_device_fault (struct device *dev,struct iommu_fault_event *evt) +{ +return 0 ; +} +static inline int iommu_group_id (struct iommu_group *group) {return -ENODEV ;
Signature · 1 line
2.7.4
PATCH v4 13/22 iommu: introduce page response function
In reply to Jacob PanIO page faults can be handled outside IOMMU subsystem. For an example, when nested translation is turned on and guest owns the first level page tables, device page request can be forwared to the guest for handling faults. As the page response returns by the guest, IOMMU driver on the host need to process the response which informs the device and completes the page request transaction.
This patch introduces generic API function for page response passing from the guest or other in-kernel users. The definitions of the generic data is based on PCI ATS specification not limited to any vendor.
--- drivers/iommu/iommu.c | 45 ++++++++++++++++++++++++++++++++++++++++++++ include/linux/iommu.h | 52 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+)
drivers/iommu/iommu.c +45 -0
@@ -1503,6 +1503,51 @@ int iommu_sva_invalidate(struct iommu_domain *domain, }EXPORT_SYMBOL_GPL (iommu_sva_invalidate); +int iommu_page_response (struct device *dev, +struct page_response_msg *msg) +{ +struct iommu_param *param = dev->iommu_param; +int ret = -EINVAL ; +struct iommu_fault_event *evt, *iter; +struct iommu_domain *domain =iommu_get_domain_for_dev (dev); + +if (!domain || !domain->ops->page_response) +return -ENODEV ; + +/* +* Device iommu_param should have been allocated when device is +* added to its iommu_group. +*/ +if (!param || !param->fault_param) +return -EINVAL ; + +/* Only send response if there is a fault report pending */ +mutex_lock (¶m->fault_param->lock); +if (list_empty (¶m->fault_param->faults)) { +pr_warn ("no pending PRQ, drop response\n" ); + goto done_unlock; + } +/* +* Check if we have a matching page request pending to respond, +* otherwise return -EINVAL +*/ +list_for_each_entry_safe (evt, iter, ¶m->fault_param->faults, list) { +if (evt->pasid == msg->pasid && + msg->page_req_group_id == evt->page_req_group_id) { + msg->private_data = evt->iommu_private; + ret = domain->ops->page_response (dev, msg); +list_del (&evt->list); +kfree (evt); +break ; + } + } + +done_unlock: +mutex_unlock (¶m->fault_param->lock); +return ret; +} +EXPORT_SYMBOL_GPL (iommu_page_response); +static void __iommu_detach_device (struct iommu_domain *domain,struct device *dev) {
include/linux/iommu.h +52 -0
76 lines not on this page · raw message
Signature · 1 line
2.7.4
PATCH v4 14/22 iommu: handle page response timeout
In reply to Jacob PanWhen IO page faults are reported outside IOMMU subsystem, the page request handler may fail for various reasons. E.g. a guest received page requests but did not have a chance to run for a long time. The irresponsive behavior could hold off limited resources on the pending device.
There can be hardware or credit based software solutions as suggested in the PCI ATS Ch-4. To provide a basic safty net this patch introduces a per device deferrable timer which monitors the longest pending page fault that requires a response. Proper action such as sending failure response code could be taken when timer expires but not included in this patch. We need to consider the life cycle of page groupd ID to prevent confusion with reused group ID by a device. For now, a warning message provides clue of such failure.
--- drivers/iommu/iommu.c | 60 +++++++++++++++++++++++++++++++++++++++++++++++++-- include/linux/iommu.h | 4 ++++ 2 files changed, 62 insertions(+), 2 deletions(-)
drivers/iommu/iommu.c +58 -2
@@ -799,6 +799,39 @@ int iommu_group_unregister_notifier(struct iommu_group *group, }EXPORT_SYMBOL_GPL (iommu_group_unregister_notifier); +/* Max time to wait for a pending page request */ +#define IOMMU_PAGE_RESPONSE_MAXTIME (HZ * 10) +static void iommu_dev_fault_timer_fn (struct timer_list *t) +{ +struct iommu_fault_param *fparam =from_timer (fparam, t, timer); +struct iommu_fault_event *evt, *iter; + +u64 now; + + now =get_jiffies_64 (); + +/* The goal is to ensure driver or guest page fault handler(via vfio) +* send page response on time. Otherwise, limited queue resources +* may be occupied by some irresponsive guests or drivers. +* When per device pending fault list is not empty, we periodically checks +* if any anticipated page response time has expired. +* +* TODO: +* We could do the following if response time expires: +* 1. send page response code FAILURE to all pending PRQ +* 2. inform device driver or vfio +* 3. drain in-flight page requests and responses for this device +* 4. clear pending fault list such that driver can unregister fault +* handler(otherwise blocked when pending faults are present). +*/ +list_for_each_entry_safe (evt, iter, &fparam->faults, list) { +if (time_after64 (evt->expire, now)) +pr_err ("Page response time expired!, pasid %d gid %d exp %llu now %llu\n" , + evt->pasid, evt->page_req_group_id, evt->expire, now); + } +mod_timer (t, now +IOMMU_PAGE_RESPONSE_MAXTIME ); +} +/** * iommu_register_device_fault_handler() - Register a device fault handler * @dev: the device
@@ -806,8 +839,8 @@ EXPORT_SYMBOL_GPL(iommu_group_unregister_notifier);* @data: private data passed as argument to the handler * * When an IOMMU fault event is received, call this handler with the fault event -* and data as argument. The handler should return 0. If the fault is -* recoverable (IOMMU_FAULT_PAGE_REQ), the handler must also complete +* and data as argument. The handler should return 0 on success. If the fault is +* recoverable (IOMMU_FAULT_PAGE_REQ), the handler can also complete * the fault by calling iommu_page_response() with one of the following * response code: * - IOMMU_PAGE_RESP_SUCCESS: retry the translation
@@ -848,6 +881,9 @@ int iommu_register_device_fault_handler(struct device *dev, param->fault_param->data = data;INIT_LIST_HEAD (¶m->fault_param->faults); +timer_setup (¶m->fault_param->timer, iommu_dev_fault_timer_fn, +TIMER_DEFERRABLE ); +mutex_unlock (¶m->lock);return 0 ;
@@ -905,6 +941,8 @@ int iommu_report_device_fault(struct device *dev, struct iommu_fault_event *evt) {int ret =0 ;struct iommu_fault_event *evt_pending; +struct timer_list *tmr; +u64 exp;struct iommu_fault_param *fparam;/* iommu_param is allocated when device is added to group */
@@ -925,6 +963,17 @@ int iommu_report_device_fault(struct device *dev, struct iommu_fault_event *evt) goto done_unlock; }memcpy (evt_pending, evt,sizeof (struct iommu_fault_event )); +/* Keep track of response expiration time */ + exp =get_jiffies_64 () +IOMMU_PAGE_RESPONSE_MAXTIME ; + evt_pending->expire = exp; + +if (list_empty (&fparam->faults)) { +/* First pending event, start timer */ + tmr = &dev->iommu_param->fault_param->timer; +WARN_ON (timer_pending (tmr)); +mod_timer (tmr, exp); + } +mutex_lock (&fparam->lock);list_add_tail (&evt_pending->list, &fparam->faults);mutex_unlock (&fparam->lock);
@@ -1542,6 +1591,13 @@ int iommu_page_response(struct device *dev, } } +/* stop response timer if no more pending request */ +if (list_empty (¶m->fault_param->faults) && +timer_pending (¶m->fault_param->timer)) { +pr_debug ("no pending PRQ, stop timer\n" ); +del_timer (¶m->fault_param->timer); + } + done_unlock:mutex_unlock (¶m->fault_param->lock);return ret;
include/linux/iommu.h +4 -0
@@ -375,6 +375,7 @@ enum iommu_fault_reason {* @iommu_private: used by the IOMMU driver for storing fault-specific * data. Users should not modify this field before * sending the fault response. +* @expire: time limit in jiffies will wait for page response */ struct iommu_fault_event {struct list_head list;
@@ -388,6 +389,7 @@ struct iommu_fault_event {u32 prot;u64 device_private;u64 iommu_private; +u64 expire; };/**
@@ -395,11 +397,13 @@ struct iommu_fault_event {* @dev_fault_handler: Callback function to handle IOMMU faults at device level * @data: handler private data * @faults: holds the pending faults which needs response, e.g. page response. +* @timer: track page request pending time limit * @lock: protect pending PRQ event list */ struct iommu_fault_param {iommu_dev_fault_handler_t handler;struct list_head faults; +struct timer_list timer;struct mutex lock;void *data; };
Signature · 1 line
2.7.4
PATCH v4 15/22 iommu/config: add build dependency for dmar
In reply to Jacob PanIntel VT-d interrupts come from both IRQ remapping and DMA remapping. In order to report non-recoverable faults back to device driver, we need to have access to IOMMU fault reporting APIs. This patch adds build depenency to DMAR code where fault IRQ handlers can selectively report faults.
--- drivers/iommu/Kconfig | 1 + 1 file changed, 1 insertion(+)
drivers/iommu/Kconfig +1 -0
@@ -138,6 +138,7 @@ config AMD_IOMMU_V2 # Intel IOMMU support config DMAR_TABLE bool + select IOMMU_API config INTEL_IOMMU bool "Support for Intel IOMMU using DMA Remapping Devices"
Signature · 1 line
2.7.4
PATCH v4 16/22 iommu/vt-d: report non-recoverable faults to device
In reply to Jacob PanCurrently, dmar fault IRQ handler does nothing more than rate limited printk, no critical hardware handling need to be done in IRQ context.
For some use case such as vIOMMU, it might be useful to report non-recoverable faults outside host IOMMU subsystem. DMAR fault can come from both DMA and interrupt remapping which has to be set up early before threaded IRQ is available.
This patch adds an option and a workqueue such that when faults are requested, DMAR fault IRQ handler can use the IOMMU fault reporting API to report.
--- drivers/iommu/dmar.c | 157 ++++++++++++++++++++++++++++++++++++++++++-- drivers/iommu/intel-iommu.c | 6 +- include/linux/dmar.h | 2 +- include/linux/intel-iommu.h | 1 + 4 files changed, 157 insertions(+), 9 deletions(-)
drivers/iommu/dmar.c +152 -5
@@ -1110,6 +1110,12 @@ static int alloc_iommu(struct dmar_drhd_unit *drhd)return err; } +static inline void dmar_free_fault_wq (struct intel_iommu *iommu) +{ +if (iommu->fault_wq) +destroy_workqueue (iommu->fault_wq); +} +static void free_iommu (struct intel_iommu *iommu) {if (intel_iommu_enabled) {
@@ -1126,6 +1132,7 @@ static void free_iommu(struct intel_iommu *iommu)free_irq (iommu->irq, iommu);dmar_free_hwirq (iommu->irq); iommu->irq =0 ; +dmar_free_fault_wq (iommu); }if (iommu->qi) {
@@ -1554,6 +1561,31 @@ static const char *irq_remap_fault_reasons[] = "Blocked an interrupt request due to source-id verification failure", }; +/* fault data and status */ +enum intel_iommu_fault_reason { +INTEL_IOMMU_FAULT_REASON_SW , +INTEL_IOMMU_FAULT_REASON_ROOT_NOT_PRESENT , +INTEL_IOMMU_FAULT_REASON_CONTEXT_NOT_PRESENT , +INTEL_IOMMU_FAULT_REASON_CONTEXT_INVALID , +INTEL_IOMMU_FAULT_REASON_BEYOND_ADDR_WIDTH , +INTEL_IOMMU_FAULT_REASON_PTE_WRITE_ACCESS , +INTEL_IOMMU_FAULT_REASON_PTE_READ_ACCESS , +INTEL_IOMMU_FAULT_REASON_NEXT_PT_INVALID , +INTEL_IOMMU_FAULT_REASON_ROOT_ADDR_INVALID , +INTEL_IOMMU_FAULT_REASON_CONTEXT_PTR_INVALID , +INTEL_IOMMU_FAULT_REASON_NONE_ZERO_RTP , +INTEL_IOMMU_FAULT_REASON_NONE_ZERO_CTP , +INTEL_IOMMU_FAULT_REASON_NONE_ZERO_PTE , +NR_INTEL_IOMMU_FAULT_REASON , +}; + +/* fault reasons that are allowed to be reported outside IOMMU subsystem */ +#define INTEL_IOMMU_FAULT_REASON_ALLOWED \ + ((1ULL << INTEL_IOMMU_FAULT_REASON_BEYOND_ADDR_WIDTH) | \ + (1ULL << INTEL_IOMMU_FAULT_REASON_PTE_WRITE_ACCESS) | \ + (1ULL << INTEL_IOMMU_FAULT_REASON_PTE_READ_ACCESS)) + +static const char *dmar_get_fault_reason (u8 fault_reason,int *fault_type) { if (fault_reason >= 0x20 && (fault_reason - 0x20 <
@@ -1634,11 +1666,90 @@ void dmar_msi_read(int irq, struct msi_msg *msg)raw_spin_unlock_irqrestore (&iommu->register_lock, flag); } +static enum iommu_fault_reason to_iommu_fault_reason (u8 reason) +{ +if (reason >=NR_INTEL_IOMMU_FAULT_REASON ) { +pr_warn ("unknown DMAR fault reason %d\n" , reason); +return IOMMU_FAULT_REASON_UNKNOWN ; + } +switch (reason) { +case INTEL_IOMMU_FAULT_REASON_SW : +case INTEL_IOMMU_FAULT_REASON_ROOT_NOT_PRESENT : +case INTEL_IOMMU_FAULT_REASON_CONTEXT_NOT_PRESENT : +case INTEL_IOMMU_FAULT_REASON_CONTEXT_INVALID : +case INTEL_IOMMU_FAULT_REASON_BEYOND_ADDR_WIDTH : +case INTEL_IOMMU_FAULT_REASON_ROOT_ADDR_INVALID : +case INTEL_IOMMU_FAULT_REASON_CONTEXT_PTR_INVALID : +return IOMMU_FAULT_REASON_INTERNAL ; +case INTEL_IOMMU_FAULT_REASON_NEXT_PT_INVALID : +case INTEL_IOMMU_FAULT_REASON_PTE_WRITE_ACCESS : +case INTEL_IOMMU_FAULT_REASON_PTE_READ_ACCESS : +return IOMMU_FAULT_REASON_PERMISSION ; +default : +return IOMMU_FAULT_REASON_UNKNOWN ; + } +} + +struct dmar_fault_work { +struct work_struct fault_work; +u64 addr; +int type; +int fault_type; +enum intel_iommu_fault_reason reason; +u16 sid; +}; + +static void report_fault_to_device (struct work_struct *work) +{ + struct dmar_fault_work *dfw = container_of(work, struct dmar_fault_work, + fault_work); +struct iommu_fault_event event; +struct pci_dev *pdev; +u8 bus, devfn; + +memset (&event,0 ,sizeof (struct iommu_fault_event )); + +/* check if fault reason is permitted to report outside IOMMU */ +if (!((1 << dfw->reason) &INTEL_IOMMU_FAULT_REASON_ALLOWED )) { +pr_debug ("Fault reason %d not allowed to report to device\n" , + dfw->reason); + goto free_work; + } + + bus =PCI_BUS_NUM (dfw->sid); + devfn =PCI_DEVFN (PCI_SLOT (dfw->sid),PCI_FUNC (dfw->sid)); +/* +* we need to check if the fault reporting is requested for the +* offending device. +*/ + pdev =pci_get_bus_and_slot (bus, devfn); +if (!pdev) { +pr_warn ("No PCI device found for source ID %x\n" , dfw->sid); + goto free_work; + } +/* +* unrecoverable fault is reported per IOMMU, notifier handler can +* resolve PCI device based on source ID. +*/ + event.reason =to_iommu_fault_reason (dfw->reason); + event.addr = dfw->addr; + event.type =IOMMU_FAULT_DMA_UNRECOV ; + event.prot = dfw->type ?IOMMU_READ :IOMMU_WRITE ; +dev_warn (&pdev->dev,"report device unrecoverable fault: %d, %x, %d\n" , + event.reason, dfw->sid, event.type); +iommu_report_device_fault (&pdev->dev, &event); +pci_dev_put (pdev); + +free_work: +kfree (dfw); +} +static int dmar_fault_do_one (struct intel_iommu *iommu,int type,u8 fault_reason,u16 source_id,unsigned long long addr) {const char *reason;int fault_type; +struct dmar_fault_work *dfw; reason =dmar_get_fault_reason (fault_reason, &fault_type);
@@ -1647,11 +1758,28 @@ static int dmar_fault_do_one(struct intel_iommu *iommu, int type, source_id >>8 ,PCI_SLOT (source_id &0xFF ),PCI_FUNC (source_id &0xFF ), addr >>48 , fault_reason, reason); - else + else {pr_err ("[%s] Request device [%02x:%02x.%d] fault addr %llx [fault reason %02d] %s\n" , type ?"DMA Read" :"DMA Write" , source_id >>8 ,PCI_SLOT (source_id &0xFF ),PCI_FUNC (source_id &0xFF ), addr, fault_reason, reason); + } + + dfw =kmalloc (sizeof (*dfw),GFP_ATOMIC ); +if (!dfw) +return -ENOMEM ; + +INIT_WORK (&dfw->fault_work, report_fault_to_device); + dfw->addr = addr; + dfw->type = type; + dfw->fault_type = fault_type; + dfw->reason = fault_reason; + dfw->sid = source_id; +if (!queue_work (iommu->fault_wq, &dfw->fault_work)) { +kfree (dfw); +return -EBUSY ; + } +return 0 ; }
@@ -1731,10 +1859,28 @@ irqreturn_t dmar_fault(int irq, void *dev_id)return IRQ_HANDLED ; } -int dmar_set_interrupt (struct intel_iommu *iommu) +static int dmar_set_fault_wq (struct intel_iommu *iommu) +{ +if (iommu->fault_wq) +return 0 ; + + iommu->fault_wq =alloc_ordered_workqueue (iommu->name,0 ); +if (!iommu->fault_wq) +return -ENOMEM ; + +return 0 ; +} + +int dmar_set_interrupt (struct intel_iommu *iommu,bool queue_fault) {int irq, ret; +/* fault can be reported back to device drivers via a wq */ +if (queue_fault) { + ret =dmar_set_fault_wq (iommu); +if (ret) +pr_err ("Failed to create fault handling workqueue\n" ); + }/* * Check if the fault interrupt is already initialized. */
@@ -1748,10 +1894,11 @@ int dmar_set_interrupt(struct intel_iommu *iommu)pr_err ("No free IRQ vectors\n" );return -EINVAL ; } - ret =request_irq (irq, dmar_fault,IRQF_NO_THREAD , iommu->name, iommu); -if (ret) +if (ret) {pr_err ("Can't request irq\n" ); +dmar_free_fault_wq (iommu); + }return ret; }
@@ -1765,7 +1912,7 @@ int __init enable_drhd_fault_handling(void)*/ for_each_iommu (iommu, drhd) {u32 fault_status; -int ret =dmar_set_interrupt (iommu); +int ret =dmar_set_interrupt (iommu, false);if (ret) { pr_err("DRHD %Lx: failed to enable fault, interrupt, ret %d\n",
drivers/iommu/intel-iommu.c +3 -3
@@ -3424,10 +3424,10 @@ static int __init init_dmars(void) goto free_iommu; }#endif - ret =dmar_set_interrupt (iommu); + ret =dmar_set_interrupt (iommu, true); +if (ret) goto free_iommu; -if (!translation_pre_enabled (iommu))iommu_enable_translation (iommu);
@@ -4345,7 +4345,7 @@ static int intel_iommu_add(struct dmar_drhd_unit *dmaru) goto disable_iommu; }#endif - ret =dmar_set_interrupt (iommu); + ret =dmar_set_interrupt (iommu, true);if (ret) goto disable_iommu;
include/linux/dmar.h +1 -1
@@ -278,7 +278,7 @@ extern void dmar_msi_unmask(struct irq_data *data);extern void dmar_msi_mask (struct irq_data *data);extern void dmar_msi_read (int irq,struct msi_msg *msg);extern void dmar_msi_write (int irq,struct msi_msg *msg); -extern int dmar_set_interrupt (struct intel_iommu *iommu); +extern int dmar_set_interrupt (struct intel_iommu *iommu,bool queue_fault);extern irqreturn_t dmar_fault (int irq,void *dev_id);extern int dmar_alloc_hwirq (int id,int node,void *arg);extern void dmar_free_hwirq (int irq);
include/linux/intel-iommu.h +1 -0
@@ -445,6 +445,7 @@ struct intel_iommu {struct iommu_device iommu;/* IOMMU core code handle */ int node;u32 flags;/* Software defined flags */ +struct workqueue_struct *fault_wq;/* Reporting IOMMU fault to device */ };/* PCI domain-device relationship */
Signature · 1 line
2.7.4
RE: [PATCH v4 16/22] iommu/vt-d: report non-recoverable faults to device
Hi Jacob,
pci_get_bus_and_slot() is deprecated, may update accordingly.
Thanks,
Yi Liu
Quoted 302 lines
From: Jacob Pan [mailto:jacob.jun.pan-VuQAYsv1563Yd54FQh9/[email protected]]
Sent: Friday, March 23, 2018 11:12 AM
Subject: [PATCH v4 16/22] iommu/vt-d: report non-recoverable faults to deviceCurrently, dmar fault IRQ handler does nothing more than rate limited printk, no critical hardware handling need to be done in IRQ context.
For some use case such as vIOMMU, it might be useful to report non-recoverable faults outside host IOMMU subsystem. DMAR fault can come from both DMA and interrupt remapping which has to be set up early before threaded IRQ is available. This patch adds an option and a workqueue such that when faults are requested, DMAR fault IRQ handler can use the IOMMU fault reporting API to report.Signed-off-by Ashok Raj <[email protected]>--- drivers/iommu/dmar.c | 157 ++++++++++++++++++++++++++++++++++++++++++-- drivers/iommu/intel-iommu.c | 6 +- include/linux/dmar.h | 2 +- include/linux/intel-iommu.h | 1 + 4 files changed, 157 insertions(+), 9 deletions(-)drivers/iommu/dmar.c index 2ed4979..0f1abfc renamed from drivers/iommu/dmar.c +0
-0100644
drivers/iommu/dmar.c +31
-0@@ -1110,6 +1110,12 @@ static int alloc_iommu(struct dmar_drhd_unit *drhd)return err; } +static inline void dmar_free_fault_wq (struct intel_iommu *iommu) { +if (iommu->fault_wq) +destroy_workqueue (iommu->fault_wq); +} +static void free_iommu (struct intel_iommu *iommu) {if (intel_iommu_enabled) {@@ -1126,6 +1132,7 @@ static void free_iommu(struct intel_iommu *iommu)free_irq (iommu->irq, iommu);dmar_free_hwirq (iommu->irq); iommu->irq =0 ; +dmar_free_fault_wq (iommu); }if (iommu->qi) {@@ -1554,6 +1561,31 @@ static const char *irq_remap_fault_reasons[] = "Blocked an interrupt request due to source-id verification failure", }; +/* fault data and status */ +enum intel_iommu_fault_reason { +INTEL_IOMMU_FAULT_REASON_SW , +INTEL_IOMMU_FAULT_REASON_ROOT_NOT_PRESENT , +INTEL_IOMMU_FAULT_REASON_CONTEXT_NOT_PRESENT , +INTEL_IOMMU_FAULT_REASON_CONTEXT_INVALID , +INTEL_IOMMU_FAULT_REASON_BEYOND_ADDR_WIDTH , +INTEL_IOMMU_FAULT_REASON_PTE_WRITE_ACCESS , +INTEL_IOMMU_FAULT_REASON_PTE_READ_ACCESS , +INTEL_IOMMU_FAULT_REASON_NEXT_PT_INVALID , +INTEL_IOMMU_FAULT_REASON_ROOT_ADDR_INVALID , +INTEL_IOMMU_FAULT_REASON_CONTEXT_PTR_INVALID , +INTEL_IOMMU_FAULT_REASON_NONE_ZERO_RTP , +INTEL_IOMMU_FAULT_REASON_NONE_ZERO_CTP , +INTEL_IOMMU_FAULT_REASON_NONE_ZERO_PTE , +NR_INTEL_IOMMU_FAULT_REASON , +}; + +/* fault reasons that are allowed to be reported outside IOMMU subsystem */ +#define INTEL_IOMMU_FAULT_REASON_ALLOWED \ + ((1ULL << INTEL_IOMMU_FAULT_REASON_BEYOND_ADDR_WIDTH) | \ + (1ULL << INTEL_IOMMU_FAULT_REASON_PTE_WRITE_ACCESS) | \ + (1ULL << INTEL_IOMMU_FAULT_REASON_PTE_READ_ACCESS)) + +static const char *dmar_get_fault_reason (u8 fault_reason,int *fault_type) { if (fault_reason >= 0x20 && (fault_reason - 0x20 < @@ -1634,11 +1666,90 @@ void dmar_msi_read(int irq, struct msi_msg *msg)drivers/iommu/dmar.c +95
-1raw_spin_unlock_irqrestore (&iommu->register_lock, flag); } +static enum iommu_fault_reason to_iommu_fault_reason (u8 reason) { +if (reason >=NR_INTEL_IOMMU_FAULT_REASON ) { +pr_warn ("unknown DMAR fault reason %d\n" , reason); +return IOMMU_FAULT_REASON_UNKNOWN ; + } +switch (reason) { +case INTEL_IOMMU_FAULT_REASON_SW : +case INTEL_IOMMU_FAULT_REASON_ROOT_NOT_PRESENT : +case INTEL_IOMMU_FAULT_REASON_CONTEXT_NOT_PRESENT : +case INTEL_IOMMU_FAULT_REASON_CONTEXT_INVALID : +case INTEL_IOMMU_FAULT_REASON_BEYOND_ADDR_WIDTH : +case INTEL_IOMMU_FAULT_REASON_ROOT_ADDR_INVALID : +case INTEL_IOMMU_FAULT_REASON_CONTEXT_PTR_INVALID : +return IOMMU_FAULT_REASON_INTERNAL ; +case INTEL_IOMMU_FAULT_REASON_NEXT_PT_INVALID : +case INTEL_IOMMU_FAULT_REASON_PTE_WRITE_ACCESS : +case INTEL_IOMMU_FAULT_REASON_PTE_READ_ACCESS : +return IOMMU_FAULT_REASON_PERMISSION ; +default : +return IOMMU_FAULT_REASON_UNKNOWN ; + } +} + +struct dmar_fault_work { +struct work_struct fault_work; +u64 addr; +int type; +int fault_type; +enum intel_iommu_fault_reason reason; +u16 sid; +}; + +static void report_fault_to_device (struct work_struct *work) { + struct dmar_fault_work *dfw = container_of(work, struct dmar_fault_work, + fault_work); +struct iommu_fault_event event; +struct pci_dev *pdev; +u8 bus, devfn; + +memset (&event,0 ,sizeof (struct iommu_fault_event )); + +/* check if fault reason is permitted to report outside IOMMU */ +if (!((1 << dfw->reason) &INTEL_IOMMU_FAULT_REASON_ALLOWED )) { +pr_debug ("Fault reason %d not allowed to report to device\n" , + dfw->reason); + goto free_work; + } + + bus =PCI_BUS_NUM (dfw->sid); + devfn =PCI_DEVFN (PCI_SLOT (dfw->sid),PCI_FUNC (dfw->sid)); +/* +* we need to check if the fault reporting is requested for the +* offending device. +*/ + pdev =pci_get_bus_and_slot (bus, devfn); +if (!pdev) { +pr_warn ("No PCI device found for source ID %x\n" , dfw->sid); + goto free_work; + } +/* +* unrecoverable fault is reported per IOMMU, notifier handler can +* resolve PCI device based on source ID. +*/ + event.reason =to_iommu_fault_reason (dfw->reason); + event.addr = dfw->addr; + event.type =IOMMU_FAULT_DMA_UNRECOV ; + event.prot = dfw->type ?IOMMU_READ :IOMMU_WRITE ; +dev_warn (&pdev->dev,"report device unrecoverable fault: %d, %x, %d\n" , + event.reason, dfw->sid, event.type); +iommu_report_device_fault (&pdev->dev, &event); +pci_dev_put (pdev); + +free_work: +kfree (dfw); +} +static int dmar_fault_do_one (struct intel_iommu *iommu,int type,u8 fault_reason,u16 source_id,unsigned long long addr) {const char *reason;int fault_type; +struct dmar_fault_work *dfw; reason =dmar_get_fault_reason (fault_reason, &fault_type);@@ -1647,11 +1758,28 @@ static int dmar_fault_do_one(struct intel_iommu *iommu, int type, source_id >>8 ,PCI_SLOT (source_id &0xFF ),PCI_FUNC (source_id &0xFF ), addr >>48 , fault_reason, reason); - else + else { pr_err("[%s] Request device [%02x:%02x.%d] fault addr %llx [fault reason %02d] %s\n", type ? "DMA Read" : "DMA Write", source_id >>8 ,PCI_SLOT (source_id &0xFF ),PCI_FUNC (source_id &0xFF ), addr, fault_reason, reason); + } + + dfw =kmalloc (sizeof (*dfw),GFP_ATOMIC ); +if (!dfw) +return -ENOMEM ; + +INIT_WORK (&dfw->fault_work, report_fault_to_device); + dfw->addr = addr; + dfw->type = type; + dfw->fault_type = fault_type; + dfw->reason = fault_reason; + dfw->sid = source_id; +if (!queue_work (iommu->fault_wq, &dfw->fault_work)) { +kfree (dfw); +return -EBUSY ; + } +return 0 ;}
drivers/iommu/dmar.c +22
-4@@ -1731,10 +1859,28 @@ irqreturn_t dmar_fault(int irq, void *dev_id)return IRQ_HANDLED ; } -int dmar_set_interrupt (struct intel_iommu *iommu) +static int dmar_set_fault_wq (struct intel_iommu *iommu) { +if (iommu->fault_wq) +return 0 ; + + iommu->fault_wq =alloc_ordered_workqueue (iommu->name,0 ); +if (!iommu->fault_wq) +return -ENOMEM ; + +return 0 ; +} + +int dmar_set_interrupt (struct intel_iommu *iommu,bool queue_fault) {int irq, ret; +/* fault can be reported back to device drivers via a wq */ +if (queue_fault) { + ret =dmar_set_fault_wq (iommu); +if (ret) +pr_err ("Failed to create fault handling workqueue\n" ); + }/* * Check if the fault interrupt is already initialized. */ @@ -1748,10 +1894,11 @@ int dmar_set_interrupt(struct intel_iommu *iommu)pr_err ("No free IRQ vectors\n" );return -EINVAL ; } - ret =request_irq (irq, dmar_fault,IRQF_NO_THREAD , iommu->name, iommu); -if (ret) +if (ret) {pr_err ("Can't request irq\n" ); +dmar_free_fault_wq (iommu); + }return ret; }@@ -1765,7 +1912,7 @@ int __init enable_drhd_fault_handling(void)*/ for_each_iommu (iommu, drhd) {u32 fault_status; -int ret =dmar_set_interrupt (iommu); +int ret =dmar_set_interrupt (iommu, false);if (ret) { pr_err("DRHD %Lx: failed to enable fault, interrupt,ret %d\n", diff --git
a/drivers/iommu/intel-iommu.cb/drivers/iommu/intel-iommu.cindex 771ee1e..3229e20 100644drivers/iommu/intel-iommu.c +3
-3@@ -3424,10 +3424,10 @@ static int __init init_dmars(void) goto free_iommu; }#endif - ret =dmar_set_interrupt (iommu); + ret =dmar_set_interrupt (iommu, true); +if (ret) goto free_iommu; -if (!translation_pre_enabled (iommu))iommu_enable_translation (iommu);@@ -4345,7 +4345,7 @@ static int intel_iommu_add(struct dmar_drhd_unit *dmaru) goto disable_iommu; }#endif - ret =dmar_set_interrupt (iommu); + ret =dmar_set_interrupt (iommu, true);if (ret) goto disable_iommu;include/linux/dmar.h index e2433bc..21f2162 renamed from include/linux/dmar.h +0
-0100644
include/linux/dmar.h +0
-0@@ -278,7 +278,7 @@ extern void dmar_msi_unmask(struct irq_data *data);extern void
dmar_msi_mask(struct irq_data *data); extern voiddmar_msi_read(int irq, struct msi_msg *msg); extern voiddmar_msi_write(int irq, struct msi_msg *msg);include/linux/dmar.h +2
-1-extern int dmar_set_interrupt (struct intel_iommu *iommu); +extern int dmar_set_interrupt (struct intel_iommu *iommu,bool +queue_fault); extern irqreturn_t dmar_fault(int irq, void *dev_id); extern int dmar_alloc_hwirq(intid, int node, void *arg); extern void
dmar_free_hwirq(int irq); diff --gita/include/linux/intel-iommu.hb/include/linux/intel-iommu.hindex 245ac7e..dacb6cf 100644include/linux/intel-iommu.h +2
-1@@ -445,6 +445,7 @@ struct intel_iommu {struct iommu_device iommu;/* IOMMU core code handle */ int node;u32 flags;/* Software defined flags */ +struct workqueue_struct *fault_wq;/* Reporting IOMMU fault to device +*/ };/* PCI domain-device relationship */ --2.7.4
PATCH v4 17/22 iommu/intel-svm: report device page request
In reply to Jacob PanIf the source device of a page request has its PASID table pointer bound to a guest, the first level page tables are owned by the guest. In this case, we shall let guest OS to manage page fault.
This patch uses the IOMMU fault reporting API to send fault events, possibly via VFIO, to the guest OS. Once guest pages are fault in, guest will issue page response which will be passed down via the invalidation passdown APIs.
Recoverable faults, such as page request reporting is not limitted to guest use. In kernel driver can also request a chance to receive fault notifications.
--- drivers/iommu/intel-svm.c | 71 ++++++++++++++++++++++++++++++++++++++++------- include/linux/iommu.h | 1 + 2 files changed, 62 insertions(+), 10 deletions(-)
drivers/iommu/intel-svm.c +61 -10
@@ -580,6 +580,56 @@ static bool is_canonical_address(u64 addr)return (((saddr << shift) >> shift) == saddr); } +static int prq_to_iommu_prot (struct page_req_dsc *req) +{ +int prot =0 ; + +if (req->rd_req) + prot |=IOMMU_FAULT_READ ; +if (req->wr_req) + prot |=IOMMU_FAULT_WRITE ; +if (req->exe_req) + prot |=IOMMU_FAULT_EXEC ; +if (req->priv_req) + prot |=IOMMU_FAULT_PRIV ; + +return prot; +} + +static int intel_svm_prq_report (struct page_req_dsc *desc) +{ +int ret =0 ; +struct iommu_fault_event event; +struct pci_dev *pdev; + +memset (&event,0 ,sizeof (struct iommu_fault_event )); + pdev =pci_get_bus_and_slot (desc->bus, desc->devfn); +if (!pdev) { +pr_err ("No PCI device found for PRQ [%02x:%02x.%d]\n" , + desc->bus,PCI_SLOT (desc->devfn), +PCI_FUNC (desc->devfn)); +return -ENODEV ; + } + +/* Fill in event data for device specific processing */ + event.type =IOMMU_FAULT_PAGE_REQ ; + event.addr = (u64 )desc->addr <<VTD_PAGE_SHIFT ; + event.pasid = desc->pasid; + event.page_req_group_id = desc->prg_index; + event.prot =prq_to_iommu_prot (desc); + event.last_req = desc->lpig; + event.pasid_valid =1 ; +/* keep track of PRQ so that when the response comes back, we know +* whether we do group response or stream response. SRR[0] and +* private[54:32] bits in the descriptor are stored. +*/ + event.iommu_private = *(u64 *)desc; + ret =iommu_report_device_fault (&pdev->dev, &event); +pci_dev_put (pdev); + +return ret; +} +static irqreturn_t prq_event_thread (int irq,void *d) {struct intel_iommu *iommu = d;
@@ -628,6 +678,16 @@ static irqreturn_t prq_event_thread(int irq, void *d) goto no_pasid; } } +/* If address is not canonical, return invalid response */ +if (!is_canonical_address (address)) + goto bad_req; + +/* +* If prq is to be handled outside iommu driver via receiver of +* the fault notifiers, we skip the page response here. +*/ +if (!intel_svm_prq_report (req)) + goto prq_advance; result =QI_RESP_INVALID ;/* Since we're using init_mm.pgd directly, we should never take
@@ -638,9 +698,6 @@ static irqreturn_t prq_event_thread(int irq, void *d)if (!mmget_not_zero (svm->mm)) goto bad_req; -/* If address is not canonical, return invalid response */ -if (!is_canonical_address (address)) - goto bad_req;down_read (&svm->mm->mmap_sem); vma =find_extend_vma (svm->mm, address);
@@ -673,12 +730,6 @@ static irqreturn_t prq_event_thread(int irq, void *d)if (WARN_ON (&sdev->list == &svm->devs)) sdev =NULL ; - -if (sdev && sdev->ops && sdev->ops->fault_cb) { -int rwxp = (req->rd_req <<3 ) | (req->wr_req <<2 ) | - (req->exe_req <<1 ) | (req->priv_req); - sdev->ops->fault_cb (sdev->dev, req->pasid, req->addr, req->private, rwxp, result); - }/* We get here in the error case where the PASID lookup failed, and these can be NULL. Do not use them below this point! */ sdev =NULL ;
@@ -704,7 +755,7 @@ static irqreturn_t prq_event_thread(int irq, void *d)qi_submit_sync (&resp, iommu); } - + prq_advance: head = (head +sizeof (*req)) &PRQ_RING_MASK ; }
include/linux/iommu.h +1 -0
@@ -42,6 +42,7 @@* if the IOMMU page table format is equivalent. */ #define IOMMU_PRIV (1 << 5) +#define IOMMU_EXEC (1 << 6)struct iommu_ops ;struct iommu_group ;
Signature · 1 line
2.7.4
RE: [PATCH v4 17/22] iommu/intel-svm: report device page request
Hi Jacob,
Same comment with the one to patch 16, pci_get_bus_and_slot() is deprecated, may update accordingly.
Thanks,
Yi Liu
Quoted 143 lines
From: Jacob Pan [mailto:jacob.jun.pan-VuQAYsv1563Yd54FQh9/[email protected]]
Sent: Friday, March 23, 2018 11:12 AMIf the source device of a page request has its PASID table pointer bound to a guest, the first level page tables are owned by the guest.
In this case, we shall let guest OS to manage page fault.This patch uses the IOMMU fault reporting API to send fault events, possibly via VFIO, to the guest OS. Once guest pages are fault in, guest will issue page response which will be passed down via the invalidation passdown APIs.
Recoverable faults, such as page request reporting is not limitted to guest use. In kernel driver can also request a chance to receive fault notifications.
Signed-off-by Ashok Raj <[email protected]>--- drivers/iommu/intel-svm.c | 71 ++++++++++++++++++++++++++++++++++++++++--- ---- include/linux/iommu.h | 1 + 2 files changed, 62 insertions(+), 10 deletions(-)drivers/iommu/intel-svm.c index renamed from drivers/iommu/intel-svm.c +0
-099bc9bd..bdc702c 100644
drivers/iommu/intel-svm.c +58
-4@@ -580,6 +580,56 @@ static bool is_canonical_address(u64 addr)return (((saddr << shift) >> shift) == saddr); } +static int prq_to_iommu_prot (struct page_req_dsc *req) { +int prot =0 ; + +if (req->rd_req) + prot |=IOMMU_FAULT_READ ; +if (req->wr_req) + prot |=IOMMU_FAULT_WRITE ; +if (req->exe_req) + prot |=IOMMU_FAULT_EXEC ; +if (req->priv_req) + prot |=IOMMU_FAULT_PRIV ; + +return prot; +} + +static int intel_svm_prq_report (struct page_req_dsc *desc) { +int ret =0 ; +struct iommu_fault_event event; +struct pci_dev *pdev; + +memset (&event,0 ,sizeof (struct iommu_fault_event )); + pdev =pci_get_bus_and_slot (desc->bus, desc->devfn); +if (!pdev) { +pr_err ("No PCI device found for PRQ [%02x:%02x.%d]\n" , + desc->bus,PCI_SLOT (desc->devfn), +PCI_FUNC (desc->devfn)); +return -ENODEV ; + } + +/* Fill in event data for device specific processing */ + event.type =IOMMU_FAULT_PAGE_REQ ; + event.addr = (u64 )desc->addr <<VTD_PAGE_SHIFT ; + event.pasid = desc->pasid; + event.page_req_group_id = desc->prg_index; + event.prot =prq_to_iommu_prot (desc); + event.last_req = desc->lpig; + event.pasid_valid =1 ; +/* keep track of PRQ so that when the response comes back, we know +* whether we do group response or stream response. SRR[0] and +* private[54:32] bits in the descriptor are stored. +*/ + event.iommu_private = *(u64 *)desc; + ret =iommu_report_device_fault (&pdev->dev, &event); +pci_dev_put (pdev); + +return ret; +} +static irqreturn_t prq_event_thread (int irq,void *d) {struct intel_iommu *iommu = d;@@ -628,6 +678,16 @@ static irqreturn_t prq_event_thread(int irq, void *d) goto no_pasid; } } +/* If address is not canonical, return invalid response */ +if (!is_canonical_address (address)) + goto bad_req; + +/* +* If prq is to be handled outside iommu driver via receiver of +* the fault notifiers, we skip the page response here. +*/ +if (!intel_svm_prq_report (req)) + goto prq_advance; result =QI_RESP_INVALID ;/* Since we're using init_mm.pgd directly, we should never take @@ -638,9 +698,6 @@ static irqreturn_t prq_event_thread(int irq, void *d) if (!mmget_not_zero(svm->mm)) goto bad_req; -/* If address is not canonical, return invalid response */ -if (!is_canonical_address (address)) - goto bad_req;down_read(&svm->mm->mmap_sem); vma = find_extend_vma(svm->mm, address); @@ -673,12 +730,6 @@ static
irqreturn_tprq_event_thread(int irq, void *d)drivers/iommu/intel-svm.c +0
-5if (WARN_ON (&sdev->list == &svm->devs)) sdev =NULL ; - -if (sdev && sdev->ops && sdev->ops->fault_cb) { -int rwxp = (req->rd_req <<3 ) | (req->wr_req <<2 ) | - (req->exe_req <<1 ) | (req->priv_req); - sdev->ops->fault_cb(sdev->dev, req->pasid, req->addr, req-Quoted 1 line
private, rwxp, result);
drivers/iommu/intel-svm.c +1
-2- }/* We get here in the error case where the PASID lookup failed, and these can be NULL. Do not use them below this point! */ sdev =NULL ;@@ -704,7 +755,7 @@ static irqreturn_t prq_event_thread(int irq, void *d)qi_submit_sync (&resp, iommu); } - + prq_advance: head = (head +sizeof (*req)) &PRQ_RING_MASK ; }include/linux/iommu.h index 94cfda8..dbe2866 renamed from include/linux/iommu.h +0
-0100644
include/linux/iommu.h +1
-1@@ -42,6 +42,7 @@* if the IOMMU page table format is equivalent. */ #define IOMMU_PRIV (1 << 5) +#define IOMMU_EXEC (1 << 6)struct iommu_ops ;struct iommu_group ; --2.7.4
Re: [PATCH v4 17/22] iommu/intel-svm: report device page request
Quoted 2 lines · Yi L
Same comment with the one to patch 16,
pci_get_bus_and_slot()is deprecated, may update accordingly.
yes, it has been updated in v5, could you review v5?
PATCH v4 18/22 iommu/intel-svm: replace dev ops with fault report API
In reply to Jacob PanWith the introduction of generic IOMMU device fault reporting API, we can replace the private fault callback functions with standard function and event data.
--- drivers/iommu/intel-svm.c | 7 +------ include/linux/intel-svm.h | 20 +++----------------- 2 files changed, 4 insertions(+), 23 deletions(-)
drivers/iommu/intel-svm.c +1 -6
@@ -299,7 +299,7 @@ static const struct mmu_notifier_ops intel_mmuops = {static DEFINE_MUTEX (pasid_mutex); -int intel_svm_bind_mm (struct device *dev,int *pasid,int flags,struct svm_dev_ops *ops) +int intel_svm_bind_mm (struct device *dev,int *pasid,int flags) {struct intel_iommu *iommu =intel_svm_device_to_iommu (dev);struct intel_svm_dev *sdev;
@@ -346,10 +346,6 @@ int intel_svm_bind_mm(struct device *dev, int *pasid, int flags, struct svm_dev_list_for_each_entry (sdev, &svm->devs, list) {if (dev == sdev->dev) { -if (sdev->ops != ops) { - ret = -EBUSY ; - goto out; - } sdev->users++; goto success; }
@@ -375,7 +371,6 @@ int intel_svm_bind_mm(struct device *dev, int *pasid, int flags, struct svm_dev_ }/* Finish the setup now we know we're keeping it */ sdev->users =1 ; - sdev->ops = ops;init_rcu_head (&sdev->rcu);if (!svm) {
include/linux/intel-svm.h +3 -17
@@ -18,18 +18,6 @@struct device ; -struct svm_dev_ops { -void (*fault_cb)(struct device *dev,int pasid,u64 address, -u32 private,int rwxp,int response); -}; - -/* Values for rxwp in fault_cb callback */ -#define SVM_REQ_READ (1<<3) -#define SVM_REQ_WRITE (1<<2) -#define SVM_REQ_EXEC (1<<1) -#define SVM_REQ_PRIV (1<<0) - -/* * The SVM_FLAG_PRIVATE_PASID flag requests a PASID which is *not* the "main" * PASID for the current process. Even if a PASID already exists, a new one
@@ -60,7 +48,6 @@ struct svm_dev_ops {* @dev: Device to be granted acccess * @pasid: Address for allocated PASID * @flags: Flags. Later for requesting supervisor mode, etc. -* @ops: Callbacks to device driver * * This function attempts to enable PASID support for the given device. * If the @pasid argument is non-%NULL, a PASID is allocated for access
@@ -82,8 +69,7 @@ struct svm_dev_ops {* Multiple calls from the same process may result in the same PASID * being re-used. A reference count is kept. */ -extern int intel_svm_bind_mm (struct device *dev,int *pasid,int flags, -struct svm_dev_ops *ops); +extern int intel_svm_bind_mm (struct device *dev,int *pasid,int flags);/** * intel_svm_unbind_mm() - Unbind a specified PASID
@@ -120,7 +106,7 @@ extern int intel_svm_is_pasid_valid(struct device *dev, int pasid);#else /* CONFIG_INTEL_IOMMU_SVM */ static inline int intel_svm_bind_mm (struct device *dev,int *pasid, -int flags,struct svm_dev_ops *ops) +int flags) {return -ENOSYS ; }
@@ -136,6 +122,6 @@ static int intel_svm_is_pasid_valid(struct device *dev, int pasid) }#endif /* CONFIG_INTEL_IOMMU_SVM */ -#define intel_svm_available (dev) (!intel_svm_bind_mm((dev), NULL, 0, NULL)) +#define intel_svm_available (dev) (!intel_svm_bind_mm((dev), NULL, 0))#endif /* __INTEL_SVM_H__ */
Signature · 1 line
2.7.4
PATCH v4 19/22 iommu/intel-svm: do not flush iotlb for viommu
In reply to Jacob PanvIOMMU passdown invalidation will be inclusive, PASID cache invalidation includes TLBs. See Intel VT-d Specification Ch 6.5.2.2 for details.
--- drivers/iommu/intel-svm.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-)
drivers/iommu/intel-svm.c +3 -1
@@ -284,7 +284,9 @@ static void intel_mm_release(struct mmu_notifier *mn, struct mm_struct *mm)rcu_read_lock ();list_for_each_entry_rcu (sdev, &svm->devs, list) {intel_flush_pasid_dev (svm, sdev, svm->pasid); -intel_flush_svm_range_dev (svm, sdev,0 ,-1 ,0 , !svm->mm); +/* for emulated iommu, PASID cache invalidation implies IOTLB/DTLB */ +if (!cap_caching_mode (svm->iommu->cap)) +intel_flush_svm_range_dev (svm, sdev,0 ,-1 ,0 , !svm->mm); }rcu_read_unlock ();
Signature · 1 line
2.7.4
PATCH v4 20/22 iommu/vt-d: add intel iommu page response function
In reply to Jacob PanThis patch adds page response support for Intel VT-d. Generic response data is taken from the IOMMU API then parsed into VT-d specific response descriptor format.
--- drivers/iommu/intel-iommu.c | 47 +++++++++++++++++++++++++++++++++++++++++++++ include/linux/intel-iommu.h | 3 +++ 2 files changed, 50 insertions(+)
drivers/iommu/intel-iommu.c +47 -0
@@ -5195,6 +5195,52 @@ static int intel_iommu_sva_invalidate(struct iommu_domain *domain,return ret; } +int intel_iommu_page_response (struct device *dev,struct page_response_msg *msg) +{ +struct qi_desc resp; +struct intel_iommu *iommu; +struct pci_dev *pdev; +u8 bus, devfn; +u16 rid; +u64 desc; + + pdev =to_pci_dev (dev); + iommu =device_to_iommu (dev, &bus, &devfn); +if (!iommu) { +dev_err (dev,"No IOMMU for device to unbind PASID table\n" ); +return -ENODEV ; + } + +pci_dev_get (pdev); + rid = ((u16 )bus <<8 ) | devfn; +/* Iommu private data contains preserved page request descriptor, so we +* inspect the SRR bit for response type then queue response with only +* the private data [54:32]. +*/ + desc = msg->private_data; +if (desc &QI_PRQ_SRR ) { +/* Page Stream Response */ + resp.low =QI_PSTRM_IDX (msg->page_req_group_id) | + (desc &QI_PRQ_PRIV ) |QI_PSTRM_BUS (PCI_BUS_NUM (pdev->bus->number)) | +QI_PSTRM_PASID (msg->pasid) |QI_PSTRM_RESP_TYPE ; + resp.high =QI_PSTRM_ADDR (msg->addr) |QI_PSTRM_DEVFN (pdev->devfn &0xff ) | +QI_PSTRM_RESP_CODE (msg->resp_code); + }else { +/* Page Group Response */ + resp.low =QI_PGRP_PASID (msg->pasid) | +QI_PGRP_DID (rid) | +QI_PGRP_PASID_P (msg->pasid_present) | +QI_PGRP_RESP_TYPE ; + resp.high =QI_PGRP_IDX (msg->page_req_group_id) | + (desc &QI_PRQ_PRIV ) |QI_PGRP_RESP_CODE (msg->resp_code); + + } +qi_submit_sync (&resp, iommu); +pci_dev_put (pdev); + +return 0 ; +} +static int intel_iommu_map (struct iommu_domain *domain,unsigned long iova,phys_addr_t hpa,size_t size,int iommu_prot)
@@ -5625,6 +5671,7 @@ const struct iommu_ops intel_iommu_ops = { .bind_pasid_table = intel_iommu_bind_pasid_table, .unbind_pasid_table = intel_iommu_unbind_pasid_table, .sva_invalidate = intel_iommu_sva_invalidate, + .page_response = intel_iommu_page_response, #endif .map = intel_iommu_map, .unmap = intel_iommu_unmap,
include/linux/intel-iommu.h +3 -0
@@ -337,6 +337,9 @@ enum {#define QI_PSTRM_BUS (bus) (((u64)(bus)) << 24)#define QI_PSTRM_PASID (pasid) (((u64)(pasid)) << 4) +#define QI_PRQ_SRR BIT_ULL(0) +#define QI_PRQ_PRIV GENMASK_ULL(54, 32) +#define QI_RESP_SUCCESS 0x0#define QI_RESP_INVALID 0x1#define QI_RESP_FAILURE 0xf
Signature · 1 line
2.7.4
kbuild test robot
build botRe: [PATCH v4 20/22] iommu/vt-d: add intel iommu page response function
I love your patch! Perhaps something to improve:
Hi Jacob,
I love your patch! Perhaps something to improve:
[auto build test WARNING on iommu/next]
[also build test WARNING on v4.16-rc6 next-20180323]
[if your patch is applied to the wrong git tree, please drop us a note to help improve the system]
url: https://github.com/0day-ci/linux/commits/Jacob-Pan/IOMMU-and-VT-d-driver-support-for-Shared-Virtual-Address-SVA/20180325-024555 base: https://git.kernel.org/pub/scm/linux/kernel/git/joro/iommu.git next reproduce: # apt-get install sparse make ARCH=x86_64 allmodconfig make C=1 CF=-D__CHECK_ENDIAN__
sparse warnings: (new ones prefixed by >>)
drivers/iommu/intel-iommu.c:500:5: sparse: symbol 'intel_iommu_gfx_mapped' was not declared. Should it be static?
drivers/iommu/intel-iommu.c:3066:41: sparse: incorrect type in argument 1 (different address spaces) @@ expected void volatile [noderef] <asn:2>*addr @@ got ile [noderef] <asn:2>*addr @@
drivers/iommu/intel-iommu.c:3066:41: expected void volatile [noderef] <asn:2>*addr
drivers/iommu/intel-iommu.c:3066:41: got struct context_entry *old_ce
>> drivers/iommu/intel-iommu.c:5198:5: sparse: symbol 'intel_iommu_page_response' was not declared. Should it be static? >> drivers/iommu/intel-iommu.c:5224:48: sparse: right shift by bigger than source value
Please review and possibly fold the followup patch.
---
0-DAY kernel test infrastructure Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all Intel Corporation
kbuild test robot
build bot[RFC PATCH] iommu/vt-d: intel_iommu_page_response() can be static
In reply to Jacob PanFixes: 49c5977b4ee7 ("iommu/vt-d: add intel iommu page response function")
--- intel-iommu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/iommu/intel-iommu.c +1 -1
@@ -5195,7 +5195,7 @@ static int intel_iommu_sva_invalidate(struct iommu_domain *domain,return ret; } -int intel_iommu_page_response (struct device *dev,struct page_response_msg *msg) +static int intel_iommu_page_response (struct device *dev,struct page_response_msg *msg) {struct qi_desc resp;struct intel_iommu *iommu;
PATCH v4 21/22 trace/iommu: add sva trace events
In reply to Jacob Pan--- include/trace/events/iommu.h | 112 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+)
include/trace/events/iommu.h +112 -0
@@ -12,6 +12,8 @@#define _TRACE_IOMMU_H#include <linux/tracepoint.h> +#include <linux/iommu.h> +#include <uapi/linux/iommu.h> struct device ;
@@ -161,6 +163,116 @@ DEFINE_EVENT(iommu_error, io_page_fault,TP_ARGS (dev, iova, flags) ); + +TRACE_EVENT (dev_fault, + + TP_PROTO(struct device *dev, struct iommu_fault_event *evt), + +TP_ARGS (dev, evt), + +TP_STRUCT__entry ( + __string(device, dev_name(dev)) + __field(int, type) + __field(int, reason) + __field(u64, addr) + __field(u32, pasid) + __field(u32, pgid) + __field(u32, last_req) +__field (u32, prot) + ), + +TP_fast_assign ( + __assign_str(device, dev_name(dev)); + __entry->type = evt->type; + __entry->reason = evt->reason; + __entry->addr = evt->addr; + __entry->pasid = evt->pasid; + __entry->pgid = evt->page_req_group_id; + __entry->last_req = evt->last_req; + __entry->prot = evt->prot; + ), + +TP_printk ("IOMMU:%s type=%d reason=%d addr=0x%016llx pasid=%d group=%d last=%d prot=%d" , +__get_str (device), + __entry->type, + __entry->reason, + __entry->addr, + __entry->pasid, + __entry->pgid, + __entry->last_req, + __entry->prot + ) +); + +TRACE_EVENT (dev_page_response, + + TP_PROTO(struct device *dev, struct page_response_msg *msg), + +TP_ARGS (dev, msg), + +TP_STRUCT__entry ( + __string(device, dev_name(dev)) + __field(int, code) + __field(u64, addr) + __field(u32, pasid) +__field (u32, pgid) + ), + +TP_fast_assign ( + __assign_str(device, dev_name(dev)); + __entry->code = msg->resp_code; + __entry->addr = msg->addr; + __entry->pasid = msg->pasid; + __entry->pgid = msg->page_req_group_id; + ), + +TP_printk ("IOMMU:%s code=%d addr=0x%016llx pasid=%d group=%d" , +__get_str (device), + __entry->code, + __entry->addr, + __entry->pasid, + __entry->pgid + ) +); + +TRACE_EVENT (sva_invalidate, + + TP_PROTO(struct device *dev, struct tlb_invalidate_info *ti), + +TP_ARGS (dev, ti), + +TP_STRUCT__entry ( + __string(device, dev_name(dev)) + __field(int, type) + __field(u32, granu) + __field(u32, flags) + __field(u8, size) + __field(u32, pasid) +__field (u64, addr) + ), + +TP_fast_assign ( + __assign_str(device, dev_name(dev)); + __entry->type = ti->hdr.type; + __entry->flags = ti->flags; + __entry->granu = ti->granularity; + __entry->size = ti->size; + __entry->pasid = ti->pasid; + __entry->addr = ti->addr; + ), + +TP_printk ("IOMMU:%s type=%d flags=0x%08x granu=%d size=%d pasid=%d addr=0x%016llx" , +__get_str (device), + __entry->type, + __entry->flags, + __entry->granu, + __entry->size, + __entry->pasid, + __entry->addr + ) +); + +#endif /* _TRACE_IOMMU_H */ /* This part must be outside protection */
Signature · 1 line
2.7.4
PATCH v4 22/22 iommu: use sva invalidate and device fault trace event
In reply to Jacob PanFor performance and debugging purposes, these trace events help analyzing device faults and passdown invalidations that interact with IOMMU subsystem.
E.g.
IOMMU:0000:00:0a.0 type=2 reason=0 addr=0x00000000007ff000 pasid=1 group=1 last=0 prot=1
--- drivers/iommu/iommu.c | 3 +++ 1 file changed, 3 insertions(+)
drivers/iommu/iommu.c +3 -0
@@ -979,6 +979,7 @@ int iommu_report_device_fault(struct device *dev, struct iommu_fault_event *evt)mutex_unlock (&fparam->lock); } ret = fparam->handler (evt, fparam->data); +trace_dev_fault (dev, evt); done_unlock:mutex_unlock (&dev->iommu_param->lock);return ret;
@@ -1547,6 +1548,7 @@ int iommu_sva_invalidate(struct iommu_domain *domain,return -ENODEV ; ret = domain->ops->sva_invalidate (domain, dev, inv_info); +trace_sva_invalidate (dev, inv_info);return ret; }
@@ -1584,6 +1586,7 @@ int iommu_page_response(struct device *dev,if (evt->pasid == msg->pasid && msg->page_req_group_id == evt->page_req_group_id) { msg->private_data = evt->iommu_private; +trace_dev_page_response (dev, msg); ret = domain->ops->page_response (dev, msg);list_del (&evt->list);kfree (evt);
Signature · 1 line
2.7.4