Skip to content

Commit

Permalink
iommu: Introduce device fault report API
Browse files Browse the repository at this point in the history
Traditionally, 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.

This patch introduces a registration API for device specific fault
handlers. This differs from the existing iommu_set_fault_handler/
report_iommu_fault infrastructures in several ways:
- it allows to report more sophisticated fault events (both
  unrecoverable faults and page request faults) due to the nature
  of the iommu_fault struct
- it is device specific and not domain specific.

The current iommu_report_device_fault() implementation only handles
the "shoot and forget" unrecoverable fault case. Handling of page
request faults or stalled faults will come later.

Signed-off-by: Jacob Pan <jacob.jun.pan@linux.intel.com>
Signed-off-by: Ashok Raj <ashok.raj@intel.com>
Signed-off-by: Jean-Philippe Brucker <jean-philippe.brucker@arm.com>
Signed-off-by: Eric Auger <eric.auger@redhat.com>
Signed-off-by: Joerg Roedel <jroedel@suse.de>
  • Loading branch information
Jacob Pan authored and Joerg Roedel committed Jun 12, 2019
1 parent 4e32348 commit 0c830e6
Show file tree
Hide file tree
Showing 2 changed files with 172 additions and 3 deletions.
146 changes: 143 additions & 3 deletions drivers/iommu/iommu.c
Original file line number Diff line number Diff line change
Expand Up @@ -107,15 +107,43 @@ void iommu_device_unregister(struct iommu_device *iommu)
spin_unlock(&iommu_device_lock);
}

static struct iommu_param *iommu_get_dev_param(struct device *dev)
{
struct iommu_param *param = dev->iommu_param;

if (param)
return param;

param = kzalloc(sizeof(*param), GFP_KERNEL);
if (!param)
return NULL;

mutex_init(&param->lock);
dev->iommu_param = param;
return param;
}

static void iommu_free_dev_param(struct device *dev)
{
kfree(dev->iommu_param);
dev->iommu_param = NULL;
}

int iommu_probe_device(struct device *dev)
{
const struct iommu_ops *ops = dev->bus->iommu_ops;
int ret = -EINVAL;
int ret;

WARN_ON(dev->iommu_group);
if (!ops)
return -EINVAL;

if (ops)
ret = ops->add_device(dev);
if (!iommu_get_dev_param(dev))
return -ENOMEM;

ret = ops->add_device(dev);
if (ret)
iommu_free_dev_param(dev);

return ret;
}
Expand All @@ -126,6 +154,8 @@ void iommu_release_device(struct device *dev)

if (dev->iommu_group)
ops->remove_device(dev);

iommu_free_dev_param(dev);
}

static struct iommu_domain *__iommu_domain_alloc(struct bus_type *bus,
Expand Down Expand Up @@ -854,6 +884,116 @@ 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, this handler gets called with the
* fault event and data as argument. The handler should return 0 on success.
*
* 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;
int ret = 0;

if (!param)
return -EINVAL;

mutex_lock(&param->lock);
/* Only allow one fault handler registered for each device */
if (param->fault_param) {
ret = -EBUSY;
goto done_unlock;
}

get_device(dev);
param->fault_param = kzalloc(sizeof(*param->fault_param), GFP_KERNEL);
if (!param->fault_param) {
put_device(dev);
ret = -ENOMEM;
goto done_unlock;
}
param->fault_param->handler = handler;
param->fault_param->data = data;

done_unlock:
mutex_unlock(&param->lock);

return ret;
}
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(&param->lock);

if (!param->fault_param)
goto unlock;

kfree(param->fault_param);
param->fault_param = NULL;
put_device(dev);
unlock:
mutex_unlock(&param->lock);

return ret;
}
EXPORT_SYMBOL_GPL(iommu_unregister_device_fault_handler);

/**
* iommu_report_device_fault() - Report fault event to device driver
* @dev: the device
* @evt: fault event data
*
* Called by IOMMU drivers when a 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)
{
struct iommu_param *param = dev->iommu_param;
struct iommu_fault_param *fparam;
int ret = 0;

if (!param || !evt)
return -EINVAL;

/* we only report device fault if there is a handler registered */
mutex_lock(&param->lock);
fparam = param->fault_param;
if (!fparam || !fparam->handler) {
ret = -EINVAL;
goto done_unlock;
}
ret = fparam->handler(&evt->fault, fparam->data);
done_unlock:
mutex_unlock(&param->lock);
return ret;
}
EXPORT_SYMBOL_GPL(iommu_report_device_fault);

/**
* iommu_group_id - Return ID for a group
* @group: the group to ID
Expand Down
29 changes: 29 additions & 0 deletions include/linux/iommu.h
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ struct iommu_fault_param {
* struct iommu_fwspec *iommu_fwspec;
*/
struct iommu_param {
struct mutex lock;
struct iommu_fault_param *fault_param;
};

Expand Down Expand Up @@ -428,6 +429,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 *);
Expand Down Expand Up @@ -736,6 +746,25 @@ 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 -ENODEV;
}

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 -ENODEV;
}

static inline int iommu_group_id(struct iommu_group *group)
{
return -ENODEV;
Expand Down

0 comments on commit 0c830e6

Please sign in to comment.