Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/drivers/bus/fsl-mc/fsl-mc-allocator.c
26282 views
1
// SPDX-License-Identifier: GPL-2.0
2
/*
3
* fsl-mc object allocator driver
4
*
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
15
static 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 resource
24
* pool of a given fsl-mc bus
25
*
26
* @mc_bus: pointer to the fsl-mc bus
27
* @pool_type: pool type
28
* @mc_dev: pointer to allocatable fsl-mc device
29
*/
30
static int __must_check fsl_mc_resource_pool_add_device(struct fsl_mc_bus
31
*mc_bus,
32
enum fsl_mc_pool_type
33
pool_type,
34
struct fsl_mc_device
35
*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;
82
out_unlock:
83
mutex_unlock(&res_pool->mutex);
84
out:
85
return error;
86
}
87
88
/**
89
* fsl_mc_resource_pool_remove_device - remove an allocatable device from a
90
* resource pool
91
*
92
* @mc_dev: pointer to allocatable fsl-mc device
93
*
94
* It permanently removes an allocatable fsl-mc device from the resource
95
* pool. It's an error if the device is in use.
96
*/
97
static int __must_check fsl_mc_resource_pool_remove_device(struct fsl_mc_device
98
*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 not
135
* 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;
152
out_unlock:
153
mutex_unlock(&res_pool->mutex);
154
out:
155
return error;
156
}
157
158
static 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
165
static int __must_check object_type_to_pool_type(const char *object_type,
166
enum fsl_mc_pool_type
167
*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
181
int __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;
225
out_unlock:
226
mutex_unlock(&res_pool->mutex);
227
*new_resource = resource;
228
out:
229
return error;
230
}
231
EXPORT_SYMBOL_GPL(fsl_mc_resource_allocate);
232
233
void 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++;
251
out_unlock:
252
mutex_unlock(&res_pool->mutex);
253
}
254
EXPORT_SYMBOL_GPL(fsl_mc_resource_free);
255
256
/**
257
* fsl_mc_object_allocate - Allocates an fsl-mc object of the given
258
* pool type from a given fsl-mc bus instance
259
*
260
* @mc_dev: fsl-mc device which is used in conjunction with the
261
* allocated object
262
* @pool_type: pool type
263
* @new_mc_adev: pointer to area where the pointer to the allocated device
264
* is to be returned
265
*
266
* Allocatable objects are always used in conjunction with some functional
267
* device. This function allocates an object of the specified type from
268
* the DPRC containing the functional device.
269
*
270
* NOTE: pool_type must be different from FSL_MC_POOL_MCP, since MC
271
* portals are allocated using fsl_mc_portal_allocate(), instead of
272
* this function.
273
*/
274
int __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;
316
error:
317
if (resource)
318
fsl_mc_resource_free(resource);
319
320
return error;
321
}
322
EXPORT_SYMBOL_GPL(fsl_mc_object_allocate);
323
324
/**
325
* fsl_mc_object_free - Returns an fsl-mc object to the resource
326
* pool where it came from.
327
* @mc_adev: Pointer to the fsl-mc device
328
*/
329
void 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
}
343
EXPORT_SYMBOL_GPL(fsl_mc_object_free);
344
345
/*
346
* A DPRC and the devices in the DPRC all share the same GIC-ITS device
347
* ID. A block of IRQs is pre-allocated and maintained in a pool
348
* 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
*/
355
int 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 set
391
* by the fsl_mc_msi_write_msg() callback
392
*/
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
407
cleanup_msi_irqs:
408
fsl_mc_msi_domain_free_irqs(&mc_bus_dev->dev);
409
return error;
410
}
411
EXPORT_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
*/
417
void 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
}
438
EXPORT_SYMBOL_GPL(fsl_mc_cleanup_irq_pool);
439
440
/*
441
* Allocate the IRQs required by a given fsl-mc device.
442
*/
443
int __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
else
463
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
498
error_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
}
506
EXPORT_SYMBOL_GPL(fsl_mc_allocate_irqs);
507
508
/*
509
* Frees the IRQs that were allocated for an fsl-mc device.
510
*/
511
void 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
else
526
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
}
538
EXPORT_SYMBOL_GPL(fsl_mc_free_irqs);
539
540
void 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
558
/*
559
* fsl_mc_allocator_probe - callback invoked when an allocatable device is
560
* being added to the system
561
*/
562
static int fsl_mc_allocator_probe(struct fsl_mc_device *mc_dev)
563
{
564
enum fsl_mc_pool_type pool_type;
565
struct fsl_mc_device *mc_bus_dev;
566
struct fsl_mc_bus *mc_bus;
567
int error;
568
569
if (!fsl_mc_is_allocatable(mc_dev))
570
return -EINVAL;
571
572
mc_bus_dev = to_fsl_mc_device(mc_dev->dev.parent);
573
if (!dev_is_fsl_mc(&mc_bus_dev->dev))
574
return -EINVAL;
575
576
mc_bus = to_fsl_mc_bus(mc_bus_dev);
577
error = object_type_to_pool_type(mc_dev->obj_desc.type, &pool_type);
578
if (error < 0)
579
return error;
580
581
error = fsl_mc_resource_pool_add_device(mc_bus, pool_type, mc_dev);
582
if (error < 0)
583
return error;
584
585
dev_dbg(&mc_dev->dev,
586
"Allocatable fsl-mc device bound to fsl_mc_allocator driver");
587
return 0;
588
}
589
590
/*
591
* fsl_mc_allocator_remove - callback invoked when an allocatable device is
592
* being removed from the system
593
*/
594
static void fsl_mc_allocator_remove(struct fsl_mc_device *mc_dev)
595
{
596
int error;
597
598
if (mc_dev->resource) {
599
error = fsl_mc_resource_pool_remove_device(mc_dev);
600
if (error < 0)
601
return;
602
}
603
604
dev_dbg(&mc_dev->dev,
605
"Allocatable fsl-mc device unbound from fsl_mc_allocator driver");
606
}
607
608
static const struct fsl_mc_device_id match_id_table[] = {
609
{
610
.vendor = FSL_MC_VENDOR_FREESCALE,
611
.obj_type = "dpbp",
612
},
613
{
614
.vendor = FSL_MC_VENDOR_FREESCALE,
615
.obj_type = "dpmcp",
616
},
617
{
618
.vendor = FSL_MC_VENDOR_FREESCALE,
619
.obj_type = "dpcon",
620
},
621
{.vendor = 0x0},
622
};
623
624
static struct fsl_mc_driver fsl_mc_allocator_driver = {
625
.driver = {
626
.name = "fsl_mc_allocator",
627
.pm = NULL,
628
},
629
.match_id_table = match_id_table,
630
.probe = fsl_mc_allocator_probe,
631
.remove = fsl_mc_allocator_remove,
632
};
633
634
int __init fsl_mc_allocator_driver_init(void)
635
{
636
return fsl_mc_driver_register(&fsl_mc_allocator_driver);
637
}
638
639