diff mbox

[RFCv2,09/36] iommu/fault: Allow blocking fault handlers

Message ID 20171006133203.22803-10-jean-philippe.brucker@arm.com (mailing list archive)
State New, archived
Headers show

Commit Message

Jean-Philippe Brucker Oct. 6, 2017, 1:31 p.m. UTC
Allow device driver to register their fault handler at various stages of
the handling path, by adding flags to iommu_set_ext_fault_handler. Since
we now have a fault workqueue, it is quite easy to call their handler from
thread context instead of IRQ handler.

A driver can request to be called both in blocking and non-blocking
context, so it can filter faults early and only execute the blocking code
for some of them. Add the IOMMU_FAULT_ATOMIC fault flag to tell the driver
where we're calling it from.

Signed-off-by: Jean-Philippe Brucker <jean-philippe.brucker@arm.com>
---

Rob, would this do what you want? The MSM driver can register its handler
with ATOMIC | BLOCKING flags. When called in IRQ context, it can ignore
the fault by returning IOMMU_FAULT_STATUS_NONE, or drop it by returning
IOMMU_FAULT_STATUS_INVALID. When called in thread context, it can sleep
and then return IOMMU_FAULT_STATUS_INVALID to terminate the fault.
---
 drivers/iommu/io-pgfault.c | 16 ++++++++++++++--
 drivers/iommu/iommu.c      | 12 +++++++++---
 include/linux/iommu.h      | 20 +++++++++++++++++++-
 3 files changed, 42 insertions(+), 6 deletions(-)

Comments

Xie Yisheng Nov. 29, 2017, 6:15 a.m. UTC | #1
Hi Jean,

On 2017/10/6 21:31, Jean-Philippe Brucker wrote:
> -	if (domain->ext_handler) {
> +	if (domain->handler_flags & IOMMU_FAULT_HANDLER_ATOMIC) {
> +		fault->flags |= IOMMU_FAULT_ATOMIC;

Why remove the condition of domain->ext_handler? should it be much better like:
  if ((domain->handler_flags & IOMMU_FAULT_HANDLER_ATOMIC) && domain->ext_handler)

If domain->ext_handler is NULL, and (domain->handler_flags & IOMMU_FAULT_HANDLER_ATOMIC)
is true. It will oops, right?

>  		ret = domain->ext_handler(domain, dev, fault,
>  					  domain->handler_token);

Thanks
Yisheng Xie
Jean-Philippe Brucker Nov. 29, 2017, 3:01 p.m. UTC | #2
Hello,

On 29/11/17 06:15, Yisheng Xie wrote:
> Hi Jean,
> 
> On 2017/10/6 21:31, Jean-Philippe Brucker wrote:
>> -	if (domain->ext_handler) {
>> +	if (domain->handler_flags & IOMMU_FAULT_HANDLER_ATOMIC) {
>> +		fault->flags |= IOMMU_FAULT_ATOMIC;
> 
> Why remove the condition of domain->ext_handler? should it be much better like:
>   if ((domain->handler_flags & IOMMU_FAULT_HANDLER_ATOMIC) && domain->ext_handler)
> 
> If domain->ext_handler is NULL, and (domain->handler_flags & IOMMU_FAULT_HANDLER_ATOMIC)
> is true. It will oops, right?

I removed the check because ext_handler shouldn't be NULL if handler_flags
has a bit set (as per iommu_set_ext_fault_handler). But you're right that
this is fragile, and I overlooked the case where users could call
set_ext_fault_handler to clear the fault handler.

(Note that this ext_handler will most likely be replaced by the fault
infrastructure that Jacob is working on:
https://patchwork.kernel.org/patch/10063385/ to which we should add the
atomic/blocking flags)

Thanks,
Jean
Xie Yisheng Nov. 30, 2017, 2:45 a.m. UTC | #3
hi jean,

On 2017/11/29 23:01, Jean-Philippe Brucker wrote:
> Hello,
> 
> On 29/11/17 06:15, Yisheng Xie wrote:
>> Hi Jean,
>>
>> On 2017/10/6 21:31, Jean-Philippe Brucker wrote:
>>> -	if (domain->ext_handler) {
>>> +	if (domain->handler_flags & IOMMU_FAULT_HANDLER_ATOMIC) {
>>> +		fault->flags |= IOMMU_FAULT_ATOMIC;
>>
>> Why remove the condition of domain->ext_handler? should it be much better like:
>>   if ((domain->handler_flags & IOMMU_FAULT_HANDLER_ATOMIC) && domain->ext_handler)
>>
>> If domain->ext_handler is NULL, and (domain->handler_flags & IOMMU_FAULT_HANDLER_ATOMIC)
>> is true. It will oops, right?
> 
> I removed the check because ext_handler shouldn't be NULL if handler_flags
> has a bit set (as per iommu_set_ext_fault_handler). But you're right that
> this is fragile, and I overlooked the case where users could call
> set_ext_fault_handler to clear the fault handler.
> 
> (Note that this ext_handler will most likely be replaced by the fault
> infrastructure that Jacob is working on:
> https://patchwork.kernel.org/patch/10063385/ to which we should add the
> atomic/blocking flags)
> 

Get it, thanks for your explanation.

Thanks
Yisheng Xie

> Thanks,
> Jean
> 
> .
>
diff mbox

Patch

diff --git a/drivers/iommu/io-pgfault.c b/drivers/iommu/io-pgfault.c
index 532bdb9ce519..3ec8179f58b5 100644
--- a/drivers/iommu/io-pgfault.c
+++ b/drivers/iommu/io-pgfault.c
@@ -91,6 +91,14 @@  static int iommu_fault_handle_single(struct iommu_fault_context *fault)
 	unsigned int access_flags = 0;
 	unsigned int fault_flags = FAULT_FLAG_REMOTE;
 	struct iommu_fault *params = &fault->params;
+	struct iommu_domain *domain = fault->domain;
+
+	if (domain->handler_flags & IOMMU_FAULT_HANDLER_BLOCKING) {
+		ret = domain->ext_handler(domain, fault->dev, &fault->params,
+					  domain->handler_token);
+		if (ret != IOMMU_FAULT_STATUS_NONE)
+			return ret;
+	}
 
 	if (!(params->flags & IOMMU_FAULT_PASID))
 		return ret;
@@ -274,7 +282,8 @@  int handle_iommu_fault(struct iommu_domain *domain, struct device *dev,
 	 * if upper layers showed interest and installed a fault handler,
 	 * invoke it.
 	 */
-	if (domain->ext_handler) {
+	if (domain->handler_flags & IOMMU_FAULT_HANDLER_ATOMIC) {
+		fault->flags |= IOMMU_FAULT_ATOMIC;
 		ret = domain->ext_handler(domain, dev, fault,
 					  domain->handler_token);
 
@@ -290,8 +299,11 @@  int handle_iommu_fault(struct iommu_domain *domain, struct device *dev,
 	}
 
 	/* If the handler is blocking, handle fault in the workqueue */
-	if (fault->flags & IOMMU_FAULT_RECOVERABLE)
+	if ((fault->flags & IOMMU_FAULT_RECOVERABLE) ||
+	    (domain->handler_flags & IOMMU_FAULT_HANDLER_BLOCKING)) {
+		fault->flags &= ~IOMMU_FAULT_ATOMIC;
 		ret = iommu_queue_fault(domain, dev, fault);
+	}
 
 	return iommu_fault_finish(domain, dev, fault, ret);
 }
diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index ee956b5fc301..c189648ab7b4 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c
@@ -1258,7 +1258,9 @@  EXPORT_SYMBOL_GPL(iommu_set_fault_handler);
  * @dev: the device
  * @handler: fault handler
  * @token: user data, will be passed back to the fault handler
- * @flags: IOMMU_FAULT_HANDLER_* parameters.
+ * @flags: IOMMU_FAULT_HANDLER_* parameters. Allows the driver to tell when it
+ * wants to be notified. By default the handler will only be called from atomic
+ * context.
  *
  * This function should be used by IOMMU users which want to be notified
  * whenever an IOMMU fault happens.
@@ -1275,11 +1277,15 @@  void iommu_set_ext_fault_handler(struct device *dev,
 	if (WARN_ON(!domain))
 		return;
 
+	if (!flags)
+		flags |= IOMMU_FAULT_HANDLER_ATOMIC;
+
 	if (WARN_ON(domain->handler || domain->ext_handler))
 		return;
 
 	domain->ext_handler = handler;
 	domain->handler_token = token;
+	domain->handler_flags = flags;
 }
 EXPORT_SYMBOL_GPL(iommu_set_ext_fault_handler);
 
@@ -1824,7 +1830,7 @@  int report_iommu_fault(struct iommu_domain *domain, struct device *dev,
 	int ret = -ENOSYS;
 	struct iommu_fault fault = {
 		.address	= iova,
-		.flags		= flags,
+		.flags		= flags | IOMMU_FAULT_ATOMIC,
 	};
 
 	/*
@@ -1834,7 +1840,7 @@  int report_iommu_fault(struct iommu_domain *domain, struct device *dev,
 	if (domain->handler)
 		ret = domain->handler(domain, dev, iova, flags,
 						domain->handler_token);
-	else if (domain->ext_handler)
+	else if (domain->handler_flags & IOMMU_FAULT_HANDLER_ATOMIC)
 		ret = domain->ext_handler(domain, dev, &fault,
 					  domain->handler_token);
 
diff --git a/include/linux/iommu.h b/include/linux/iommu.h
index 37fafaf07ee2..a6d417785c7b 100644
--- a/include/linux/iommu.h
+++ b/include/linux/iommu.h
@@ -66,6 +66,8 @@  struct notifier_block;
 #define IOMMU_FAULT_GROUP		(1 << 6)
 /* Fault is last of its group */
 #define IOMMU_FAULT_LAST		(1 << 7)
+/* The fault handler is being called from atomic context */
+#define IOMMU_FAULT_ATOMIC		(1 << 8)
 
 /**
  * enum iommu_fault_status - Return status of fault handlers, telling the IOMMU
@@ -97,6 +99,21 @@  enum iommu_fault_status {
 typedef int (*iommu_fault_handler_t)(struct iommu_domain *,
 			struct device *, unsigned long, int, void *);
 
+/*
+ * IOMMU_FAULT_HANDLER_ATOMIC: Notify device driver from within atomic context
+ * (IRQ handler). The callback is not allowed to sleep. If the fault is
+ * recoverable, the driver must either return a fault status telling the IOMMU
+ * driver how to complete the fault (FAILURE, INVALID, HANDLED) or complete the
+ * fault later with iommu_fault_response.
+ */
+#define IOMMU_FAULT_HANDLER_ATOMIC	(1 << 0)
+/*
+ * IOMMU_FAULT_HANDLER_BLOCKING: Notify device driver from a thread. If the fault
+ * is recoverable, the driver must return a fault status telling the IOMMU
+ * driver how to complete the fault (FAILURE, INVALID, HANDLED)
+ */
+#define IOMMU_FAULT_HANDLER_BLOCKING	(1 << 1)
+
 struct iommu_fault {
 	/* Faulting address */
 	unsigned long		address;
@@ -161,6 +178,7 @@  struct iommu_domain {
 	iommu_fault_handler_t handler;
 	iommu_ext_fault_handler_t ext_handler;
 	void *handler_token;
+	int handler_flags;
 	iommu_process_exit_handler_t process_exit;
 	void *process_exit_token;
 	struct iommu_domain_geometry geometry;
@@ -633,7 +651,7 @@  static inline phys_addr_t iommu_iova_to_phys(struct iommu_domain *domain, dma_ad
 }
 
 static inline void iommu_set_fault_handler(struct iommu_domain *domain,
-				iommu_fault_handler_t handler, void *token)
+				iommu_fault_handler_t handler, void *token, int flags)
 {
 }