Path: blob/master/drivers/bus/fsl-mc/fsl-mc-allocator.c
26282 views
// SPDX-License-Identifier: GPL-2.01/*2* fsl-mc object allocator driver3*4* Copyright (C) 2013-2016 Freescale Semiconductor, Inc.5*6*/78#include <linux/module.h>9#include <linux/msi.h>10#include <linux/fsl/mc.h>1112#include "fsl-mc-private.h"1314static bool __must_check fsl_mc_is_allocatable(struct fsl_mc_device *mc_dev)15{16return is_fsl_mc_bus_dpbp(mc_dev) ||17is_fsl_mc_bus_dpmcp(mc_dev) ||18is_fsl_mc_bus_dpcon(mc_dev);19}2021/**22* fsl_mc_resource_pool_add_device - add allocatable object to a resource23* pool of a given fsl-mc bus24*25* @mc_bus: pointer to the fsl-mc bus26* @pool_type: pool type27* @mc_dev: pointer to allocatable fsl-mc device28*/29static int __must_check fsl_mc_resource_pool_add_device(struct fsl_mc_bus30*mc_bus,31enum fsl_mc_pool_type32pool_type,33struct fsl_mc_device34*mc_dev)35{36struct fsl_mc_resource_pool *res_pool;37struct fsl_mc_resource *resource;38struct fsl_mc_device *mc_bus_dev = &mc_bus->mc_dev;39int error = -EINVAL;4041if (pool_type < 0 || pool_type >= FSL_MC_NUM_POOL_TYPES)42goto out;43if (!fsl_mc_is_allocatable(mc_dev))44goto out;45if (mc_dev->resource)46goto out;4748res_pool = &mc_bus->resource_pools[pool_type];49if (res_pool->type != pool_type)50goto out;51if (res_pool->mc_bus != mc_bus)52goto out;5354mutex_lock(&res_pool->mutex);5556if (res_pool->max_count < 0)57goto out_unlock;58if (res_pool->free_count < 0 ||59res_pool->free_count > res_pool->max_count)60goto out_unlock;6162resource = devm_kzalloc(&mc_bus_dev->dev, sizeof(*resource),63GFP_KERNEL);64if (!resource) {65error = -ENOMEM;66dev_err(&mc_bus_dev->dev,67"Failed to allocate memory for fsl_mc_resource\n");68goto out_unlock;69}7071resource->type = pool_type;72resource->id = mc_dev->obj_desc.id;73resource->data = mc_dev;74resource->parent_pool = res_pool;75INIT_LIST_HEAD(&resource->node);76list_add_tail(&resource->node, &res_pool->free_list);77mc_dev->resource = resource;78res_pool->free_count++;79res_pool->max_count++;80error = 0;81out_unlock:82mutex_unlock(&res_pool->mutex);83out:84return error;85}8687/**88* fsl_mc_resource_pool_remove_device - remove an allocatable device from a89* resource pool90*91* @mc_dev: pointer to allocatable fsl-mc device92*93* It permanently removes an allocatable fsl-mc device from the resource94* pool. It's an error if the device is in use.95*/96static int __must_check fsl_mc_resource_pool_remove_device(struct fsl_mc_device97*mc_dev)98{99struct fsl_mc_device *mc_bus_dev;100struct fsl_mc_bus *mc_bus;101struct fsl_mc_resource_pool *res_pool;102struct fsl_mc_resource *resource;103int error = -EINVAL;104105mc_bus_dev = to_fsl_mc_device(mc_dev->dev.parent);106mc_bus = to_fsl_mc_bus(mc_bus_dev);107108resource = mc_dev->resource;109if (!resource || resource->data != mc_dev) {110dev_err(&mc_bus_dev->dev, "resource mismatch\n");111goto out;112}113114res_pool = resource->parent_pool;115if (res_pool != &mc_bus->resource_pools[resource->type]) {116dev_err(&mc_bus_dev->dev, "pool mismatch\n");117goto out;118}119120mutex_lock(&res_pool->mutex);121122if (res_pool->max_count <= 0) {123dev_err(&mc_bus_dev->dev, "max_count underflow\n");124goto out_unlock;125}126if (res_pool->free_count <= 0 ||127res_pool->free_count > res_pool->max_count) {128dev_err(&mc_bus_dev->dev, "free_count mismatch\n");129goto out_unlock;130}131132/*133* If the device is currently allocated, its resource is not134* in the free list and thus, the device cannot be removed.135*/136if (list_empty(&resource->node)) {137error = -EBUSY;138dev_err(&mc_bus_dev->dev,139"Device %s cannot be removed from resource pool\n",140dev_name(&mc_dev->dev));141goto out_unlock;142}143144list_del_init(&resource->node);145res_pool->free_count--;146res_pool->max_count--;147148devm_kfree(&mc_bus_dev->dev, resource);149mc_dev->resource = NULL;150error = 0;151out_unlock:152mutex_unlock(&res_pool->mutex);153out:154return error;155}156157static const char *const fsl_mc_pool_type_strings[] = {158[FSL_MC_POOL_DPMCP] = "dpmcp",159[FSL_MC_POOL_DPBP] = "dpbp",160[FSL_MC_POOL_DPCON] = "dpcon",161[FSL_MC_POOL_IRQ] = "irq",162};163164static int __must_check object_type_to_pool_type(const char *object_type,165enum fsl_mc_pool_type166*pool_type)167{168unsigned int i;169170for (i = 0; i < ARRAY_SIZE(fsl_mc_pool_type_strings); i++) {171if (strcmp(object_type, fsl_mc_pool_type_strings[i]) == 0) {172*pool_type = i;173return 0;174}175}176177return -EINVAL;178}179180int __must_check fsl_mc_resource_allocate(struct fsl_mc_bus *mc_bus,181enum fsl_mc_pool_type pool_type,182struct fsl_mc_resource **new_resource)183{184struct fsl_mc_resource_pool *res_pool;185struct fsl_mc_resource *resource;186struct fsl_mc_device *mc_bus_dev = &mc_bus->mc_dev;187int error = -EINVAL;188189BUILD_BUG_ON(ARRAY_SIZE(fsl_mc_pool_type_strings) !=190FSL_MC_NUM_POOL_TYPES);191192*new_resource = NULL;193if (pool_type < 0 || pool_type >= FSL_MC_NUM_POOL_TYPES)194goto out;195196res_pool = &mc_bus->resource_pools[pool_type];197if (res_pool->mc_bus != mc_bus)198goto out;199200mutex_lock(&res_pool->mutex);201resource = list_first_entry_or_null(&res_pool->free_list,202struct fsl_mc_resource, node);203204if (!resource) {205error = -ENXIO;206dev_err(&mc_bus_dev->dev,207"No more resources of type %s left\n",208fsl_mc_pool_type_strings[pool_type]);209goto out_unlock;210}211212if (resource->type != pool_type)213goto out_unlock;214if (resource->parent_pool != res_pool)215goto out_unlock;216if (res_pool->free_count <= 0 ||217res_pool->free_count > res_pool->max_count)218goto out_unlock;219220list_del_init(&resource->node);221222res_pool->free_count--;223error = 0;224out_unlock:225mutex_unlock(&res_pool->mutex);226*new_resource = resource;227out:228return error;229}230EXPORT_SYMBOL_GPL(fsl_mc_resource_allocate);231232void fsl_mc_resource_free(struct fsl_mc_resource *resource)233{234struct fsl_mc_resource_pool *res_pool;235236res_pool = resource->parent_pool;237if (resource->type != res_pool->type)238return;239240mutex_lock(&res_pool->mutex);241if (res_pool->free_count < 0 ||242res_pool->free_count >= res_pool->max_count)243goto out_unlock;244245if (!list_empty(&resource->node))246goto out_unlock;247248list_add_tail(&resource->node, &res_pool->free_list);249res_pool->free_count++;250out_unlock:251mutex_unlock(&res_pool->mutex);252}253EXPORT_SYMBOL_GPL(fsl_mc_resource_free);254255/**256* fsl_mc_object_allocate - Allocates an fsl-mc object of the given257* pool type from a given fsl-mc bus instance258*259* @mc_dev: fsl-mc device which is used in conjunction with the260* allocated object261* @pool_type: pool type262* @new_mc_adev: pointer to area where the pointer to the allocated device263* is to be returned264*265* Allocatable objects are always used in conjunction with some functional266* device. This function allocates an object of the specified type from267* the DPRC containing the functional device.268*269* NOTE: pool_type must be different from FSL_MC_POOL_MCP, since MC270* portals are allocated using fsl_mc_portal_allocate(), instead of271* this function.272*/273int __must_check fsl_mc_object_allocate(struct fsl_mc_device *mc_dev,274enum fsl_mc_pool_type pool_type,275struct fsl_mc_device **new_mc_adev)276{277struct fsl_mc_device *mc_bus_dev;278struct fsl_mc_bus *mc_bus;279struct fsl_mc_device *mc_adev;280int error = -EINVAL;281struct fsl_mc_resource *resource = NULL;282283*new_mc_adev = NULL;284if (mc_dev->flags & FSL_MC_IS_DPRC)285goto error;286287if (!dev_is_fsl_mc(mc_dev->dev.parent))288goto error;289290if (pool_type == FSL_MC_POOL_DPMCP)291goto error;292293mc_bus_dev = to_fsl_mc_device(mc_dev->dev.parent);294mc_bus = to_fsl_mc_bus(mc_bus_dev);295error = fsl_mc_resource_allocate(mc_bus, pool_type, &resource);296if (error < 0)297goto error;298299mc_adev = resource->data;300if (!mc_adev) {301error = -EINVAL;302goto error;303}304305mc_adev->consumer_link = device_link_add(&mc_dev->dev,306&mc_adev->dev,307DL_FLAG_AUTOREMOVE_CONSUMER);308if (!mc_adev->consumer_link) {309error = -EINVAL;310goto error;311}312313*new_mc_adev = mc_adev;314return 0;315error:316if (resource)317fsl_mc_resource_free(resource);318319return error;320}321EXPORT_SYMBOL_GPL(fsl_mc_object_allocate);322323/**324* fsl_mc_object_free - Returns an fsl-mc object to the resource325* pool where it came from.326* @mc_adev: Pointer to the fsl-mc device327*/328void fsl_mc_object_free(struct fsl_mc_device *mc_adev)329{330struct fsl_mc_resource *resource;331332resource = mc_adev->resource;333if (resource->type == FSL_MC_POOL_DPMCP)334return;335if (resource->data != mc_adev)336return;337338fsl_mc_resource_free(resource);339340mc_adev->consumer_link = NULL;341}342EXPORT_SYMBOL_GPL(fsl_mc_object_free);343344/*345* A DPRC and the devices in the DPRC all share the same GIC-ITS device346* ID. A block of IRQs is pre-allocated and maintained in a pool347* from which devices can allocate them when needed.348*/349350/*351* Initialize the interrupt pool associated with an fsl-mc bus.352* It allocates a block of IRQs from the GIC-ITS.353*/354int fsl_mc_populate_irq_pool(struct fsl_mc_device *mc_bus_dev,355unsigned int irq_count)356{357unsigned int i;358struct fsl_mc_device_irq *irq_resources;359struct fsl_mc_device_irq *mc_dev_irq;360int error;361struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_bus_dev);362struct fsl_mc_resource_pool *res_pool =363&mc_bus->resource_pools[FSL_MC_POOL_IRQ];364365/* do nothing if the IRQ pool is already populated */366if (mc_bus->irq_resources)367return 0;368369if (irq_count == 0 ||370irq_count > FSL_MC_IRQ_POOL_MAX_TOTAL_IRQS)371return -EINVAL;372373error = fsl_mc_msi_domain_alloc_irqs(&mc_bus_dev->dev, irq_count);374if (error < 0)375return error;376377irq_resources = devm_kcalloc(&mc_bus_dev->dev,378irq_count, sizeof(*irq_resources),379GFP_KERNEL);380if (!irq_resources) {381error = -ENOMEM;382goto cleanup_msi_irqs;383}384385for (i = 0; i < irq_count; i++) {386mc_dev_irq = &irq_resources[i];387388/*389* NOTE: This mc_dev_irq's MSI addr/value pair will be set390* by the fsl_mc_msi_write_msg() callback391*/392mc_dev_irq->resource.type = res_pool->type;393mc_dev_irq->resource.data = mc_dev_irq;394mc_dev_irq->resource.parent_pool = res_pool;395mc_dev_irq->virq = msi_get_virq(&mc_bus_dev->dev, i);396mc_dev_irq->resource.id = mc_dev_irq->virq;397INIT_LIST_HEAD(&mc_dev_irq->resource.node);398list_add_tail(&mc_dev_irq->resource.node, &res_pool->free_list);399}400401res_pool->max_count = irq_count;402res_pool->free_count = irq_count;403mc_bus->irq_resources = irq_resources;404return 0;405406cleanup_msi_irqs:407fsl_mc_msi_domain_free_irqs(&mc_bus_dev->dev);408return error;409}410EXPORT_SYMBOL_GPL(fsl_mc_populate_irq_pool);411412/*413* Teardown the interrupt pool associated with an fsl-mc bus.414* It frees the IRQs that were allocated to the pool, back to the GIC-ITS.415*/416void fsl_mc_cleanup_irq_pool(struct fsl_mc_device *mc_bus_dev)417{418struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_bus_dev);419struct fsl_mc_resource_pool *res_pool =420&mc_bus->resource_pools[FSL_MC_POOL_IRQ];421422if (!mc_bus->irq_resources)423return;424425if (res_pool->max_count == 0)426return;427428if (res_pool->free_count != res_pool->max_count)429return;430431INIT_LIST_HEAD(&res_pool->free_list);432res_pool->max_count = 0;433res_pool->free_count = 0;434mc_bus->irq_resources = NULL;435fsl_mc_msi_domain_free_irqs(&mc_bus_dev->dev);436}437EXPORT_SYMBOL_GPL(fsl_mc_cleanup_irq_pool);438439/*440* Allocate the IRQs required by a given fsl-mc device.441*/442int __must_check fsl_mc_allocate_irqs(struct fsl_mc_device *mc_dev)443{444int i;445int irq_count;446int res_allocated_count = 0;447int error = -EINVAL;448struct fsl_mc_device_irq **irqs = NULL;449struct fsl_mc_bus *mc_bus;450struct fsl_mc_resource_pool *res_pool;451452if (mc_dev->irqs)453return -EINVAL;454455irq_count = mc_dev->obj_desc.irq_count;456if (irq_count == 0)457return -EINVAL;458459if (is_fsl_mc_bus_dprc(mc_dev))460mc_bus = to_fsl_mc_bus(mc_dev);461else462mc_bus = to_fsl_mc_bus(to_fsl_mc_device(mc_dev->dev.parent));463464if (!mc_bus->irq_resources)465return -EINVAL;466467res_pool = &mc_bus->resource_pools[FSL_MC_POOL_IRQ];468if (res_pool->free_count < irq_count) {469dev_err(&mc_dev->dev,470"Not able to allocate %u irqs for device\n", irq_count);471return -ENOSPC;472}473474irqs = devm_kcalloc(&mc_dev->dev, irq_count, sizeof(irqs[0]),475GFP_KERNEL);476if (!irqs)477return -ENOMEM;478479for (i = 0; i < irq_count; i++) {480struct fsl_mc_resource *resource;481482error = fsl_mc_resource_allocate(mc_bus, FSL_MC_POOL_IRQ,483&resource);484if (error < 0)485goto error_resource_alloc;486487irqs[i] = to_fsl_mc_irq(resource);488res_allocated_count++;489490irqs[i]->mc_dev = mc_dev;491irqs[i]->dev_irq_index = i;492}493494mc_dev->irqs = irqs;495return 0;496497error_resource_alloc:498for (i = 0; i < res_allocated_count; i++) {499irqs[i]->mc_dev = NULL;500fsl_mc_resource_free(&irqs[i]->resource);501}502503return error;504}505EXPORT_SYMBOL_GPL(fsl_mc_allocate_irqs);506507/*508* Frees the IRQs that were allocated for an fsl-mc device.509*/510void fsl_mc_free_irqs(struct fsl_mc_device *mc_dev)511{512int i;513int irq_count;514struct fsl_mc_bus *mc_bus;515struct fsl_mc_device_irq **irqs = mc_dev->irqs;516517if (!irqs)518return;519520irq_count = mc_dev->obj_desc.irq_count;521522if (is_fsl_mc_bus_dprc(mc_dev))523mc_bus = to_fsl_mc_bus(mc_dev);524else525mc_bus = to_fsl_mc_bus(to_fsl_mc_device(mc_dev->dev.parent));526527if (!mc_bus->irq_resources)528return;529530for (i = 0; i < irq_count; i++) {531irqs[i]->mc_dev = NULL;532fsl_mc_resource_free(&irqs[i]->resource);533}534535mc_dev->irqs = NULL;536}537EXPORT_SYMBOL_GPL(fsl_mc_free_irqs);538539void fsl_mc_init_all_resource_pools(struct fsl_mc_device *mc_bus_dev)540{541int pool_type;542struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_bus_dev);543544for (pool_type = 0; pool_type < FSL_MC_NUM_POOL_TYPES; pool_type++) {545struct fsl_mc_resource_pool *res_pool =546&mc_bus->resource_pools[pool_type];547548res_pool->type = pool_type;549res_pool->max_count = 0;550res_pool->free_count = 0;551res_pool->mc_bus = mc_bus;552INIT_LIST_HEAD(&res_pool->free_list);553mutex_init(&res_pool->mutex);554}555}556557/*558* fsl_mc_allocator_probe - callback invoked when an allocatable device is559* being added to the system560*/561static int fsl_mc_allocator_probe(struct fsl_mc_device *mc_dev)562{563enum fsl_mc_pool_type pool_type;564struct fsl_mc_device *mc_bus_dev;565struct fsl_mc_bus *mc_bus;566int error;567568if (!fsl_mc_is_allocatable(mc_dev))569return -EINVAL;570571mc_bus_dev = to_fsl_mc_device(mc_dev->dev.parent);572if (!dev_is_fsl_mc(&mc_bus_dev->dev))573return -EINVAL;574575mc_bus = to_fsl_mc_bus(mc_bus_dev);576error = object_type_to_pool_type(mc_dev->obj_desc.type, &pool_type);577if (error < 0)578return error;579580error = fsl_mc_resource_pool_add_device(mc_bus, pool_type, mc_dev);581if (error < 0)582return error;583584dev_dbg(&mc_dev->dev,585"Allocatable fsl-mc device bound to fsl_mc_allocator driver");586return 0;587}588589/*590* fsl_mc_allocator_remove - callback invoked when an allocatable device is591* being removed from the system592*/593static void fsl_mc_allocator_remove(struct fsl_mc_device *mc_dev)594{595int error;596597if (mc_dev->resource) {598error = fsl_mc_resource_pool_remove_device(mc_dev);599if (error < 0)600return;601}602603dev_dbg(&mc_dev->dev,604"Allocatable fsl-mc device unbound from fsl_mc_allocator driver");605}606607static const struct fsl_mc_device_id match_id_table[] = {608{609.vendor = FSL_MC_VENDOR_FREESCALE,610.obj_type = "dpbp",611},612{613.vendor = FSL_MC_VENDOR_FREESCALE,614.obj_type = "dpmcp",615},616{617.vendor = FSL_MC_VENDOR_FREESCALE,618.obj_type = "dpcon",619},620{.vendor = 0x0},621};622623static struct fsl_mc_driver fsl_mc_allocator_driver = {624.driver = {625.name = "fsl_mc_allocator",626.pm = NULL,627},628.match_id_table = match_id_table,629.probe = fsl_mc_allocator_probe,630.remove = fsl_mc_allocator_remove,631};632633int __init fsl_mc_allocator_driver_init(void)634{635return fsl_mc_driver_register(&fsl_mc_allocator_driver);636}637638639