Add bind() and unbind() operations to the IOMMU API. Bind() returns a PASID to the device driver (by convention, a 20-bit system-wide ID representing the address space). When programming DMA addresses, device drivers include this PASID in a device-specific manner, to let the device access the given address space. Since the process memory may be paged out, device and IOMMU must support I/O page faults (e.g. PCI PRI).
Device drivers pass an mm_exit() callback to bind(), that is called by the IOMMU driver if the process exits before the device driver called unbind(). In mm_exit(), device driver should disable DMA from the given context, so that the core IOMMU can reallocate the PASID. To use these functions, device driver must first enable the IOMMU_DEV_FEAT_SVA device feature with iommu_dev_enable_feature(). Signed-off-by: Jean-Philippe Brucker <jean-philippe.bruc...@arm.com> --- drivers/iommu/iommu.c | 104 ++++++++++++++++++++++++++++++++++++++++++ include/linux/iommu.h | 24 ++++++++++ 2 files changed, 128 insertions(+) diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c index 1629354255c3..5feba98566b2 100644 --- a/drivers/iommu/iommu.c +++ b/drivers/iommu/iommu.c @@ -2351,3 +2351,107 @@ int iommu_aux_get_pasid(struct iommu_domain *domain, struct device *dev) return ret; } EXPORT_SYMBOL_GPL(iommu_aux_get_pasid); + +/** + * iommu_sva_bind_device() - Bind a process address space to a device + * @dev: the device + * @mm: the mm to bind, caller must hold a reference to it + * @pasid: valid address where the PASID will be stored + * @mm_exit: notifier function to call when the mm exits + * @drvdata: private data passed to the mm exit handler + * + * Create a bond between device and task, allowing the device to access the mm + * using the returned PASID. If unbind() isn't called first, a subsequent bind() + * for the same device and mm fails with -EEXIST. + * + * iommu_dev_enable_feature(dev, IOMMU_DEV_FEAT_SVA) must be called first, to + * initialize the required SVA features. + * + * @mm_exit is called when the mm is about to be torn down by exit_mmap. After + * @mm_exit returns, the device must not issue any more transaction with the + * PASID given as argument. + * + * The @mm_exit handler is allowed to sleep. Be careful about the locks taken in + * @mm_exit, because they might lead to deadlocks if they are also held when + * dropping references to the mm. Consider the following call chain: + * mutex_lock(A); mmput(mm) -> exit_mm() -> @mm_exit() -> mutex_lock(A) + * Using mmput_async() prevents this scenario. + * + * On success, 0 is returned and @pasid contains a valid ID. Otherwise, an error + * is returned. + */ +int iommu_sva_bind_device(struct device *dev, struct mm_struct *mm, int *pasid, + iommu_mm_exit_handler_t mm_exit, void *drvdata) +{ + int ret = -EINVAL; + struct iommu_group *group; + const struct iommu_ops *ops = dev->bus->iommu_ops; + + if (!pasid) + return -EINVAL; + + if (!ops || !ops->bind_mm) + return -ENODEV; + + group = iommu_group_get(dev); + if (!group) + return -ENODEV; + + /* Ensure device count and domain don't change while we're binding */ + mutex_lock(&group->mutex); + + /* + * To keep things simple, SVA currently doesn't support IOMMU groups + * with more than one device. Existing SVA-capable systems are not + * affected by the problems that required IOMMU groups (lack of ACS + * isolation, device ID aliasing and other hardware issues). + */ + if (iommu_group_device_count(group) != 1) + goto out_unlock; + + ret = ops->bind_mm(dev, mm, pasid, mm_exit, drvdata); + +out_unlock: + mutex_unlock(&group->mutex); + iommu_group_put(group); + + return ret; +} +EXPORT_SYMBOL_GPL(iommu_sva_bind_device); + +/** + * iommu_sva_unbind_device() - Remove a bond created with iommu_sva_bind_device + * @dev: the device + * @pasid: the pasid returned by bind() + * + * Remove bond between device and address space identified by @pasid. Users + * should not call unbind() if the corresponding mm exited (as the PASID might + * have been reallocated for another process). + * + * The device must not be issuing any more transaction for this PASID. All + * outstanding page requests for this PASID must have been flushed to the IOMMU. + * + * Returns 0 on success, or an error value + */ +int iommu_sva_unbind_device(struct device *dev, int pasid) +{ + int ret = -EINVAL; + struct iommu_group *group; + const struct iommu_ops *ops = dev->bus->iommu_ops; + + if (!ops || !ops->unbind_mm) + return -ENODEV; + + group = iommu_group_get(dev); + if (!group) + return -ENODEV; + + mutex_lock(&group->mutex); + ret = ops->unbind_mm(dev, pasid); + mutex_unlock(&group->mutex); + + iommu_group_put(group); + + return ret; +} +EXPORT_SYMBOL_GPL(iommu_sva_unbind_device); diff --git a/include/linux/iommu.h b/include/linux/iommu.h index bdd68778ceb5..a69aeea58818 100644 --- a/include/linux/iommu.h +++ b/include/linux/iommu.h @@ -55,6 +55,7 @@ struct iommu_fault_event; 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 *); +typedef int (*iommu_mm_exit_handler_t)(struct device *dev, int pasid, void *); struct iommu_domain_geometry { dma_addr_t aperture_start; /* First address that can be mapped */ @@ -159,6 +160,7 @@ struct iommu_resv_region { /* Per device IOMMU features */ enum iommu_dev_features { IOMMU_DEV_FEAT_AUX, /* Aux-domain feature */ + IOMMU_DEV_FEAT_SVA, /* Shared Virtual Addresses */ }; #ifdef CONFIG_IOMMU_API @@ -228,6 +230,8 @@ struct page_response_msg { * @dev_feat_enabled: check enabled feature * @aux_attach/detach_dev: aux-domain specific attach/detach entries. * @aux_get_pasid: get the pasid given an aux-domain + * @bind_mm: Bind process address space to device + * @unbind_mm: Unbind process address space from device * @page_response: handle page request response * @pgsize_bitmap: bitmap of all possible supported page sizes */ @@ -283,6 +287,9 @@ struct iommu_ops { void (*aux_detach_dev)(struct iommu_domain *domain, struct device *dev); int (*aux_get_pasid)(struct iommu_domain *domain, struct device *dev); + int (*bind_mm)(struct device *dev, struct mm_struct *mm, int *pasid, + iommu_mm_exit_handler_t mm_exit, void *drvdata); + int (*unbind_mm)(struct device *dev, int pasid); int (*page_response)(struct device *dev, struct page_response_msg *msg); unsigned long pgsize_bitmap; @@ -541,6 +548,11 @@ int iommu_aux_attach_device(struct iommu_domain *domain, struct device *dev); void iommu_aux_detach_device(struct iommu_domain *domain, struct device *dev); int iommu_aux_get_pasid(struct iommu_domain *domain, struct device *dev); +extern int iommu_sva_bind_device(struct device *dev, struct mm_struct *mm, + int *pasid, iommu_mm_exit_handler_t mm_exit, + void *drvdata); +extern int iommu_sva_unbind_device(struct device *dev, int pasid); + #else /* CONFIG_IOMMU_API */ struct iommu_ops {}; @@ -889,6 +901,18 @@ iommu_aux_get_pasid(struct iommu_domain *domain, struct device *dev) return -ENODEV; } +static inline int +iommu_sva_bind_device(struct device *dev, struct mm_struct *mm, int *pasid, + iommu_mm_exit_handler_t mm_exit, void *drvdata) +{ + return -ENODEV; +} + +static inline int iommu_sva_unbind_device(struct device *dev, int pasid) +{ + return -ENODEV; +} + #endif /* CONFIG_IOMMU_API */ #ifdef CONFIG_IOMMU_DEBUGFS -- 2.20.1 _______________________________________________ iommu mailing list iommu@lists.linux-foundation.org https://lists.linuxfoundation.org/mailman/listinfo/iommu