diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c index c6b9a51485f1e95f1b3c32337cabe1657849d8a3..7e465dde9d3c645f5e8b02a99f885e497205e694 100644 --- a/drivers/iommu/iommu.c +++ b/drivers/iommu/iommu.c @@ -649,6 +649,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; @@ -679,6 +686,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: @@ -725,7 +733,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; @@ -859,6 +867,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 diff --git a/include/linux/iommu.h b/include/linux/iommu.h index 20bdfc0844ba21970f3df891d88d048752393895..7a25981570ccbbe09ad739d15e2868f930673bd1 100644 --- a/include/linux/iommu.h +++ b/include/linux/iommu.h @@ -304,7 +304,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 @@ -322,6 +323,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; @@ -338,10 +340,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; }; @@ -355,6 +360,7 @@ struct iommu_fault_param { * struct iommu_fwspec *iommu_fwspec; */ struct iommu_param { + struct mutex lock; struct iommu_fault_param *fault_param; }; @@ -454,6 +460,15 @@ 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 *); @@ -721,6 +736,24 @@ 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;