[PATCH 1/7] staging: fsl-mc: MC bus IRQ support

J. German Rivera German.Rivera at freescale.com
Tue Apr 28 17:39:04 UTC 2015


All the IRQs for DPAA2 objects in the same DPRC must use
the ICID of that DPRC, as their device Id in the GIC-ITS.
Thus, all these IRQs must share the same ITT table in GIC. As
a result, a pool of IRQs with the same device Id must be
preallocated per DPRC (fsl-mc bus instance). So, the fsl-mc
bus object allocator is extended to also provide services
to allocate IRQs to DPAA2 devices, from their parent fsl-mc bus
IRQ pool.

Also, the interrupt handler for DPRC IRQs is added. DPRC IRQs are
generated for hot plug events related to DPAA2 objects in a given
DPRC. These events include, creating/destroying DPAA2 objects in
the DPRC, changing the "plugged" state of DPAA2 objects and moving
objects between DPRCs.

Signed-off-by: J. German Rivera <German.Rivera at freescale.com>
Change-Id: I2a986c465989c3811de19cfe9ed0b77168250cb1
Reviewed-on: http://git.am.freescale.net:8181/33626
Tested-by: Review Code-CDREVIEW <CDREVIEW at freescale.com>
Reviewed-by: Stuart Yoder <stuart.yoder at freescale.com>
---
 drivers/staging/fsl-mc/TODO                 |   3 +
 drivers/staging/fsl-mc/bus/dprc-driver.c    | 337 +++++++++++++++++++++++++-
 drivers/staging/fsl-mc/bus/mc-allocator.c   | 105 +++++++++
 drivers/staging/fsl-mc/bus/mc-bus.c         | 352 +++++++++++++++++++++++++++-
 drivers/staging/fsl-mc/include/mc-private.h |  26 +-
 drivers/staging/fsl-mc/include/mc.h         |  25 ++
 6 files changed, 837 insertions(+), 11 deletions(-)

diff --git a/drivers/staging/fsl-mc/TODO b/drivers/staging/fsl-mc/TODO
index d78288b..1b8868d 100644
--- a/drivers/staging/fsl-mc/TODO
+++ b/drivers/staging/fsl-mc/TODO
@@ -8,6 +8,9 @@
 * Add at least one device driver for a DPAA2 object (child device of the
   fsl-mc bus).
 
+* Enable code blocks guarded by #ifdef GIC_ITS_MC_SUPPORT, when GIC-ITS
+  support for the MC MSIs gets merged.
+
 Please send any patches to Greg Kroah-Hartman <gregkh at linuxfoundation.org>,
 german.rivera at freescale.com, devel at driverdev.osuosl.org,
 linux-kernel at vger.kernel.org
diff --git a/drivers/staging/fsl-mc/bus/dprc-driver.c b/drivers/staging/fsl-mc/bus/dprc-driver.c
index 35c06cf..8531ba8 100644
--- a/drivers/staging/fsl-mc/bus/dprc-driver.c
+++ b/drivers/staging/fsl-mc/bus/dprc-driver.c
@@ -13,6 +13,7 @@
 #include "../include/mc-sys.h"
 #include <linux/module.h>
 #include <linux/slab.h>
+#include <linux/interrupt.h>
 #include "dprc-cmd.h"
 
 struct dprc_child_objs {
@@ -241,6 +242,7 @@ static void dprc_cleanup_all_resource_pools(struct fsl_mc_device *mc_bus_dev)
  * dprc_scan_objects - Discover objects in a DPRC
  *
  * @mc_bus_dev: pointer to the fsl-mc device that represents a DPRC object
+ * @total_irq_count: total number of IRQs needed by objects in the DPRC.
  *
  * Detects objects added and removed from a DPRC and synchronizes the
  * state of the Linux bus driver, MC by adding and removing
@@ -254,11 +256,13 @@ static void dprc_cleanup_all_resource_pools(struct fsl_mc_device *mc_bus_dev)
  * populated before they can get allocation requests from probe callbacks
  * of the device drivers for the non-allocatable devices.
  */
-int dprc_scan_objects(struct fsl_mc_device *mc_bus_dev)
+int dprc_scan_objects(struct fsl_mc_device *mc_bus_dev,
+		      unsigned int *total_irq_count)
 {
 	int num_child_objects;
 	int dprc_get_obj_failures;
 	int error;
+	unsigned int irq_count = mc_bus_dev->obj_desc.irq_count;
 	struct dprc_obj_desc *child_obj_desc_array = NULL;
 
 	error = dprc_get_obj_count(mc_bus_dev->mc_io,
@@ -305,6 +309,7 @@ int dprc_scan_objects(struct fsl_mc_device *mc_bus_dev)
 				continue;
 			}
 
+			irq_count += obj_desc->irq_count;
 			dev_dbg(&mc_bus_dev->dev,
 				"Discovered object: type %s, id %d\n",
 				obj_desc->type, obj_desc->id);
@@ -317,6 +322,7 @@ int dprc_scan_objects(struct fsl_mc_device *mc_bus_dev)
 		}
 	}
 
+	*total_irq_count = irq_count;
 	dprc_remove_devices(mc_bus_dev, child_obj_desc_array,
 			    num_child_objects);
 
@@ -339,9 +345,10 @@ EXPORT_SYMBOL_GPL(dprc_scan_objects);
  * bus driver with the actual state of the MC by adding and removing
  * devices as appropriate.
  */
-int dprc_scan_container(struct fsl_mc_device *mc_bus_dev)
+static int dprc_scan_container(struct fsl_mc_device *mc_bus_dev)
 {
 	int error;
+	unsigned int irq_count;
 	struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_bus_dev);
 
 	dprc_init_all_resource_pools(mc_bus_dev);
@@ -350,17 +357,313 @@ int dprc_scan_container(struct fsl_mc_device *mc_bus_dev)
 	 * Discover objects in the DPRC:
 	 */
 	mutex_lock(&mc_bus->scan_mutex);
-	error = dprc_scan_objects(mc_bus_dev);
+	error = dprc_scan_objects(mc_bus_dev, &irq_count);
 	mutex_unlock(&mc_bus->scan_mutex);
 	if (error < 0)
 		goto error;
 
+	if (!mc_bus->irq_resources) {
+		irq_count += FSL_MC_IRQ_POOL_MAX_EXTRA_IRQS;
+		error = fsl_mc_populate_irq_pool(mc_bus, irq_count);
+		if (error < 0)
+			goto error;
+	}
+
 	return 0;
 error:
 	dprc_cleanup_all_resource_pools(mc_bus_dev);
 	return error;
 }
-EXPORT_SYMBOL_GPL(dprc_scan_container);
+
+/**
+ * dprc_irq0_handler - Regular ISR for DPRC interrupt 0
+ *
+ * @irq: IRQ number of the interrupt being handled
+ * @arg: Pointer to device structure
+ */
+static irqreturn_t dprc_irq0_handler(int irq_num, void *arg)
+{
+	return IRQ_WAKE_THREAD;
+}
+
+/**
+ * dprc_irq0_handler_thread - Handler thread function for DPRC interrupt 0
+ *
+ * @irq: IRQ number of the interrupt being handled
+ * @arg: Pointer to device structure
+ */
+static irqreturn_t dprc_irq0_handler_thread(int irq_num, void *arg)
+{
+	int error;
+	uint32_t status;
+	struct device *dev = (struct device *)arg;
+	struct fsl_mc_device *mc_dev = to_fsl_mc_device(dev);
+	struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_dev);
+	struct fsl_mc_io *mc_io = mc_dev->mc_io;
+	int irq_index = 0;
+
+	dev_dbg(dev, "DPRC IRQ %d\n", irq_num);
+	if (WARN_ON(!(mc_dev->flags & FSL_MC_IS_DPRC)))
+		return IRQ_HANDLED;
+
+	mutex_lock(&mc_bus->scan_mutex);
+	if (WARN_ON(mc_dev->irqs[irq_index]->irq_number != (uint32_t)irq_num))
+		goto out;
+
+	error = dprc_get_irq_status(mc_io, mc_dev->mc_handle, irq_index,
+				    &status);
+	if (error < 0) {
+		dev_err(dev,
+			"dprc_get_irq_status() failed: %d\n", error);
+		goto out;
+	}
+
+	error = dprc_clear_irq_status(mc_io, mc_dev->mc_handle, irq_index,
+				      status);
+	if (error < 0) {
+		dev_err(dev,
+			"dprc_clear_irq_status() failed: %d\n", error);
+		goto out;
+	}
+
+	if (status & (DPRC_IRQ_EVENT_OBJ_ADDED |
+		      DPRC_IRQ_EVENT_OBJ_REMOVED |
+		      DPRC_IRQ_EVENT_CONTAINER_DESTROYED |
+		      DPRC_IRQ_EVENT_OBJ_DESTROYED |
+		      DPRC_IRQ_EVENT_OBJ_CREATED)) {
+		unsigned int irq_count;
+
+		error = dprc_scan_objects(mc_dev, &irq_count);
+		if (error < 0) {
+			dev_err(dev, "dprc_scan_objects() failed: %d\n", error);
+			goto out;
+		}
+
+		WARN_ON((int16_t)irq_count < 0);
+
+		if ((int16_t)irq_count >
+			mc_bus->resource_pools[FSL_MC_POOL_IRQ].max_count) {
+			dev_warn(dev,
+				 "IRQs needed (%u) exceed IRQs preallocated (%u)\n",
+				 irq_count,
+				 mc_bus->resource_pools[FSL_MC_POOL_IRQ].
+								max_count);
+		}
+	}
+
+out:
+	mutex_unlock(&mc_bus->scan_mutex);
+	return IRQ_HANDLED;
+}
+
+/*
+ * Disable and clear interrupts for a given DPRC object
+ */
+static int disable_dprc_irqs(struct fsl_mc_device *mc_dev)
+{
+	int i;
+	int error;
+	struct fsl_mc_io *mc_io = mc_dev->mc_io;
+	int irq_count = mc_dev->obj_desc.irq_count;
+
+	if (WARN_ON(irq_count == 0))
+		return -EINVAL;
+
+	for (i = 0; i < irq_count; i++) {
+		/*
+		 * Disable generation of interrupt i, while we configure it:
+		 */
+		error = dprc_set_irq_enable(mc_io, mc_dev->mc_handle, i, 0);
+		if (error < 0) {
+			dev_err(&mc_dev->dev,
+				"dprc_set_irq_enable() failed: %d\n", error);
+
+			return error;
+		}
+
+		/*
+		 * Disable all interrupt causes for interrupt i:
+		 */
+		error = dprc_set_irq_mask(mc_io, mc_dev->mc_handle, i, 0x0);
+		if (error < 0) {
+			dev_err(&mc_dev->dev,
+				"dprc_set_irq_mask() failed: %d\n", error);
+
+			return error;
+		}
+
+		/*
+		 * Clear any leftover interrupt i:
+		 */
+		error = dprc_clear_irq_status(mc_io, mc_dev->mc_handle, i,
+					      ~0x0U);
+		if (error < 0) {
+			dev_err(&mc_dev->dev,
+				"dprc_clear_irq_status() failed: %d\n",
+				error);
+			return error;
+		}
+	}
+
+	return 0;
+}
+
+static void unregister_dprc_irq_handlers(struct fsl_mc_device *mc_dev)
+{
+	int i;
+	struct fsl_mc_device_irq *irq;
+	int irq_count = mc_dev->obj_desc.irq_count;
+
+	for (i = 0; i < irq_count; i++) {
+		irq = mc_dev->irqs[i];
+		devm_free_irq(&mc_dev->dev, irq->irq_number,
+			      &mc_dev->dev);
+	}
+}
+
+static int register_dprc_irq_handlers(struct fsl_mc_device *mc_dev)
+{
+	static const struct irq_handler {
+		irq_handler_t irq_handler;
+		irq_handler_t irq_handler_thread;
+		const char *irq_name;
+	} irq_handlers[] = {
+		[0] = {
+			.irq_handler = dprc_irq0_handler,
+			.irq_handler_thread = dprc_irq0_handler_thread,
+			.irq_name = "FSL MC DPRC irq0",
+		},
+	};
+
+	unsigned int i;
+	int error;
+	struct fsl_mc_device_irq *irq;
+	unsigned int num_irq_handlers_registered = 0;
+	int irq_count = mc_dev->obj_desc.irq_count;
+
+	if (WARN_ON(irq_count != ARRAY_SIZE(irq_handlers)))
+		return -EINVAL;
+
+	for (i = 0; i < ARRAY_SIZE(irq_handlers); i++) {
+		irq = mc_dev->irqs[i];
+		error = devm_request_threaded_irq(&mc_dev->dev,
+						  irq->irq_number,
+						  irq_handlers[i].irq_handler,
+						  irq_handlers[i].
+							irq_handler_thread,
+						  IRQF_NO_SUSPEND |
+							IRQF_ONESHOT,
+						  irq_handlers[i].irq_name,
+						  &mc_dev->dev);
+		if (error < 0) {
+			dev_err(&mc_dev->dev,
+				"devm_request_threaded_irq() failed: %d\n",
+				error);
+			goto error_unregister_irq_handlers;
+		}
+
+		/*
+		 * Program the MSI (paddr, value) pair in the device:
+		 *
+		 * TODO: This needs to be moved to mc_bus_msi_domain_write_msg()
+		 * when the MC object-independent dprc_set_irq() flib API
+		 * becomes available
+		 */
+		error = dprc_set_irq(mc_dev->mc_io, mc_dev->mc_handle,
+				     i, irq->msi_paddr,
+				     irq->msi_value,
+				     irq->irq_number);
+		if (error < 0) {
+			dev_err(&mc_dev->dev,
+				"mc_set_irq() failed: %d\n", error);
+			goto error_unregister_irq_handlers;
+		}
+
+		num_irq_handlers_registered++;
+	}
+
+	return 0;
+
+error_unregister_irq_handlers:
+	for (i = 0; i < num_irq_handlers_registered; i++) {
+		irq = mc_dev->irqs[i];
+		devm_free_irq(&mc_dev->dev, irq->irq_number,
+			      &mc_dev->dev);
+	}
+
+	return error;
+}
+
+static int enable_dprc_irqs(struct fsl_mc_device *mc_dev)
+{
+	int i;
+	int error;
+	int irq_count = mc_dev->obj_desc.irq_count;
+
+	for (i = 0; i < irq_count; i++) {
+		/*
+		 * Enable all interrupt causes for the interrupt:
+		 */
+		error = dprc_set_irq_mask(mc_dev->mc_io,
+					  mc_dev->mc_handle,
+					  i,
+					  ~0x0u);
+		if (error < 0) {
+			dev_err(&mc_dev->dev,
+				"dprc_set_irq_mask() failed: %d\n", error);
+
+			return error;
+		}
+
+		/*
+		 * Enable generation of the interrupt:
+		 */
+		error = dprc_set_irq_enable(mc_dev->mc_io,
+					    mc_dev->mc_handle,
+					    i, 1);
+		if (error < 0) {
+			dev_err(&mc_dev->dev,
+				"dprc_set_irq_enable() failed: %d\n", error);
+
+			return error;
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * Setup interrupts for a given DPRC device
+ */
+static int dprc_setup_irqs(struct fsl_mc_device *mc_dev)
+{
+	int error;
+
+	error = fsl_mc_allocate_irqs(mc_dev);
+	if (error < 0)
+		return error;
+
+	error = disable_dprc_irqs(mc_dev);
+	if (error < 0)
+		goto error_free_irqs;
+
+	error = register_dprc_irq_handlers(mc_dev);
+	if (error < 0)
+		goto error_free_irqs;
+
+	error = enable_dprc_irqs(mc_dev);
+	if (error < 0)
+		goto error_unregister_irq_handlers;
+
+	return 0;
+
+error_unregister_irq_handlers:
+	unregister_dprc_irq_handlers(mc_dev);
+
+error_free_irqs:
+	fsl_mc_free_irqs(mc_dev);
+	return error;
+}
 
 /**
  * dprc_probe - callback invoked when a DPRC is being bound to this driver
@@ -415,10 +718,20 @@ static int dprc_probe(struct fsl_mc_device *mc_dev)
 	if (error < 0)
 		goto error_cleanup_open;
 
+	/*
+	 * Configure interrupts for the DPRC object associated with this MC bus:
+	 */
+	error = dprc_setup_irqs(mc_dev);
+	if (error < 0)
+		goto error_cleanup_open;
+
 	dev_info(&mc_dev->dev, "DPRC device bound to driver");
 	return 0;
 
 error_cleanup_open:
+	if (mc_bus->irq_resources)
+		fsl_mc_cleanup_irq_pool(mc_bus);
+
 	(void)dprc_close(mc_dev->mc_io, mc_dev->mc_handle);
 
 error_cleanup_mc_io:
@@ -426,6 +739,16 @@ error_cleanup_mc_io:
 	return error;
 }
 
+/*
+ * Tear down interrupts for a given DPRC object
+ */
+static void dprc_teardown_irqs(struct fsl_mc_device *mc_dev)
+{
+	(void)disable_dprc_irqs(mc_dev);
+	unregister_dprc_irq_handlers(mc_dev);
+	fsl_mc_free_irqs(mc_dev);
+}
+
 /**
  * dprc_remove - callback invoked when a DPRC is being unbound from this driver
  *
@@ -439,18 +762,24 @@ error_cleanup_mc_io:
 static int dprc_remove(struct fsl_mc_device *mc_dev)
 {
 	int error;
+	struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_dev);
 
 	if (WARN_ON(strcmp(mc_dev->obj_desc.type, "dprc") != 0))
 		return -EINVAL;
 	if (WARN_ON(!mc_dev->mc_io))
 		return -EINVAL;
 
+	if (WARN_ON(!mc_bus->irq_resources))
+		return -EINVAL;
+
+	dprc_teardown_irqs(mc_dev);
 	device_for_each_child(&mc_dev->dev, NULL, __fsl_mc_device_remove);
 	dprc_cleanup_all_resource_pools(mc_dev);
 	error = dprc_close(mc_dev->mc_io, mc_dev->mc_handle);
 	if (error < 0)
 		dev_err(&mc_dev->dev, "dprc_close() failed: %d\n", error);
 
+	fsl_mc_cleanup_irq_pool(mc_bus);
 	dev_info(&mc_dev->dev, "DPRC device unbound from driver");
 	return 0;
 }
diff --git a/drivers/staging/fsl-mc/bus/mc-allocator.c b/drivers/staging/fsl-mc/bus/mc-allocator.c
index e36235d..27497e7 100644
--- a/drivers/staging/fsl-mc/bus/mc-allocator.c
+++ b/drivers/staging/fsl-mc/bus/mc-allocator.c
@@ -160,6 +160,7 @@ static const char *const fsl_mc_pool_type_strings[] = {
 	[FSL_MC_POOL_DPMCP] = "dpmcp",
 	[FSL_MC_POOL_DPBP] = "dpbp",
 	[FSL_MC_POOL_DPCON] = "dpcon",
+	[FSL_MC_POOL_IRQ] = "irq",
 };
 
 static int __must_check object_type_to_pool_type(const char *object_type,
@@ -473,6 +474,110 @@ void fsl_mc_object_free(struct fsl_mc_device *mc_adev)
 EXPORT_SYMBOL_GPL(fsl_mc_object_free);
 
 /**
+ * It allocates the IRQs required by a given MC object device. The
+ * IRQs are allocated from the interrupt pool associated with the
+ * MC bus that contains the device, if the device is not a DPRC device.
+ * Otherwise, the IRQs are allocated from the interrupt pool associated
+ * with the MC bus that represents the DPRC device itself.
+ */
+int __must_check fsl_mc_allocate_irqs(struct fsl_mc_device *mc_dev)
+{
+	int i;
+	int irq_count;
+	int res_allocated_count = 0;
+	int error = -EINVAL;
+	struct fsl_mc_device_irq **irqs = NULL;
+	struct fsl_mc_bus *mc_bus;
+	struct fsl_mc_resource_pool *res_pool;
+
+	if (WARN_ON(mc_dev->irqs))
+		goto error;
+
+	irq_count = mc_dev->obj_desc.irq_count;
+	if (WARN_ON(irq_count == 0))
+		goto error;
+
+	if (strcmp(mc_dev->obj_desc.type, "dprc") == 0)
+		mc_bus = to_fsl_mc_bus(mc_dev);
+	else
+		mc_bus = to_fsl_mc_bus(to_fsl_mc_device(mc_dev->dev.parent));
+
+	if (WARN_ON(!mc_bus->irq_resources))
+		goto error;
+
+	res_pool = &mc_bus->resource_pools[FSL_MC_POOL_IRQ];
+	if (res_pool->free_count < irq_count) {
+		dev_err(&mc_dev->dev,
+			"Not able to allocate %u irqs for device\n", irq_count);
+		error = -ENOSPC;
+		goto error;
+	}
+
+	irqs = devm_kzalloc(&mc_dev->dev, irq_count * sizeof(irqs[0]),
+			    GFP_KERNEL);
+	if (!irqs) {
+		error = -ENOMEM;
+		dev_err(&mc_dev->dev, "No memory to allocate irqs[]\n");
+		goto error;
+	}
+
+	for (i = 0; i < irq_count; i++) {
+		struct fsl_mc_resource *resource;
+
+		error = fsl_mc_resource_allocate(mc_bus, FSL_MC_POOL_IRQ,
+						 &resource);
+		if (error < 0)
+			goto error;
+
+		irqs[i] = to_fsl_mc_irq(resource);
+		res_allocated_count++;
+	}
+
+	mc_dev->irqs = irqs;
+	return 0;
+error:
+	for (i = 0; i < res_allocated_count; i++)
+		fsl_mc_resource_free(&irqs[i]->resource);
+
+	if (irqs)
+		devm_kfree(&mc_dev->dev, irqs);
+
+	return error;
+}
+EXPORT_SYMBOL_GPL(fsl_mc_allocate_irqs);
+
+/*
+ * It frees the IRQs that were allocated for a MC object device, by
+ * returning them to the corresponding interrupt pool.
+ */
+void fsl_mc_free_irqs(struct fsl_mc_device *mc_dev)
+{
+	int i;
+	int irq_count;
+	struct fsl_mc_bus *mc_bus;
+
+	if (WARN_ON(!mc_dev->irqs))
+		return;
+
+	irq_count = mc_dev->obj_desc.irq_count;
+
+	if (strcmp(mc_dev->obj_desc.type, "dprc") == 0)
+		mc_bus = to_fsl_mc_bus(mc_dev);
+	else
+		mc_bus = to_fsl_mc_bus(to_fsl_mc_device(mc_dev->dev.parent));
+
+	if (WARN_ON(!mc_bus->irq_resources))
+		return;
+
+	for (i = 0; i < irq_count; i++)
+		fsl_mc_resource_free(&mc_dev->irqs[i]->resource);
+
+	devm_kfree(&mc_dev->dev, mc_dev->irqs);
+	mc_dev->irqs = NULL;
+}
+EXPORT_SYMBOL_GPL(fsl_mc_free_irqs);
+
+/**
  * fsl_mc_allocator_probe - callback invoked when an allocatable device is
  * being added to the system
  */
diff --git a/drivers/staging/fsl-mc/bus/mc-bus.c b/drivers/staging/fsl-mc/bus/mc-bus.c
index 23512d0..7332b26 100644
--- a/drivers/staging/fsl-mc/bus/mc-bus.c
+++ b/drivers/staging/fsl-mc/bus/mc-bus.c
@@ -15,11 +15,27 @@
 #include <linux/of_address.h>
 #include <linux/ioport.h>
 #include <linux/slab.h>
+#include <linux/irqchip/arm-gic-v3.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
 #include <linux/limits.h>
+#include <linux/bitops.h>
 #include "../include/dpmng.h"
 #include "../include/mc-sys.h"
 #include "dprc-cmd.h"
 
+/*
+ * IOMMU stream ID flags
+ */
+#define STREAM_ID_PL_MASK   BIT(9)	    /* privilege level */
+#define STREAM_ID_BMT_MASK  BIT(8)	    /* bypass memory translation */
+#define STREAM_ID_VA_MASK   BIT(7)	    /* virtual address translation
+					     * (two-stage translation) */
+#define STREAM_ID_ICID_MASK (BIT(7) - 1)    /* isolation context ID
+					     * (translation context) */
+
+#define MAX_STREAM_ID_ICID  STREAM_ID_ICID_MASK
+
 static struct kmem_cache *mc_dev_cache;
 
 /**
@@ -295,8 +311,9 @@ static int fsl_mc_device_get_mmio_regions(struct fsl_mc_device *mc_dev,
 					  &regions[i].start);
 		if (error < 0) {
 			dev_err(parent_dev,
-				"Invalid MC address: %#llx\n",
-				region_desc.base_paddr);
+				"Invalid MC address: %#llx (for %s.%d\'s region %d)\n",
+				region_desc.base_paddr,
+				obj_desc->type, obj_desc->id, i);
 			goto error_cleanup_regions;
 		}
 
@@ -352,6 +369,13 @@ int fsl_mc_device_add(struct dprc_obj_desc *obj_desc,
 	mc_dev->obj_desc = *obj_desc;
 	mc_dev->mc_io = mc_io;
 	device_initialize(&mc_dev->dev);
+
+	/*
+	 * FIXME: Enable this code when the GIC-ITS MC support patch is merged
+	 */
+#ifdef GIC_ITS_MC_SUPPORT
+	INIT_LIST_HEAD(&mc_dev->dev.msi_list);
+#endif
 	mc_dev->dev.parent = parent_dev;
 	mc_dev->dev.bus = &fsl_mc_bus_type;
 	dev_set_name(&mc_dev->dev, "%s.%d", obj_desc->type, obj_desc->id);
@@ -482,6 +506,312 @@ void fsl_mc_device_remove(struct fsl_mc_device *mc_dev)
 }
 EXPORT_SYMBOL_GPL(fsl_mc_device_remove);
 
+/*
+ * FIXME: Enable this code when the GIC-ITS MC support patch is merged
+ */
+#ifdef GIC_ITS_MC_SUPPORT
+static int mc_bus_msi_prepare(struct irq_domain *domain, struct device *dev,
+			      int nvec, msi_alloc_info_t *info)
+{
+	int error;
+	u32 its_dev_id;
+	struct dprc_attributes dprc_attr;
+	struct fsl_mc_device *mc_bus_dev = to_fsl_mc_device(dev);
+
+	if (WARN_ON(!(mc_bus_dev->flags & FSL_MC_IS_DPRC)))
+		return -EINVAL;
+
+	error = dprc_get_attributes(mc_bus_dev->mc_io,
+				    mc_bus_dev->mc_handle, &dprc_attr);
+	if (error < 0) {
+		dev_err(&mc_bus_dev->dev,
+			"dprc_get_attributes() failed: %d\n",
+			error);
+		return error;
+	}
+
+	/*
+	 * Build the device Id to be passed to the GIC-ITS:
+	 *
+	 * NOTE: This device id corresponds to the IOMMU stream ID
+	 * associated with the DPRC object.
+	 */
+	its_dev_id = mc_bus_dev->icid;
+	if (its_dev_id > STREAM_ID_ICID_MASK) {
+		dev_err(&mc_bus_dev->dev,
+			"Invalid ICID: %#x\n", its_dev_id);
+		return -ERANGE;
+	}
+
+	if (dprc_attr.options & DPRC_CFG_OPT_IOMMU_BYPASS)
+		its_dev_id |= STREAM_ID_PL_MASK | STREAM_ID_BMT_MASK;
+
+	return __its_msi_prepare(domain->parent, its_dev_id, dev, nvec, info);
+}
+
+static void mc_bus_mask_msi_irq(struct irq_data *d)
+{
+	/* Bus specefic Mask */
+	irq_chip_mask_parent(d);
+}
+
+static void mc_bus_unmask_msi_irq(struct irq_data *d)
+{
+	/* Bus specefic unmask */
+	irq_chip_unmask_parent(d);
+}
+
+static void mc_bus_msi_domain_write_msg(struct irq_data *irq_data,
+					struct msi_msg *msg)
+{
+	struct msi_desc *msi_entry = irq_data->msi_desc;
+	struct fsl_mc_device *mc_bus_dev = to_fsl_mc_device(msi_entry->dev);
+	struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_bus_dev);
+	struct fsl_mc_device_irq *irq_res =
+		&mc_bus->irq_resources[msi_entry->msi_attrib.entry_nr];
+
+	if (irq_res->irq_number == irq_data->irq) {
+		/*
+		 * write msg->address_hi/lo to irq_resource
+		 */
+		irq_res->msi_paddr =
+			((u64)msg->address_hi << 32) | msg->address_lo;
+		irq_res->msi_value = msg->data;
+	}
+}
+
+static struct irq_chip mc_bus_msi_irq_chip = {
+	.name			= "fsl-mc-bus-msi",
+	.irq_unmask		= mc_bus_unmask_msi_irq,
+	.irq_mask		= mc_bus_mask_msi_irq,
+	.irq_eoi		= irq_chip_eoi_parent,
+	.irq_write_msi_msg	= mc_bus_msi_domain_write_msg,
+};
+
+static struct msi_domain_ops mc_bus_msi_ops = {
+	.msi_prepare	= mc_bus_msi_prepare,
+};
+
+static struct msi_domain_info mc_bus_msi_domain_info = {
+	.flags	= (MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS |
+		   MSI_FLAG_MULTI_PCI_MSI | MSI_FLAG_PCI_MSIX),
+	.ops	= &mc_bus_msi_ops,
+	.chip	= &mc_bus_msi_irq_chip,
+};
+
+static int create_mc_irq_domain(struct platform_device *mc_pdev,
+				struct irq_domain **new_irq_domain)
+{
+	int error;
+	struct device_node *its_of_node;
+	struct irq_domain *its_domain;
+	struct irq_domain *irq_domain;
+	struct device_node *mc_of_node = mc_pdev->dev.of_node;
+
+	its_of_node = of_parse_phandle(mc_of_node, "lpi-parent", 0);
+	if (!its_of_node) {
+		dev_err(&mc_pdev->dev,
+			"lpi-parent phandle missing for %s\n",
+			mc_of_node->full_name);
+		return -ENOENT;
+	}
+
+	/*
+	 * Extract MSI parent node:
+	 */
+	its_domain = irq_find_host(its_of_node);
+	if (!its_domain) {
+		dev_err(&mc_pdev->dev, "Unable to find parent domain\n");
+		error = -ENOENT;
+		goto cleanup_its_of_node;
+	}
+
+	/*
+	 * FIXME: Enable this code when the GIC-ITS MC support patch is merged
+	 */
+#ifdef GIC_ITS_MC_SUPPORT
+	irq_domain = msi_create_irq_domain(mc_of_node, &mc_bus_msi_domain_info,
+					   its_domain->parent);
+	if (!irq_domain) {
+		dev_err(&mc_pdev->dev, "Failed to allocate msi_domain\n");
+		error = -ENOMEM;
+		goto cleanup_its_of_node;
+	}
+
+	dev_dbg(&mc_pdev->dev, "Allocated MSI domain\n");
+#else
+	irq_domain = NULL;
+#endif
+	*new_irq_domain = irq_domain;
+	return 0;
+
+cleanup_its_of_node:
+	of_node_put(its_of_node);
+	return error;
+}
+#endif /* GIC_ITS_MC_SUPPORT */
+
+/*
+ * Initialize the interrupt pool associated with a MC bus.
+ * It allocates a block of IRQs from the GIC-ITS
+ */
+int __must_check fsl_mc_populate_irq_pool(struct fsl_mc_bus *mc_bus,
+					  unsigned int irq_count)
+{
+	unsigned int i;
+	struct fsl_mc_device_irq *irq_resources;
+	struct fsl_mc_device_irq *irq_res;
+	struct fsl_mc_device *mc_bus_dev = &mc_bus->mc_dev;
+	struct fsl_mc_resource_pool *res_pool =
+			&mc_bus->resource_pools[FSL_MC_POOL_IRQ];
+	/*
+	 * FIXME: Enable this code when the GIC-ITS MC support patch is merged
+	 */
+#ifdef GIC_ITS_MC_SUPPORT
+	int error;
+	struct msi_desc *msi_entry;
+	struct msi_desc *next_msi_entry;
+	struct fsl_mc *mc = dev_get_drvdata(fsl_mc_bus_type.dev_root->parent);
+
+	/*
+	 * Detect duplicate invocations of this function:
+	 */
+	if (WARN_ON(!list_empty(&mc_bus_dev->dev.msi_list)))
+		return -EINVAL;
+#endif
+
+	if (WARN_ON(irq_count == 0 ||
+		    irq_count > FSL_MC_IRQ_POOL_MAX_TOTAL_IRQS))
+		return -EINVAL;
+
+	irq_resources =
+		devm_kzalloc(&mc_bus_dev->dev,
+			     sizeof(*irq_resources) * irq_count,
+			     GFP_KERNEL);
+	if (!irq_resources)
+		return -ENOMEM;
+
+	for (i = 0; i < irq_count; i++) {
+		irq_res = &irq_resources[i];
+	/*
+	 * FIXME: Enable this code when the GIC-ITS MC support patch is merged
+	 */
+#ifdef GIC_ITS_MC_SUPPORT
+		msi_entry = alloc_msi_entry(&mc_bus_dev->dev);
+		if (!msi_entry) {
+			dev_err(&mc_bus_dev->dev, "Failed to allocate msi entry\n");
+			error = -ENOMEM;
+			goto cleanup_msi_entries;
+		}
+
+		msi_entry->msi_attrib.is_msix = 1;
+		msi_entry->msi_attrib.is_64 = 1;
+		msi_entry->msi_attrib.entry_nr = i;
+		msi_entry->nvec_used = 1;
+		list_add_tail(&msi_entry->list, &mc_bus_dev->dev.msi_list);
+#endif
+
+		/*
+		 * NOTE: irq_res->msi_paddr will be set by the
+		 * mc_bus_msi_domain_write_msg() callback
+		 */
+		irq_res->resource.type = res_pool->type;
+		irq_res->resource.data = irq_res;
+		irq_res->resource.parent_pool = res_pool;
+		INIT_LIST_HEAD(&irq_res->resource.node);
+		list_add_tail(&irq_res->resource.node, &res_pool->free_list);
+	}
+
+	/*
+	 * FIXME: Enable this code when the GIC-ITS MC support patch is merged
+	 */
+#ifdef GIC_ITS_MC_SUPPORT
+	/*
+	 * NOTE: Calling this function will trigger the invocation of the
+	 * mc_bus_msi_prepare() callback
+	 */
+	error = msi_domain_alloc_irqs(mc->irq_domain,
+				      &mc_bus_dev->dev, irq_count);
+
+	if (error) {
+		dev_err(&mc_bus_dev->dev, "Failed to allocate IRQs\n");
+		goto cleanup_msi_entries;
+	}
+#endif
+
+	/*
+	 * FIXME: Enable this code when the GIC-ITS MC support patch is merged
+	 */
+#ifdef GIC_ITS_MC_SUPPORT
+	for_each_msi_entry(msi_entry, &mc_bus_dev->dev) {
+		u32 irq_num = msi_entry->irq;
+
+		irq_res = &irq_resources[msi_entry->msi_attrib.entry_nr];
+		irq_res->irq_number = irq_num;
+		irq_res->resource.id = irq_num;
+	}
+#endif
+
+	res_pool->max_count = irq_count;
+	res_pool->free_count = irq_count;
+	mc_bus->irq_resources = irq_resources;
+	return 0;
+
+	/*
+	 * FIXME: Enable this code when the GIC-ITS MC support patch is merged
+	 */
+#ifdef GIC_ITS_MC_SUPPORT
+cleanup_msi_entries:
+	list_for_each_entry_safe(msi_entry, next_msi_entry,
+				 &mc_bus_dev->dev.msi_list, list)
+		kfree(msi_entry);
+
+	devm_kfree(&mc_bus_dev->dev, irq_resources);
+	return error;
+#endif
+}
+EXPORT_SYMBOL_GPL(fsl_mc_populate_irq_pool);
+
+/**
+ * Teardown the interrupt pool associated with an MC bus.
+ * It frees the IRQs that were allocated to the pool, back to the GIC-ITS.
+ */
+void fsl_mc_cleanup_irq_pool(struct fsl_mc_bus *mc_bus)
+{
+	/*
+	 * FIXME: Enable this code when the GIC-ITS MC support patch is merged
+	 */
+#ifdef GIC_ITS_MC_SUPPORT
+	struct msi_desc *msi_entry;
+	struct msi_desc *next_msi_entry;
+	struct fsl_mc *mc = dev_get_drvdata(fsl_mc_bus_type.dev_root->parent);
+#endif
+	struct fsl_mc_resource_pool *res_pool =
+			&mc_bus->resource_pools[FSL_MC_POOL_IRQ];
+
+	if (WARN_ON(res_pool->max_count == 0))
+		return;
+
+	if (WARN_ON(res_pool->free_count != res_pool->max_count))
+		return;
+
+	/*
+	 * FIXME: Enable this code when the GIC-ITS MC support patch is merged
+	 */
+#ifdef GIC_ITS_MC_SUPPORT
+	msi_domain_free_irqs(mc->irq_domain, &mc_bus->mc_dev.dev);
+	list_for_each_entry_safe(msi_entry, next_msi_entry,
+				 &mc_bus->mc_dev.dev.msi_list, list)
+		kfree(msi_entry);
+#endif
+
+	devm_kfree(&mc_bus->mc_dev.dev, mc_bus->irq_resources);
+	res_pool->max_count = 0;
+	res_pool->free_count = 0;
+	mc_bus->irq_resources = NULL;
+}
+EXPORT_SYMBOL_GPL(fsl_mc_cleanup_irq_pool);
+
 static int parse_mc_ranges(struct device *dev,
 			   int *paddr_cells,
 			   int *mc_addr_cells,
@@ -613,6 +943,15 @@ static int fsl_mc_bus_probe(struct platform_device *pdev)
 	platform_set_drvdata(pdev, mc);
 
 	/*
+	 * FIXME: Enable this code when the GIC-ITS MC support patch is merged
+	 */
+#ifdef GIC_ITS_MC_SUPPORT
+	error = create_mc_irq_domain(pdev, &mc->irq_domain);
+	if (error < 0)
+		return error;
+#endif
+
+	/*
 	 * Get physical address of MC portal for the root DPRC:
 	 */
 	error = of_address_to_resource(pdev->dev.of_node, 0, &res);
@@ -620,7 +959,7 @@ static int fsl_mc_bus_probe(struct platform_device *pdev)
 		dev_err(&pdev->dev,
 			"of_address_to_resource() failed for %s\n",
 			pdev->dev.of_node->full_name);
-		return error;
+		goto error_cleanup_irq_domain;
 	}
 
 	mc_portal_phys_addr = res.start;
@@ -628,7 +967,7 @@ static int fsl_mc_bus_probe(struct platform_device *pdev)
 	error = fsl_create_mc_io(&pdev->dev, mc_portal_phys_addr,
 				 mc_portal_size, NULL, 0, &mc_io);
 	if (error < 0)
-		return error;
+		goto error_cleanup_irq_domain;
 
 	error = mc_get_version(mc_io, &mc_version);
 	if (error != 0) {
@@ -673,6 +1012,7 @@ static int fsl_mc_bus_probe(struct platform_device *pdev)
 	obj_desc.id = container_id;
 	obj_desc.ver_major = DPRC_VER_MAJOR;
 	obj_desc.ver_minor = DPRC_VER_MINOR;
+	obj_desc.irq_count = 1;
 	obj_desc.region_count = 0;
 
 	error = fsl_mc_device_add(&obj_desc, mc_io, &pdev->dev, &mc_bus_dev);
@@ -684,6 +1024,9 @@ static int fsl_mc_bus_probe(struct platform_device *pdev)
 
 error_cleanup_mc_io:
 	fsl_destroy_mc_io(mc_io);
+
+error_cleanup_irq_domain:
+	irq_domain_remove(mc->irq_domain);
 	return error;
 }
 
@@ -698,6 +1041,7 @@ static int fsl_mc_bus_remove(struct platform_device *pdev)
 	if (WARN_ON(&mc->root_mc_bus_dev->dev != fsl_mc_bus_type.dev_root))
 		return -EINVAL;
 
+	irq_domain_remove(mc->irq_domain);
 	fsl_mc_device_remove(mc->root_mc_bus_dev);
 	dev_info(&pdev->dev, "Root MC bus device removed");
 	return 0;
diff --git a/drivers/staging/fsl-mc/include/mc-private.h b/drivers/staging/fsl-mc/include/mc-private.h
index c045f49..6e33942 100644
--- a/drivers/staging/fsl-mc/include/mc-private.h
+++ b/drivers/staging/fsl-mc/include/mc-private.h
@@ -27,12 +27,26 @@
 	 strcmp(_obj_type, "dpcon") == 0)
 
 /**
+ * Maximum number of total IRQs that can be pre-allocated for an MC bus'
+ * IRQ pool
+ */
+#define FSL_MC_IRQ_POOL_MAX_TOTAL_IRQS	256
+
+/**
+ * Maximum number of extra IRQs pre-reallocated for an MC bus' IRQ pool,
+ * to be used by dynamically created MC objects
+ */
+#define FSL_MC_IRQ_POOL_MAX_EXTRA_IRQS	64
+
+/**
  * struct fsl_mc - Private data of a "fsl,qoriq-mc" platform device
  * @root_mc_bus_dev: MC object device representing the root DPRC
+ * @irq_domain: IRQ domain for the fsl-mc bus type
  * @addr_translation_ranges: array of bus to system address translation ranges
  */
 struct fsl_mc {
 	struct fsl_mc_device *root_mc_bus_dev;
+	struct irq_domain *irq_domain;
 	uint8_t num_translation_ranges;
 	struct fsl_mc_addr_translation_range *translation_ranges;
 };
@@ -76,11 +90,13 @@ struct fsl_mc_resource_pool {
  * @resource_pools: array of resource pools (one pool per resource type)
  * for this MC bus. These resources represent allocatable entities
  * from the physical DPRC.
+ * @irq_resources: Pointer to array of IRQ objects for the IRQ pool.
  * @scan_mutex: Serializes bus scanning
  */
 struct fsl_mc_bus {
 	struct fsl_mc_device mc_dev;
 	struct fsl_mc_resource_pool resource_pools[FSL_MC_NUM_POOL_TYPES];
+	struct fsl_mc_device_irq *irq_resources;
 	struct mutex scan_mutex;    /* serializes bus scanning */
 };
 
@@ -94,9 +110,8 @@ int __must_check fsl_mc_device_add(struct dprc_obj_desc *obj_desc,
 
 void fsl_mc_device_remove(struct fsl_mc_device *mc_dev);
 
-int dprc_scan_container(struct fsl_mc_device *mc_bus_dev);
-
-int dprc_scan_objects(struct fsl_mc_device *mc_bus_dev);
+int dprc_scan_objects(struct fsl_mc_device *mc_bus_dev,
+		      unsigned int *total_irq_count);
 
 int __init dprc_driver_init(void);
 
@@ -113,4 +128,9 @@ int __must_check fsl_mc_resource_allocate(struct fsl_mc_bus *mc_bus,
 
 void fsl_mc_resource_free(struct fsl_mc_resource *resource);
 
+int __must_check fsl_mc_populate_irq_pool(struct fsl_mc_bus *mc_bus,
+					  unsigned int irq_count);
+
+void fsl_mc_cleanup_irq_pool(struct fsl_mc_bus *mc_bus);
+
 #endif /* _FSL_MC_PRIVATE_H_ */
diff --git a/drivers/staging/fsl-mc/include/mc.h b/drivers/staging/fsl-mc/include/mc.h
index fa02ef0..531cb8c 100644
--- a/drivers/staging/fsl-mc/include/mc.h
+++ b/drivers/staging/fsl-mc/include/mc.h
@@ -14,6 +14,7 @@
 #include <linux/device.h>
 #include <linux/mod_devicetable.h>
 #include <linux/list.h>
+#include <linux/interrupt.h>
 #include "../include/dprc.h"
 
 #define FSL_MC_VENDOR_FREESCALE	0x1957
@@ -75,6 +76,7 @@ enum fsl_mc_pool_type {
 	FSL_MC_POOL_DPMCP = 0x0,    /* corresponds to "dpmcp" in the MC */
 	FSL_MC_POOL_DPBP,	    /* corresponds to "dpbp" in the MC */
 	FSL_MC_POOL_DPCON,	    /* corresponds to "dpcon" in the MC */
+	FSL_MC_POOL_IRQ,
 
 	/*
 	 * NOTE: New resource pool types must be added before this entry
@@ -104,6 +106,23 @@ struct fsl_mc_resource {
 };
 
 /**
+ * struct fsl_mc_device_irq - MC object device message-based interrupt
+ * @msi_paddr: message-based interrupt physical address
+ * @msi_value: message-based interrupt data value
+ * @irq_number: Linux IRQ number assigned to the interrupt
+ * @resource: MC generic resource associated with the interrupt
+ */
+struct fsl_mc_device_irq {
+	phys_addr_t msi_paddr;
+	uint32_t msi_value;
+	uint32_t irq_number;
+	struct fsl_mc_resource resource;
+};
+
+#define to_fsl_mc_irq(_mc_resource) \
+	container_of(_mc_resource, struct fsl_mc_device_irq, resource)
+
+/**
  * Bit masks for a MC object device (struct fsl_mc_device) flags
  */
 #define FSL_MC_IS_DPRC	0x0001
@@ -124,6 +143,7 @@ struct fsl_mc_resource {
  * NULL if none.
  * @obj_desc: MC description of the DPAA device
  * @regions: pointer to array of MMIO region entries
+ * @irqs: pointer to array of pointers to interrupts allocated to this device
  * @resource: generic resource associated with this MC object device, if any.
  *
  * Generic device object for MC object devices that are "attached" to a
@@ -155,6 +175,7 @@ struct fsl_mc_device {
 	struct fsl_mc_io *mc_io;
 	struct dprc_obj_desc obj_desc;
 	struct resource *regions;
+	struct fsl_mc_device_irq **irqs;
 	struct fsl_mc_resource *resource;
 };
 
@@ -196,6 +217,10 @@ int __must_check fsl_mc_object_allocate(struct fsl_mc_device *mc_dev,
 
 void fsl_mc_object_free(struct fsl_mc_device *mc_adev);
 
+int __must_check fsl_mc_allocate_irqs(struct fsl_mc_device *mc_dev);
+
+void fsl_mc_free_irqs(struct fsl_mc_device *mc_dev);
+
 extern struct bus_type fsl_mc_bus_type;
 
 #endif /* _FSL_MC_H_ */
-- 
2.3.3



More information about the devel mailing list