664 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * fsl-mc object allocator driver4 *5 * Copyright (C) 2013-2016 Freescale Semiconductor, Inc.6 *7 */8 9#include <linux/module.h>10#include <linux/msi.h>11#include <linux/fsl/mc.h>12 13#include "fsl-mc-private.h"14 15static bool __must_check fsl_mc_is_allocatable(struct fsl_mc_device *mc_dev)16{17 return is_fsl_mc_bus_dpbp(mc_dev) ||18 is_fsl_mc_bus_dpmcp(mc_dev) ||19 is_fsl_mc_bus_dpcon(mc_dev);20}21 22/**23 * fsl_mc_resource_pool_add_device - add allocatable object to a resource24 * pool of a given fsl-mc bus25 *26 * @mc_bus: pointer to the fsl-mc bus27 * @pool_type: pool type28 * @mc_dev: pointer to allocatable fsl-mc device29 */30static int __must_check fsl_mc_resource_pool_add_device(struct fsl_mc_bus31 *mc_bus,32 enum fsl_mc_pool_type33 pool_type,34 struct fsl_mc_device35 *mc_dev)36{37 struct fsl_mc_resource_pool *res_pool;38 struct fsl_mc_resource *resource;39 struct fsl_mc_device *mc_bus_dev = &mc_bus->mc_dev;40 int error = -EINVAL;41 42 if (pool_type < 0 || pool_type >= FSL_MC_NUM_POOL_TYPES)43 goto out;44 if (!fsl_mc_is_allocatable(mc_dev))45 goto out;46 if (mc_dev->resource)47 goto out;48 49 res_pool = &mc_bus->resource_pools[pool_type];50 if (res_pool->type != pool_type)51 goto out;52 if (res_pool->mc_bus != mc_bus)53 goto out;54 55 mutex_lock(&res_pool->mutex);56 57 if (res_pool->max_count < 0)58 goto out_unlock;59 if (res_pool->free_count < 0 ||60 res_pool->free_count > res_pool->max_count)61 goto out_unlock;62 63 resource = devm_kzalloc(&mc_bus_dev->dev, sizeof(*resource),64 GFP_KERNEL);65 if (!resource) {66 error = -ENOMEM;67 dev_err(&mc_bus_dev->dev,68 "Failed to allocate memory for fsl_mc_resource\n");69 goto out_unlock;70 }71 72 resource->type = pool_type;73 resource->id = mc_dev->obj_desc.id;74 resource->data = mc_dev;75 resource->parent_pool = res_pool;76 INIT_LIST_HEAD(&resource->node);77 list_add_tail(&resource->node, &res_pool->free_list);78 mc_dev->resource = resource;79 res_pool->free_count++;80 res_pool->max_count++;81 error = 0;82out_unlock:83 mutex_unlock(&res_pool->mutex);84out:85 return error;86}87 88/**89 * fsl_mc_resource_pool_remove_device - remove an allocatable device from a90 * resource pool91 *92 * @mc_dev: pointer to allocatable fsl-mc device93 *94 * It permanently removes an allocatable fsl-mc device from the resource95 * pool. It's an error if the device is in use.96 */97static int __must_check fsl_mc_resource_pool_remove_device(struct fsl_mc_device98 *mc_dev)99{100 struct fsl_mc_device *mc_bus_dev;101 struct fsl_mc_bus *mc_bus;102 struct fsl_mc_resource_pool *res_pool;103 struct fsl_mc_resource *resource;104 int error = -EINVAL;105 106 mc_bus_dev = to_fsl_mc_device(mc_dev->dev.parent);107 mc_bus = to_fsl_mc_bus(mc_bus_dev);108 109 resource = mc_dev->resource;110 if (!resource || resource->data != mc_dev) {111 dev_err(&mc_bus_dev->dev, "resource mismatch\n");112 goto out;113 }114 115 res_pool = resource->parent_pool;116 if (res_pool != &mc_bus->resource_pools[resource->type]) {117 dev_err(&mc_bus_dev->dev, "pool mismatch\n");118 goto out;119 }120 121 mutex_lock(&res_pool->mutex);122 123 if (res_pool->max_count <= 0) {124 dev_err(&mc_bus_dev->dev, "max_count underflow\n");125 goto out_unlock;126 }127 if (res_pool->free_count <= 0 ||128 res_pool->free_count > res_pool->max_count) {129 dev_err(&mc_bus_dev->dev, "free_count mismatch\n");130 goto out_unlock;131 }132 133 /*134 * If the device is currently allocated, its resource is not135 * in the free list and thus, the device cannot be removed.136 */137 if (list_empty(&resource->node)) {138 error = -EBUSY;139 dev_err(&mc_bus_dev->dev,140 "Device %s cannot be removed from resource pool\n",141 dev_name(&mc_dev->dev));142 goto out_unlock;143 }144 145 list_del_init(&resource->node);146 res_pool->free_count--;147 res_pool->max_count--;148 149 devm_kfree(&mc_bus_dev->dev, resource);150 mc_dev->resource = NULL;151 error = 0;152out_unlock:153 mutex_unlock(&res_pool->mutex);154out:155 return error;156}157 158static const char *const fsl_mc_pool_type_strings[] = {159 [FSL_MC_POOL_DPMCP] = "dpmcp",160 [FSL_MC_POOL_DPBP] = "dpbp",161 [FSL_MC_POOL_DPCON] = "dpcon",162 [FSL_MC_POOL_IRQ] = "irq",163};164 165static int __must_check object_type_to_pool_type(const char *object_type,166 enum fsl_mc_pool_type167 *pool_type)168{169 unsigned int i;170 171 for (i = 0; i < ARRAY_SIZE(fsl_mc_pool_type_strings); i++) {172 if (strcmp(object_type, fsl_mc_pool_type_strings[i]) == 0) {173 *pool_type = i;174 return 0;175 }176 }177 178 return -EINVAL;179}180 181int __must_check fsl_mc_resource_allocate(struct fsl_mc_bus *mc_bus,182 enum fsl_mc_pool_type pool_type,183 struct fsl_mc_resource **new_resource)184{185 struct fsl_mc_resource_pool *res_pool;186 struct fsl_mc_resource *resource;187 struct fsl_mc_device *mc_bus_dev = &mc_bus->mc_dev;188 int error = -EINVAL;189 190 BUILD_BUG_ON(ARRAY_SIZE(fsl_mc_pool_type_strings) !=191 FSL_MC_NUM_POOL_TYPES);192 193 *new_resource = NULL;194 if (pool_type < 0 || pool_type >= FSL_MC_NUM_POOL_TYPES)195 goto out;196 197 res_pool = &mc_bus->resource_pools[pool_type];198 if (res_pool->mc_bus != mc_bus)199 goto out;200 201 mutex_lock(&res_pool->mutex);202 resource = list_first_entry_or_null(&res_pool->free_list,203 struct fsl_mc_resource, node);204 205 if (!resource) {206 error = -ENXIO;207 dev_err(&mc_bus_dev->dev,208 "No more resources of type %s left\n",209 fsl_mc_pool_type_strings[pool_type]);210 goto out_unlock;211 }212 213 if (resource->type != pool_type)214 goto out_unlock;215 if (resource->parent_pool != res_pool)216 goto out_unlock;217 if (res_pool->free_count <= 0 ||218 res_pool->free_count > res_pool->max_count)219 goto out_unlock;220 221 list_del_init(&resource->node);222 223 res_pool->free_count--;224 error = 0;225out_unlock:226 mutex_unlock(&res_pool->mutex);227 *new_resource = resource;228out:229 return error;230}231EXPORT_SYMBOL_GPL(fsl_mc_resource_allocate);232 233void fsl_mc_resource_free(struct fsl_mc_resource *resource)234{235 struct fsl_mc_resource_pool *res_pool;236 237 res_pool = resource->parent_pool;238 if (resource->type != res_pool->type)239 return;240 241 mutex_lock(&res_pool->mutex);242 if (res_pool->free_count < 0 ||243 res_pool->free_count >= res_pool->max_count)244 goto out_unlock;245 246 if (!list_empty(&resource->node))247 goto out_unlock;248 249 list_add_tail(&resource->node, &res_pool->free_list);250 res_pool->free_count++;251out_unlock:252 mutex_unlock(&res_pool->mutex);253}254EXPORT_SYMBOL_GPL(fsl_mc_resource_free);255 256/**257 * fsl_mc_object_allocate - Allocates an fsl-mc object of the given258 * pool type from a given fsl-mc bus instance259 *260 * @mc_dev: fsl-mc device which is used in conjunction with the261 * allocated object262 * @pool_type: pool type263 * @new_mc_adev: pointer to area where the pointer to the allocated device264 * is to be returned265 *266 * Allocatable objects are always used in conjunction with some functional267 * device. This function allocates an object of the specified type from268 * the DPRC containing the functional device.269 *270 * NOTE: pool_type must be different from FSL_MC_POOL_MCP, since MC271 * portals are allocated using fsl_mc_portal_allocate(), instead of272 * this function.273 */274int __must_check fsl_mc_object_allocate(struct fsl_mc_device *mc_dev,275 enum fsl_mc_pool_type pool_type,276 struct fsl_mc_device **new_mc_adev)277{278 struct fsl_mc_device *mc_bus_dev;279 struct fsl_mc_bus *mc_bus;280 struct fsl_mc_device *mc_adev;281 int error = -EINVAL;282 struct fsl_mc_resource *resource = NULL;283 284 *new_mc_adev = NULL;285 if (mc_dev->flags & FSL_MC_IS_DPRC)286 goto error;287 288 if (!dev_is_fsl_mc(mc_dev->dev.parent))289 goto error;290 291 if (pool_type == FSL_MC_POOL_DPMCP)292 goto error;293 294 mc_bus_dev = to_fsl_mc_device(mc_dev->dev.parent);295 mc_bus = to_fsl_mc_bus(mc_bus_dev);296 error = fsl_mc_resource_allocate(mc_bus, pool_type, &resource);297 if (error < 0)298 goto error;299 300 mc_adev = resource->data;301 if (!mc_adev) {302 error = -EINVAL;303 goto error;304 }305 306 mc_adev->consumer_link = device_link_add(&mc_dev->dev,307 &mc_adev->dev,308 DL_FLAG_AUTOREMOVE_CONSUMER);309 if (!mc_adev->consumer_link) {310 error = -EINVAL;311 goto error;312 }313 314 *new_mc_adev = mc_adev;315 return 0;316error:317 if (resource)318 fsl_mc_resource_free(resource);319 320 return error;321}322EXPORT_SYMBOL_GPL(fsl_mc_object_allocate);323 324/**325 * fsl_mc_object_free - Returns an fsl-mc object to the resource326 * pool where it came from.327 * @mc_adev: Pointer to the fsl-mc device328 */329void fsl_mc_object_free(struct fsl_mc_device *mc_adev)330{331 struct fsl_mc_resource *resource;332 333 resource = mc_adev->resource;334 if (resource->type == FSL_MC_POOL_DPMCP)335 return;336 if (resource->data != mc_adev)337 return;338 339 fsl_mc_resource_free(resource);340 341 mc_adev->consumer_link = NULL;342}343EXPORT_SYMBOL_GPL(fsl_mc_object_free);344 345/*346 * A DPRC and the devices in the DPRC all share the same GIC-ITS device347 * ID. A block of IRQs is pre-allocated and maintained in a pool348 * from which devices can allocate them when needed.349 */350 351/*352 * Initialize the interrupt pool associated with an fsl-mc bus.353 * It allocates a block of IRQs from the GIC-ITS.354 */355int fsl_mc_populate_irq_pool(struct fsl_mc_device *mc_bus_dev,356 unsigned int irq_count)357{358 unsigned int i;359 struct fsl_mc_device_irq *irq_resources;360 struct fsl_mc_device_irq *mc_dev_irq;361 int error;362 struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_bus_dev);363 struct fsl_mc_resource_pool *res_pool =364 &mc_bus->resource_pools[FSL_MC_POOL_IRQ];365 366 /* do nothing if the IRQ pool is already populated */367 if (mc_bus->irq_resources)368 return 0;369 370 if (irq_count == 0 ||371 irq_count > FSL_MC_IRQ_POOL_MAX_TOTAL_IRQS)372 return -EINVAL;373 374 error = fsl_mc_msi_domain_alloc_irqs(&mc_bus_dev->dev, irq_count);375 if (error < 0)376 return error;377 378 irq_resources = devm_kcalloc(&mc_bus_dev->dev,379 irq_count, sizeof(*irq_resources),380 GFP_KERNEL);381 if (!irq_resources) {382 error = -ENOMEM;383 goto cleanup_msi_irqs;384 }385 386 for (i = 0; i < irq_count; i++) {387 mc_dev_irq = &irq_resources[i];388 389 /*390 * NOTE: This mc_dev_irq's MSI addr/value pair will be set391 * by the fsl_mc_msi_write_msg() callback392 */393 mc_dev_irq->resource.type = res_pool->type;394 mc_dev_irq->resource.data = mc_dev_irq;395 mc_dev_irq->resource.parent_pool = res_pool;396 mc_dev_irq->virq = msi_get_virq(&mc_bus_dev->dev, i);397 mc_dev_irq->resource.id = mc_dev_irq->virq;398 INIT_LIST_HEAD(&mc_dev_irq->resource.node);399 list_add_tail(&mc_dev_irq->resource.node, &res_pool->free_list);400 }401 402 res_pool->max_count = irq_count;403 res_pool->free_count = irq_count;404 mc_bus->irq_resources = irq_resources;405 return 0;406 407cleanup_msi_irqs:408 fsl_mc_msi_domain_free_irqs(&mc_bus_dev->dev);409 return error;410}411EXPORT_SYMBOL_GPL(fsl_mc_populate_irq_pool);412 413/*414 * Teardown the interrupt pool associated with an fsl-mc bus.415 * It frees the IRQs that were allocated to the pool, back to the GIC-ITS.416 */417void fsl_mc_cleanup_irq_pool(struct fsl_mc_device *mc_bus_dev)418{419 struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_bus_dev);420 struct fsl_mc_resource_pool *res_pool =421 &mc_bus->resource_pools[FSL_MC_POOL_IRQ];422 423 if (!mc_bus->irq_resources)424 return;425 426 if (res_pool->max_count == 0)427 return;428 429 if (res_pool->free_count != res_pool->max_count)430 return;431 432 INIT_LIST_HEAD(&res_pool->free_list);433 res_pool->max_count = 0;434 res_pool->free_count = 0;435 mc_bus->irq_resources = NULL;436 fsl_mc_msi_domain_free_irqs(&mc_bus_dev->dev);437}438EXPORT_SYMBOL_GPL(fsl_mc_cleanup_irq_pool);439 440/*441 * Allocate the IRQs required by a given fsl-mc device.442 */443int __must_check fsl_mc_allocate_irqs(struct fsl_mc_device *mc_dev)444{445 int i;446 int irq_count;447 int res_allocated_count = 0;448 int error = -EINVAL;449 struct fsl_mc_device_irq **irqs = NULL;450 struct fsl_mc_bus *mc_bus;451 struct fsl_mc_resource_pool *res_pool;452 453 if (mc_dev->irqs)454 return -EINVAL;455 456 irq_count = mc_dev->obj_desc.irq_count;457 if (irq_count == 0)458 return -EINVAL;459 460 if (is_fsl_mc_bus_dprc(mc_dev))461 mc_bus = to_fsl_mc_bus(mc_dev);462 else463 mc_bus = to_fsl_mc_bus(to_fsl_mc_device(mc_dev->dev.parent));464 465 if (!mc_bus->irq_resources)466 return -EINVAL;467 468 res_pool = &mc_bus->resource_pools[FSL_MC_POOL_IRQ];469 if (res_pool->free_count < irq_count) {470 dev_err(&mc_dev->dev,471 "Not able to allocate %u irqs for device\n", irq_count);472 return -ENOSPC;473 }474 475 irqs = devm_kcalloc(&mc_dev->dev, irq_count, sizeof(irqs[0]),476 GFP_KERNEL);477 if (!irqs)478 return -ENOMEM;479 480 for (i = 0; i < irq_count; i++) {481 struct fsl_mc_resource *resource;482 483 error = fsl_mc_resource_allocate(mc_bus, FSL_MC_POOL_IRQ,484 &resource);485 if (error < 0)486 goto error_resource_alloc;487 488 irqs[i] = to_fsl_mc_irq(resource);489 res_allocated_count++;490 491 irqs[i]->mc_dev = mc_dev;492 irqs[i]->dev_irq_index = i;493 }494 495 mc_dev->irqs = irqs;496 return 0;497 498error_resource_alloc:499 for (i = 0; i < res_allocated_count; i++) {500 irqs[i]->mc_dev = NULL;501 fsl_mc_resource_free(&irqs[i]->resource);502 }503 504 return error;505}506EXPORT_SYMBOL_GPL(fsl_mc_allocate_irqs);507 508/*509 * Frees the IRQs that were allocated for an fsl-mc device.510 */511void fsl_mc_free_irqs(struct fsl_mc_device *mc_dev)512{513 int i;514 int irq_count;515 struct fsl_mc_bus *mc_bus;516 struct fsl_mc_device_irq **irqs = mc_dev->irqs;517 518 if (!irqs)519 return;520 521 irq_count = mc_dev->obj_desc.irq_count;522 523 if (is_fsl_mc_bus_dprc(mc_dev))524 mc_bus = to_fsl_mc_bus(mc_dev);525 else526 mc_bus = to_fsl_mc_bus(to_fsl_mc_device(mc_dev->dev.parent));527 528 if (!mc_bus->irq_resources)529 return;530 531 for (i = 0; i < irq_count; i++) {532 irqs[i]->mc_dev = NULL;533 fsl_mc_resource_free(&irqs[i]->resource);534 }535 536 mc_dev->irqs = NULL;537}538EXPORT_SYMBOL_GPL(fsl_mc_free_irqs);539 540void fsl_mc_init_all_resource_pools(struct fsl_mc_device *mc_bus_dev)541{542 int pool_type;543 struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_bus_dev);544 545 for (pool_type = 0; pool_type < FSL_MC_NUM_POOL_TYPES; pool_type++) {546 struct fsl_mc_resource_pool *res_pool =547 &mc_bus->resource_pools[pool_type];548 549 res_pool->type = pool_type;550 res_pool->max_count = 0;551 res_pool->free_count = 0;552 res_pool->mc_bus = mc_bus;553 INIT_LIST_HEAD(&res_pool->free_list);554 mutex_init(&res_pool->mutex);555 }556}557 558static void fsl_mc_cleanup_resource_pool(struct fsl_mc_device *mc_bus_dev,559 enum fsl_mc_pool_type pool_type)560{561 struct fsl_mc_resource *resource;562 struct fsl_mc_resource *next;563 struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_bus_dev);564 struct fsl_mc_resource_pool *res_pool =565 &mc_bus->resource_pools[pool_type];566 567 list_for_each_entry_safe(resource, next, &res_pool->free_list, node)568 devm_kfree(&mc_bus_dev->dev, resource);569}570 571void fsl_mc_cleanup_all_resource_pools(struct fsl_mc_device *mc_bus_dev)572{573 int pool_type;574 575 for (pool_type = 0; pool_type < FSL_MC_NUM_POOL_TYPES; pool_type++)576 fsl_mc_cleanup_resource_pool(mc_bus_dev, pool_type);577}578 579/*580 * fsl_mc_allocator_probe - callback invoked when an allocatable device is581 * being added to the system582 */583static int fsl_mc_allocator_probe(struct fsl_mc_device *mc_dev)584{585 enum fsl_mc_pool_type pool_type;586 struct fsl_mc_device *mc_bus_dev;587 struct fsl_mc_bus *mc_bus;588 int error;589 590 if (!fsl_mc_is_allocatable(mc_dev))591 return -EINVAL;592 593 mc_bus_dev = to_fsl_mc_device(mc_dev->dev.parent);594 if (!dev_is_fsl_mc(&mc_bus_dev->dev))595 return -EINVAL;596 597 mc_bus = to_fsl_mc_bus(mc_bus_dev);598 error = object_type_to_pool_type(mc_dev->obj_desc.type, &pool_type);599 if (error < 0)600 return error;601 602 error = fsl_mc_resource_pool_add_device(mc_bus, pool_type, mc_dev);603 if (error < 0)604 return error;605 606 dev_dbg(&mc_dev->dev,607 "Allocatable fsl-mc device bound to fsl_mc_allocator driver");608 return 0;609}610 611/*612 * fsl_mc_allocator_remove - callback invoked when an allocatable device is613 * being removed from the system614 */615static void fsl_mc_allocator_remove(struct fsl_mc_device *mc_dev)616{617 int error;618 619 if (mc_dev->resource) {620 error = fsl_mc_resource_pool_remove_device(mc_dev);621 if (error < 0)622 return;623 }624 625 dev_dbg(&mc_dev->dev,626 "Allocatable fsl-mc device unbound from fsl_mc_allocator driver");627}628 629static const struct fsl_mc_device_id match_id_table[] = {630 {631 .vendor = FSL_MC_VENDOR_FREESCALE,632 .obj_type = "dpbp",633 },634 {635 .vendor = FSL_MC_VENDOR_FREESCALE,636 .obj_type = "dpmcp",637 },638 {639 .vendor = FSL_MC_VENDOR_FREESCALE,640 .obj_type = "dpcon",641 },642 {.vendor = 0x0},643};644 645static struct fsl_mc_driver fsl_mc_allocator_driver = {646 .driver = {647 .name = "fsl_mc_allocator",648 .pm = NULL,649 },650 .match_id_table = match_id_table,651 .probe = fsl_mc_allocator_probe,652 .remove = fsl_mc_allocator_remove,653};654 655int __init fsl_mc_allocator_driver_init(void)656{657 return fsl_mc_driver_register(&fsl_mc_allocator_driver);658}659 660void fsl_mc_allocator_driver_exit(void)661{662 fsl_mc_driver_unregister(&fsl_mc_allocator_driver);663}664