/** * fsl_mc_allocator_probe - callback invoked when an allocatable device is * being added to the system */ static int fsl_mc_allocator_probe(struct fsl_mc_device *mc_dev) { enum fsl_mc_pool_type pool_type; struct fsl_mc_device *mc_bus_dev; struct fsl_mc_bus *mc_bus; int error = -EINVAL; if (WARN_ON(!FSL_MC_IS_ALLOCATABLE(mc_dev->obj_desc.type))) goto error; mc_bus_dev = to_fsl_mc_device(mc_dev->dev.parent); if (WARN_ON(mc_bus_dev->dev.bus != &fsl_mc_bus_type)) goto error; mc_bus = to_fsl_mc_bus(mc_bus_dev); error = object_type_to_pool_type(mc_dev->obj_desc.type, &pool_type); if (error < 0) goto error; error = fsl_mc_resource_pool_add_device(mc_bus, pool_type, mc_dev); if (error < 0) goto error; dev_info(&mc_dev->dev, "Allocatable MC object device bound to fsl_mc_allocator driver"); return 0; error: return error; }
/** * fsl_mc_device_remove - Remove a MC object device from being visible to * Linux * * @mc_dev: Pointer to a MC object device object */ void fsl_mc_device_remove(struct fsl_mc_device *mc_dev) { struct fsl_mc_bus *mc_bus = NULL; kfree(mc_dev->regions); /* * The device-specific remove callback will get invoked by device_del() */ device_del(&mc_dev->dev); put_device(&mc_dev->dev); if (strcmp(mc_dev->obj_desc.type, "dprc") == 0) { mc_bus = to_fsl_mc_bus(mc_dev); if (mc_dev->mc_io) { fsl_destroy_mc_io(mc_dev->mc_io); mc_dev->mc_io = NULL; } if (fsl_mc_is_root_dprc(&mc_dev->dev)) { if (atomic_read(&root_dprc_count) > 0) atomic_dec(&root_dprc_count); else WARN_ON(1); } } if (mc_bus) devm_kfree(mc_dev->dev.parent, mc_bus); else kmem_cache_free(mc_dev_cache, mc_dev); }
/** * fsl_mc_resource_pool_remove_device - remove an allocatable device from a * resource pool * * @mc_dev: Pointer to allocatable MC object device * * It permanently removes an allocatable MC object device from the resource * pool, the device is currently in, as long as it is in the pool's free list. */ static int __must_check fsl_mc_resource_pool_remove_device(struct fsl_mc_device *mc_dev) { struct fsl_mc_device *mc_bus_dev; struct fsl_mc_bus *mc_bus; struct fsl_mc_resource_pool *res_pool; struct fsl_mc_resource *resource; int error = -EINVAL; bool mutex_locked = false; if (WARN_ON(!FSL_MC_IS_ALLOCATABLE(mc_dev->obj_desc.type))) goto out; resource = mc_dev->resource; if (WARN_ON(resource->data != mc_dev)) goto out; mc_bus_dev = to_fsl_mc_device(mc_dev->dev.parent); mc_bus = to_fsl_mc_bus(mc_bus_dev); res_pool = resource->parent_pool; if (WARN_ON(res_pool != &mc_bus->resource_pools[resource->type])) goto out; mutex_lock(&res_pool->mutex); mutex_locked = true; if (WARN_ON(res_pool->max_count <= 0)) goto out; if (WARN_ON(res_pool->free_count <= 0 || res_pool->free_count > res_pool->max_count)) goto out; /* * If the device is currently allocated, its resource is not * in the free list and thus, the device cannot be removed. */ if (list_empty(&resource->node)) { error = -EBUSY; dev_err(&mc_bus_dev->dev, "Device %s cannot be removed from resource pool\n", dev_name(&mc_dev->dev)); goto out; } list_del(&resource->node); INIT_LIST_HEAD(&resource->node); res_pool->free_count--; res_pool->max_count--; devm_kfree(&mc_bus_dev->dev, resource); mc_dev->resource = NULL; error = 0; out: if (mutex_locked) mutex_unlock(&res_pool->mutex); return error; }
/** * fsl_mc_portal_allocate - Allocates an MC portal * * @mc_dev: MC device for which the MC portal is to be allocated * @mc_io_flags: Flags for the fsl_mc_io object that wraps the allocated * MC portal. * @new_mc_io: Pointer to area where the pointer to the fsl_mc_io object * that wraps the allocated MC portal is to be returned * * This function allocates an MC portal from the device's parent DPRC, * from the corresponding MC bus' pool of MC portals and wraps * it in a new fsl_mc_io object. If 'mc_dev' is a DPRC itself, the * portal is allocated from its own MC bus. */ int __must_check fsl_mc_portal_allocate(struct fsl_mc_device *mc_dev, uint16_t mc_io_flags, struct fsl_mc_io **new_mc_io) { struct fsl_mc_device *mc_bus_dev; struct fsl_mc_bus *mc_bus; phys_addr_t mc_portal_phys_addr; size_t mc_portal_size; struct fsl_mc_device *mc_adev; int error = -EINVAL; struct fsl_mc_resource *resource = NULL; struct fsl_mc_io *mc_io = NULL; if (mc_dev->flags & FSL_MC_IS_DPRC) { mc_bus_dev = mc_dev; } else { if (WARN_ON(mc_dev->dev.parent->bus != &fsl_mc_bus_type)) return error; mc_bus_dev = to_fsl_mc_device(mc_dev->dev.parent); } mc_bus = to_fsl_mc_bus(mc_bus_dev); *new_mc_io = NULL; error = fsl_mc_resource_allocate(mc_bus, FSL_MC_POOL_DPMCP, &resource); if (error < 0) return error; mc_adev = resource->data; if (WARN_ON(!mc_adev)) goto error_cleanup_resource; if (WARN_ON(mc_adev->obj_desc.region_count == 0)) goto error_cleanup_resource; mc_portal_phys_addr = mc_adev->regions[0].start; mc_portal_size = mc_adev->regions[0].end - mc_adev->regions[0].start + 1; if (WARN_ON(mc_portal_size != mc_bus_dev->mc_io->portal_size)) goto error_cleanup_resource; error = fsl_create_mc_io(&mc_bus_dev->dev, mc_portal_phys_addr, mc_portal_size, resource, mc_io_flags, &mc_io); if (error < 0) goto error_cleanup_resource; *new_mc_io = mc_io; return 0; error_cleanup_resource: fsl_mc_resource_free(resource); return error; }
/* * NOTE: This function is invoked with interrupts disabled */ static void fsl_mc_msi_write_msg(struct irq_data *irq_data, struct msi_msg *msg) { struct msi_desc *msi_desc = irq_data_get_msi_desc(irq_data); struct fsl_mc_device *mc_bus_dev = to_fsl_mc_device(msi_desc->dev); struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_bus_dev); struct fsl_mc_device_irq *mc_dev_irq = &mc_bus->irq_resources[msi_desc->fsl_mc.msi_index]; msi_desc->msg = *msg; /* * Program the MSI (paddr, value) pair in the device: */ __fsl_mc_msi_write_msg(mc_bus_dev, mc_dev_irq); }
/** * fsl_mc_object_allocate - Allocates a MC object device of the given * pool type from a given MC bus * * @mc_dev: MC device for which the MC object device is to be allocated * @pool_type: MC bus resource pool type * @new_mc_dev: Pointer to area where the pointer to the allocated * MC object device is to be returned * * This function allocates a MC object device from the device's parent DPRC, * from the corresponding MC bus' pool of allocatable MC object devices of * the given resource type. mc_dev cannot be a DPRC itself. * * NOTE: pool_type must be different from FSL_MC_POOL_MCP, since MC * portals are allocated using fsl_mc_portal_allocate(), instead of * this function. */ int __must_check fsl_mc_object_allocate(struct fsl_mc_device *mc_dev, enum fsl_mc_pool_type pool_type, struct fsl_mc_device **new_mc_adev) { struct fsl_mc_device *mc_bus_dev; struct fsl_mc_bus *mc_bus; struct fsl_mc_device *mc_adev; int error = -EINVAL; struct fsl_mc_resource *resource = NULL; *new_mc_adev = NULL; if (WARN_ON(mc_dev->flags & FSL_MC_IS_DPRC)) goto error; if (WARN_ON(mc_dev->dev.parent->bus != &fsl_mc_bus_type)) goto error; if (WARN_ON(pool_type == FSL_MC_POOL_DPMCP)) goto error; mc_bus_dev = to_fsl_mc_device(mc_dev->dev.parent); mc_bus = to_fsl_mc_bus(mc_bus_dev); error = fsl_mc_resource_allocate(mc_bus, pool_type, &resource); if (error < 0) goto error; mc_adev = resource->data; if (WARN_ON(!mc_adev)) goto error; *new_mc_adev = mc_adev; return 0; error: if (resource) fsl_mc_resource_free(resource); return error; }
/** * dprc_remove - callback invoked when a DPRC is being unbound from this driver * * @mc_dev: Pointer to fsl-mc device representing the DPRC * * It removes the DPRC's child objects from Linux (not from the MC) and * closes the DPRC device in the MC. * It tears down the interrupts that were configured for the DPRC device. * It destroys the interrupt pool associated with this MC bus. */ 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; if (dev_get_msi_domain(&mc_dev->dev)) dprc_teardown_irq(mc_dev); device_for_each_child(&mc_dev->dev, NULL, __fsl_mc_device_remove); if (dev_get_msi_domain(&mc_dev->dev)) { fsl_mc_cleanup_irq_pool(mc_bus); dev_set_msi_domain(&mc_dev->dev, NULL); } fsl_mc_cleanup_all_resource_pools(mc_dev); error = dprc_close(mc_dev->mc_io, 0, mc_dev->mc_handle); if (error < 0) dev_err(&mc_dev->dev, "dprc_close() failed: %d\n", error); if (!fsl_mc_is_root_dprc(&mc_dev->dev)) { fsl_destroy_mc_io(mc_dev->mc_io); mc_dev->mc_io = NULL; } dev_info(&mc_dev->dev, "DPRC device unbound from driver"); return 0; }
/** * dprc_scan_container - Scans a physical DPRC and synchronizes Linux bus state * * @mc_bus_dev: pointer to the fsl-mc device that represents a DPRC object * * Scans the physical DPRC and synchronizes the state of the Linux * 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) { int error; unsigned int irq_count; struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_bus_dev); fsl_mc_init_all_resource_pools(mc_bus_dev); /* * Discover objects in the DPRC: */ mutex_lock(&mc_bus->scan_mutex); error = dprc_scan_objects(mc_bus_dev, &irq_count); mutex_unlock(&mc_bus->scan_mutex); if (error < 0) goto error; if (dev_get_msi_domain(&mc_bus_dev->dev) && !mc_bus->irq_resources) { if (irq_count > FSL_MC_IRQ_POOL_MAX_TOTAL_IRQS) { dev_warn(&mc_bus_dev->dev, "IRQs needed (%u) exceed IRQs preallocated (%u)\n", irq_count, FSL_MC_IRQ_POOL_MAX_TOTAL_IRQS); } error = fsl_mc_populate_irq_pool( mc_bus, FSL_MC_IRQ_POOL_MAX_TOTAL_IRQS); if (error < 0) goto error; } return 0; error: fsl_mc_cleanup_all_resource_pools(mc_bus_dev); return error; }
/** * dprc_probe - callback invoked when a DPRC is being bound to this driver * * @mc_dev: Pointer to fsl-mc device representing a DPRC * * It opens the physical DPRC in the MC. * It scans the DPRC to discover the MC objects contained in it. * It creates the interrupt pool for the MC bus associated with the DPRC. * It configures the interrupts for the DPRC device itself. */ static int dprc_probe(struct fsl_mc_device *mc_dev) { int error; size_t region_size; struct device *parent_dev = mc_dev->dev.parent; struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_dev); bool mc_io_created = false; bool msi_domain_set = false; if (WARN_ON(strcmp(mc_dev->obj_desc.type, "dprc") != 0)) return -EINVAL; if (WARN_ON(dev_get_msi_domain(&mc_dev->dev))) return -EINVAL; if (!mc_dev->mc_io) { /* * This is a child DPRC: */ if (WARN_ON(!dev_is_fsl_mc(parent_dev))) return -EINVAL; if (WARN_ON(mc_dev->obj_desc.region_count == 0)) return -EINVAL; region_size = mc_dev->regions[0].end - mc_dev->regions[0].start + 1; error = fsl_create_mc_io(&mc_dev->dev, mc_dev->regions[0].start, region_size, NULL, FSL_MC_IO_ATOMIC_CONTEXT_PORTAL, &mc_dev->mc_io); if (error < 0) return error; mc_io_created = true; /* * Inherit parent MSI domain: */ dev_set_msi_domain(&mc_dev->dev, dev_get_msi_domain(parent_dev)); msi_domain_set = true; } else { /* * This is a root DPRC */ struct irq_domain *mc_msi_domain; if (WARN_ON(dev_is_fsl_mc(parent_dev))) return -EINVAL; error = fsl_mc_find_msi_domain(parent_dev, &mc_msi_domain); if (error < 0) { dev_warn(&mc_dev->dev, "WARNING: MC bus without interrupt support\n"); } else { dev_set_msi_domain(&mc_dev->dev, mc_msi_domain); msi_domain_set = true; } } error = dprc_open(mc_dev->mc_io, 0, mc_dev->obj_desc.id, &mc_dev->mc_handle); if (error < 0) { dev_err(&mc_dev->dev, "dprc_open() failed: %d\n", error); goto error_cleanup_msi_domain; } error = dprc_get_attributes(mc_dev->mc_io, 0, mc_dev->mc_handle, &mc_bus->dprc_attr); if (error < 0) { dev_err(&mc_dev->dev, "dprc_get_attributes() failed: %d\n", error); goto error_cleanup_open; } if (mc_bus->dprc_attr.version.major < DPRC_MIN_VER_MAJOR || (mc_bus->dprc_attr.version.major == DPRC_MIN_VER_MAJOR && mc_bus->dprc_attr.version.minor < DPRC_MIN_VER_MINOR)) { dev_err(&mc_dev->dev, "ERROR: DPRC version %d.%d not supported\n", mc_bus->dprc_attr.version.major, mc_bus->dprc_attr.version.minor); error = -ENOTSUPP; goto error_cleanup_open; } mutex_init(&mc_bus->scan_mutex); /* * Discover MC objects in DPRC object: */ error = dprc_scan_container(mc_dev); if (error < 0) goto error_cleanup_open; /* * Configure interrupt for the DPRC object associated with this MC bus: */ error = dprc_setup_irq(mc_dev); if (error < 0) goto error_cleanup_open; dev_info(&mc_dev->dev, "DPRC device bound to driver"); return 0; error_cleanup_open: (void)dprc_close(mc_dev->mc_io, 0, mc_dev->mc_handle); error_cleanup_msi_domain: if (msi_domain_set) dev_set_msi_domain(&mc_dev->dev, NULL); if (mc_io_created) { fsl_destroy_mc_io(mc_dev->mc_io); mc_dev->mc_io = NULL; } return error; }
/** * 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; u32 status; struct device *dev = 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; struct msi_desc *msi_desc = mc_dev->irqs[0]->msi_desc; dev_dbg(dev, "DPRC IRQ %d triggered on CPU %u\n", irq_num, smp_processor_id()); if (WARN_ON(!(mc_dev->flags & FSL_MC_IS_DPRC))) return IRQ_HANDLED; mutex_lock(&mc_bus->scan_mutex); if (WARN_ON(!msi_desc || msi_desc->irq != (u32)irq_num)) goto out; status = 0; error = dprc_get_irq_status(mc_io, 0, mc_dev->mc_handle, 0, &status); if (error < 0) { dev_err(dev, "dprc_get_irq_status() failed: %d\n", error); goto out; } error = dprc_clear_irq_status(mc_io, 0, mc_dev->mc_handle, 0, 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) { /* * If the error is -ENXIO, we ignore it, as it indicates * that the object scan was aborted, as we detected that * an object was removed from the DPRC in the MC, while * we were scanning the DPRC. */ if (error != -ENXIO) { dev_err(dev, "dprc_scan_objects() failed: %d\n", error); } goto out; } if (irq_count > FSL_MC_IRQ_POOL_MAX_TOTAL_IRQS) { dev_warn(dev, "IRQs needed (%u) exceed IRQs preallocated (%u)\n", irq_count, FSL_MC_IRQ_POOL_MAX_TOTAL_IRQS); } } out: mutex_unlock(&mc_bus->scan_mutex); return IRQ_HANDLED; }
/** * fsl_mc_portal_allocate - Allocates an MC portal * * @mc_dev: MC device for which the MC portal is to be allocated * @mc_io_flags: Flags for the fsl_mc_io object that wraps the allocated * MC portal. * @new_mc_io: Pointer to area where the pointer to the fsl_mc_io object * that wraps the allocated MC portal is to be returned * * This function allocates an MC portal from the device's parent DPRC, * from the corresponding MC bus' pool of MC portals and wraps * it in a new fsl_mc_io object. If 'mc_dev' is a DPRC itself, the * portal is allocated from its own MC bus. */ int __must_check fsl_mc_portal_allocate(struct fsl_mc_device *mc_dev, u16 mc_io_flags, struct fsl_mc_io **new_mc_io) { struct fsl_mc_device *mc_bus_dev; struct fsl_mc_bus *mc_bus; phys_addr_t mc_portal_phys_addr; size_t mc_portal_size; struct fsl_mc_device *dpmcp_dev; int error = -EINVAL; struct fsl_mc_resource *resource = NULL; struct fsl_mc_io *mc_io = NULL; if (mc_dev->flags & FSL_MC_IS_DPRC) { mc_bus_dev = mc_dev; } else { if (WARN_ON(!dev_is_fsl_mc(mc_dev->dev.parent))) return error; mc_bus_dev = to_fsl_mc_device(mc_dev->dev.parent); } mc_bus = to_fsl_mc_bus(mc_bus_dev); *new_mc_io = NULL; error = fsl_mc_resource_allocate(mc_bus, FSL_MC_POOL_DPMCP, &resource); if (error < 0) return error; error = -EINVAL; dpmcp_dev = resource->data; if (WARN_ON(!dpmcp_dev)) goto error_cleanup_resource; if (dpmcp_dev->obj_desc.ver_major < DPMCP_MIN_VER_MAJOR || (dpmcp_dev->obj_desc.ver_major == DPMCP_MIN_VER_MAJOR && dpmcp_dev->obj_desc.ver_minor < DPMCP_MIN_VER_MINOR)) { dev_err(&dpmcp_dev->dev, "ERROR: Version %d.%d of DPMCP not supported.\n", dpmcp_dev->obj_desc.ver_major, dpmcp_dev->obj_desc.ver_minor); error = -ENOTSUPP; goto error_cleanup_resource; } if (WARN_ON(dpmcp_dev->obj_desc.region_count == 0)) goto error_cleanup_resource; mc_portal_phys_addr = dpmcp_dev->regions[0].start; mc_portal_size = dpmcp_dev->regions[0].end - dpmcp_dev->regions[0].start + 1; if (WARN_ON(mc_portal_size != mc_bus_dev->mc_io->portal_size)) goto error_cleanup_resource; error = fsl_create_mc_io(&mc_bus_dev->dev, mc_portal_phys_addr, mc_portal_size, dpmcp_dev, mc_io_flags, &mc_io); if (error < 0) goto error_cleanup_resource; *new_mc_io = mc_io; return 0; error_cleanup_resource: fsl_mc_resource_free(resource); return error; }