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@xxxxxxxxxxxxx> Change-Id: I2a986c465989c3811de19cfe9ed0b77168250cb1 Reviewed-on: http://git.am.freescale.net:8181/33626 Tested-by: Review Code-CDREVIEW <CDREVIEW@xxxxxxxxxxxxx> Reviewed-by: Stuart Yoder <stuart.yoder@xxxxxxxxxxxxx> --- 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@xxxxxxxxxxxxxxxxxxx>, german.rivera@xxxxxxxxxxxxx, devel@xxxxxxxxxxxxxxxxxxxx, linux-kernel@xxxxxxxxxxxxxxx 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, ®ions[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 _______________________________________________ devel mailing list devel@xxxxxxxxxxxxxxxxxxxxxx http://driverdev.linuxdriverproject.org/mailman/listinfo/driverdev-devel