Commit | Line | Data |
---|---|---|
f2f2726b GR |
1 | /* |
2 | * Freescale data path resource container (DPRC) driver | |
3 | * | |
4 | * Copyright (C) 2014 Freescale Semiconductor, Inc. | |
5 | * Author: German Rivera <German.Rivera@freescale.com> | |
6 | * | |
7 | * This file is licensed under the terms of the GNU General Public | |
8 | * License version 2. This program is licensed "as is" without any | |
9 | * warranty of any kind, whether express or implied. | |
10 | */ | |
11 | ||
12 | #include "../include/mc-private.h" | |
13 | #include "../include/mc-sys.h" | |
14 | #include <linux/module.h> | |
15 | #include <linux/slab.h> | |
16 | #include "dprc-cmd.h" | |
17 | ||
18 | struct dprc_child_objs { | |
19 | int child_count; | |
20 | struct dprc_obj_desc *child_array; | |
21 | }; | |
22 | ||
23 | static int __fsl_mc_device_remove_if_not_in_mc(struct device *dev, void *data) | |
24 | { | |
25 | int i; | |
26 | struct dprc_child_objs *objs; | |
27 | struct fsl_mc_device *mc_dev; | |
28 | ||
29 | WARN_ON(!dev); | |
30 | WARN_ON(!data); | |
31 | mc_dev = to_fsl_mc_device(dev); | |
32 | objs = data; | |
33 | ||
34 | for (i = 0; i < objs->child_count; i++) { | |
35 | struct dprc_obj_desc *obj_desc = &objs->child_array[i]; | |
36 | ||
37 | if (strlen(obj_desc->type) != 0 && | |
38 | FSL_MC_DEVICE_MATCH(mc_dev, obj_desc)) | |
39 | break; | |
40 | } | |
41 | ||
42 | if (i == objs->child_count) | |
43 | fsl_mc_device_remove(mc_dev); | |
44 | ||
45 | return 0; | |
46 | } | |
47 | ||
48 | static int __fsl_mc_device_remove(struct device *dev, void *data) | |
49 | { | |
50 | WARN_ON(!dev); | |
51 | WARN_ON(data); | |
52 | fsl_mc_device_remove(to_fsl_mc_device(dev)); | |
53 | return 0; | |
54 | } | |
55 | ||
56 | /** | |
57 | * dprc_remove_devices - Removes devices for objects removed from a DPRC | |
58 | * | |
59 | * @mc_bus_dev: pointer to the fsl-mc device that represents a DPRC object | |
60 | * @obj_desc_array: array of object descriptors for child objects currently | |
61 | * present in the DPRC in the MC. | |
62 | * @num_child_objects_in_mc: number of entries in obj_desc_array | |
63 | * | |
64 | * Synchronizes the state of the Linux bus driver with the actual state of | |
65 | * the MC by removing devices that represent MC objects that have | |
66 | * been dynamically removed in the physical DPRC. | |
67 | */ | |
68 | static void dprc_remove_devices(struct fsl_mc_device *mc_bus_dev, | |
69 | struct dprc_obj_desc *obj_desc_array, | |
70 | int num_child_objects_in_mc) | |
71 | { | |
72 | if (num_child_objects_in_mc != 0) { | |
73 | /* | |
74 | * Remove child objects that are in the DPRC in Linux, | |
75 | * but not in the MC: | |
76 | */ | |
77 | struct dprc_child_objs objs; | |
78 | ||
79 | objs.child_count = num_child_objects_in_mc; | |
80 | objs.child_array = obj_desc_array; | |
81 | device_for_each_child(&mc_bus_dev->dev, &objs, | |
82 | __fsl_mc_device_remove_if_not_in_mc); | |
83 | } else { | |
84 | /* | |
85 | * There are no child objects for this DPRC in the MC. | |
86 | * So, remove all the child devices from Linux: | |
87 | */ | |
88 | device_for_each_child(&mc_bus_dev->dev, NULL, | |
89 | __fsl_mc_device_remove); | |
90 | } | |
91 | } | |
92 | ||
93 | static int __fsl_mc_device_match(struct device *dev, void *data) | |
94 | { | |
95 | struct dprc_obj_desc *obj_desc = data; | |
96 | struct fsl_mc_device *mc_dev = to_fsl_mc_device(dev); | |
97 | ||
98 | return FSL_MC_DEVICE_MATCH(mc_dev, obj_desc); | |
99 | } | |
100 | ||
101 | static struct fsl_mc_device *fsl_mc_device_lookup(struct dprc_obj_desc | |
102 | *obj_desc, | |
103 | struct fsl_mc_device | |
104 | *mc_bus_dev) | |
105 | { | |
106 | struct device *dev; | |
107 | ||
108 | dev = device_find_child(&mc_bus_dev->dev, obj_desc, | |
109 | __fsl_mc_device_match); | |
110 | ||
111 | return dev ? to_fsl_mc_device(dev) : NULL; | |
112 | } | |
113 | ||
114 | /** | |
115 | * dprc_add_new_devices - Adds devices to the logical bus for a DPRC | |
116 | * | |
117 | * @mc_bus_dev: pointer to the fsl-mc device that represents a DPRC object | |
118 | * @obj_desc_array: array of device descriptors for child devices currently | |
119 | * present in the physical DPRC. | |
120 | * @num_child_objects_in_mc: number of entries in obj_desc_array | |
121 | * | |
122 | * Synchronizes the state of the Linux bus driver with the actual | |
123 | * state of the MC by adding objects that have been newly discovered | |
124 | * in the physical DPRC. | |
125 | */ | |
126 | static void dprc_add_new_devices(struct fsl_mc_device *mc_bus_dev, | |
127 | struct dprc_obj_desc *obj_desc_array, | |
128 | int num_child_objects_in_mc) | |
129 | { | |
130 | int error; | |
131 | int i; | |
132 | ||
133 | for (i = 0; i < num_child_objects_in_mc; i++) { | |
134 | struct fsl_mc_device *child_dev; | |
135 | struct fsl_mc_io *mc_io = NULL; | |
136 | struct dprc_obj_desc *obj_desc = &obj_desc_array[i]; | |
137 | ||
138 | if (strlen(obj_desc->type) == 0) | |
139 | continue; | |
140 | ||
141 | /* | |
142 | * Check if device is already known to Linux: | |
143 | */ | |
144 | child_dev = fsl_mc_device_lookup(obj_desc, mc_bus_dev); | |
145 | if (child_dev) | |
146 | continue; | |
147 | ||
148 | error = fsl_mc_device_add(obj_desc, mc_io, &mc_bus_dev->dev, | |
149 | &child_dev); | |
150 | if (error < 0) { | |
151 | if (mc_io) | |
152 | fsl_destroy_mc_io(mc_io); | |
153 | ||
154 | continue; | |
155 | } | |
156 | } | |
157 | } | |
158 | ||
197f4d6a GR |
159 | static void dprc_init_all_resource_pools(struct fsl_mc_device *mc_bus_dev) |
160 | { | |
161 | int pool_type; | |
162 | struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_bus_dev); | |
163 | ||
164 | for (pool_type = 0; pool_type < FSL_MC_NUM_POOL_TYPES; pool_type++) { | |
165 | struct fsl_mc_resource_pool *res_pool = | |
166 | &mc_bus->resource_pools[pool_type]; | |
167 | ||
168 | res_pool->type = pool_type; | |
169 | res_pool->max_count = 0; | |
170 | res_pool->free_count = 0; | |
171 | res_pool->mc_bus = mc_bus; | |
172 | INIT_LIST_HEAD(&res_pool->free_list); | |
173 | mutex_init(&res_pool->mutex); | |
174 | } | |
175 | } | |
176 | ||
177 | static void dprc_cleanup_resource_pool(struct fsl_mc_device *mc_bus_dev, | |
178 | enum fsl_mc_pool_type pool_type) | |
179 | { | |
180 | struct fsl_mc_resource *resource; | |
181 | struct fsl_mc_resource *next; | |
182 | struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_bus_dev); | |
183 | struct fsl_mc_resource_pool *res_pool = | |
184 | &mc_bus->resource_pools[pool_type]; | |
185 | int free_count = 0; | |
186 | ||
187 | WARN_ON(res_pool->type != pool_type); | |
188 | WARN_ON(res_pool->free_count != res_pool->max_count); | |
189 | ||
190 | list_for_each_entry_safe(resource, next, &res_pool->free_list, node) { | |
191 | free_count++; | |
192 | WARN_ON(resource->type != res_pool->type); | |
193 | WARN_ON(resource->parent_pool != res_pool); | |
194 | devm_kfree(&mc_bus_dev->dev, resource); | |
195 | } | |
196 | ||
197 | WARN_ON(free_count != res_pool->free_count); | |
198 | } | |
199 | ||
200 | static void dprc_cleanup_all_resource_pools(struct fsl_mc_device *mc_bus_dev) | |
201 | { | |
202 | int pool_type; | |
203 | ||
204 | for (pool_type = 0; pool_type < FSL_MC_NUM_POOL_TYPES; pool_type++) | |
205 | dprc_cleanup_resource_pool(mc_bus_dev, pool_type); | |
206 | } | |
207 | ||
208 | static void reorder_obj_desc_array(struct dprc_obj_desc *obj_desc_array, | |
209 | int num_devs) | |
210 | { | |
211 | struct dprc_obj_desc tmp; | |
212 | struct dprc_obj_desc *top_cursor = &obj_desc_array[0]; | |
213 | struct dprc_obj_desc *bottom_cursor = &obj_desc_array[num_devs - 1]; | |
214 | ||
215 | /* | |
216 | * Reorder entries in obj_desc_array so that all allocatable devices | |
217 | * are placed before all non-allocatable devices: | |
218 | * | |
219 | * Loop Invariant: everything before top_cursor is allocatable and | |
220 | * everything after bottom_cursor is non-allocatable. | |
221 | */ | |
222 | while (top_cursor < bottom_cursor) { | |
223 | if (FSL_MC_IS_ALLOCATABLE(top_cursor->type)) { | |
224 | top_cursor++; | |
225 | } else { | |
226 | if (FSL_MC_IS_ALLOCATABLE(bottom_cursor->type)) { | |
227 | tmp = *bottom_cursor; | |
228 | *bottom_cursor = *top_cursor; | |
229 | *top_cursor = tmp; | |
230 | top_cursor++; | |
231 | } | |
232 | ||
233 | bottom_cursor--; | |
234 | } | |
235 | } | |
236 | } | |
237 | ||
f2f2726b GR |
238 | /** |
239 | * dprc_scan_objects - Discover objects in a DPRC | |
240 | * | |
241 | * @mc_bus_dev: pointer to the fsl-mc device that represents a DPRC object | |
242 | * | |
243 | * Detects objects added and removed from a DPRC and synchronizes the | |
244 | * state of the Linux bus driver, MC by adding and removing | |
245 | * devices accordingly. | |
197f4d6a GR |
246 | * Two types of devices can be found in a DPRC: allocatable objects (e.g., |
247 | * dpbp, dpmcp) and non-allocatable devices (e.g., dprc, dpni). | |
248 | * All allocatable devices needed to be probed before all non-allocatable | |
249 | * devices, to ensure that device drivers for non-allocatable | |
250 | * devices can allocate any type of allocatable devices. | |
251 | * That is, we need to ensure that the corresponding resource pools are | |
252 | * populated before they can get allocation requests from probe callbacks | |
253 | * of the device drivers for the non-allocatable devices. | |
f2f2726b GR |
254 | */ |
255 | int dprc_scan_objects(struct fsl_mc_device *mc_bus_dev) | |
256 | { | |
257 | int num_child_objects; | |
258 | int dprc_get_obj_failures; | |
259 | int error; | |
260 | struct dprc_obj_desc *child_obj_desc_array = NULL; | |
261 | ||
262 | error = dprc_get_obj_count(mc_bus_dev->mc_io, | |
263 | mc_bus_dev->mc_handle, | |
264 | &num_child_objects); | |
265 | if (error < 0) { | |
266 | dev_err(&mc_bus_dev->dev, "dprc_get_obj_count() failed: %d\n", | |
267 | error); | |
268 | return error; | |
269 | } | |
270 | ||
271 | if (num_child_objects != 0) { | |
272 | int i; | |
273 | ||
274 | child_obj_desc_array = | |
275 | devm_kmalloc_array(&mc_bus_dev->dev, num_child_objects, | |
276 | sizeof(*child_obj_desc_array), | |
277 | GFP_KERNEL); | |
278 | if (!child_obj_desc_array) | |
279 | return -ENOMEM; | |
280 | ||
281 | /* | |
282 | * Discover objects currently present in the physical DPRC: | |
283 | */ | |
284 | dprc_get_obj_failures = 0; | |
285 | for (i = 0; i < num_child_objects; i++) { | |
286 | struct dprc_obj_desc *obj_desc = | |
287 | &child_obj_desc_array[i]; | |
288 | ||
289 | error = dprc_get_obj(mc_bus_dev->mc_io, | |
290 | mc_bus_dev->mc_handle, | |
291 | i, obj_desc); | |
292 | if (error < 0) { | |
293 | dev_err(&mc_bus_dev->dev, | |
294 | "dprc_get_obj(i=%d) failed: %d\n", | |
295 | i, error); | |
296 | /* | |
297 | * Mark the obj entry as "invalid", by using the | |
298 | * empty string as obj type: | |
299 | */ | |
300 | obj_desc->type[0] = '\0'; | |
301 | obj_desc->id = error; | |
302 | dprc_get_obj_failures++; | |
303 | continue; | |
304 | } | |
305 | ||
306 | dev_dbg(&mc_bus_dev->dev, | |
307 | "Discovered object: type %s, id %d\n", | |
308 | obj_desc->type, obj_desc->id); | |
309 | } | |
310 | ||
311 | if (dprc_get_obj_failures != 0) { | |
312 | dev_err(&mc_bus_dev->dev, | |
313 | "%d out of %d devices could not be retrieved\n", | |
314 | dprc_get_obj_failures, num_child_objects); | |
315 | } | |
197f4d6a GR |
316 | |
317 | reorder_obj_desc_array(child_obj_desc_array, num_child_objects); | |
f2f2726b GR |
318 | } |
319 | ||
320 | dprc_remove_devices(mc_bus_dev, child_obj_desc_array, | |
321 | num_child_objects); | |
322 | ||
323 | dprc_add_new_devices(mc_bus_dev, child_obj_desc_array, | |
324 | num_child_objects); | |
325 | ||
326 | if (child_obj_desc_array) | |
327 | devm_kfree(&mc_bus_dev->dev, child_obj_desc_array); | |
328 | ||
329 | return 0; | |
330 | } | |
331 | EXPORT_SYMBOL_GPL(dprc_scan_objects); | |
332 | ||
333 | /** | |
334 | * dprc_scan_container - Scans a physical DPRC and synchronizes Linux bus state | |
335 | * | |
336 | * @mc_bus_dev: pointer to the fsl-mc device that represents a DPRC object | |
337 | * | |
338 | * Scans the physical DPRC and synchronizes the state of the Linux | |
339 | * bus driver with the actual state of the MC by adding and removing | |
340 | * devices as appropriate. | |
341 | */ | |
342 | int dprc_scan_container(struct fsl_mc_device *mc_bus_dev) | |
343 | { | |
344 | int error; | |
345 | struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_bus_dev); | |
346 | ||
197f4d6a GR |
347 | dprc_init_all_resource_pools(mc_bus_dev); |
348 | ||
f2f2726b GR |
349 | /* |
350 | * Discover objects in the DPRC: | |
351 | */ | |
352 | mutex_lock(&mc_bus->scan_mutex); | |
353 | error = dprc_scan_objects(mc_bus_dev); | |
354 | mutex_unlock(&mc_bus->scan_mutex); | |
197f4d6a GR |
355 | if (error < 0) |
356 | goto error; | |
357 | ||
358 | return 0; | |
359 | error: | |
360 | dprc_cleanup_all_resource_pools(mc_bus_dev); | |
f2f2726b GR |
361 | return error; |
362 | } | |
363 | EXPORT_SYMBOL_GPL(dprc_scan_container); | |
364 | ||
365 | /** | |
366 | * dprc_probe - callback invoked when a DPRC is being bound to this driver | |
367 | * | |
368 | * @mc_dev: Pointer to fsl-mc device representing a DPRC | |
369 | * | |
370 | * It opens the physical DPRC in the MC. | |
371 | * It scans the DPRC to discover the MC objects contained in it. | |
197f4d6a GR |
372 | * It creates the interrupt pool for the MC bus associated with the DPRC. |
373 | * It configures the interrupts for the DPRC device itself. | |
f2f2726b GR |
374 | */ |
375 | static int dprc_probe(struct fsl_mc_device *mc_dev) | |
376 | { | |
377 | int error; | |
378 | size_t region_size; | |
379 | struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_dev); | |
380 | ||
381 | if (WARN_ON(strcmp(mc_dev->obj_desc.type, "dprc") != 0)) | |
382 | return -EINVAL; | |
383 | ||
384 | if (!mc_dev->mc_io) { | |
385 | /* | |
386 | * This is a child DPRC: | |
387 | */ | |
388 | if (WARN_ON(mc_dev->obj_desc.region_count == 0)) | |
389 | return -EINVAL; | |
390 | ||
391 | region_size = mc_dev->regions[0].end - | |
392 | mc_dev->regions[0].start + 1; | |
393 | ||
394 | error = fsl_create_mc_io(&mc_dev->dev, | |
395 | mc_dev->regions[0].start, | |
396 | region_size, | |
197f4d6a | 397 | NULL, 0, &mc_dev->mc_io); |
f2f2726b GR |
398 | if (error < 0) |
399 | return error; | |
400 | } | |
401 | ||
402 | error = dprc_open(mc_dev->mc_io, mc_dev->obj_desc.id, | |
403 | &mc_dev->mc_handle); | |
404 | if (error < 0) { | |
405 | dev_err(&mc_dev->dev, "dprc_open() failed: %d\n", error); | |
406 | goto error_cleanup_mc_io; | |
407 | } | |
408 | ||
409 | mutex_init(&mc_bus->scan_mutex); | |
410 | ||
411 | /* | |
412 | * Discover MC objects in DPRC object: | |
413 | */ | |
414 | error = dprc_scan_container(mc_dev); | |
415 | if (error < 0) | |
416 | goto error_cleanup_open; | |
417 | ||
418 | dev_info(&mc_dev->dev, "DPRC device bound to driver"); | |
419 | return 0; | |
420 | ||
421 | error_cleanup_open: | |
422 | (void)dprc_close(mc_dev->mc_io, mc_dev->mc_handle); | |
423 | ||
424 | error_cleanup_mc_io: | |
425 | fsl_destroy_mc_io(mc_dev->mc_io); | |
426 | return error; | |
427 | } | |
428 | ||
429 | /** | |
430 | * dprc_remove - callback invoked when a DPRC is being unbound from this driver | |
431 | * | |
432 | * @mc_dev: Pointer to fsl-mc device representing the DPRC | |
433 | * | |
434 | * It removes the DPRC's child objects from Linux (not from the MC) and | |
435 | * closes the DPRC device in the MC. | |
197f4d6a GR |
436 | * It tears down the interrupts that were configured for the DPRC device. |
437 | * It destroys the interrupt pool associated with this MC bus. | |
f2f2726b GR |
438 | */ |
439 | static int dprc_remove(struct fsl_mc_device *mc_dev) | |
440 | { | |
441 | int error; | |
442 | ||
443 | if (WARN_ON(strcmp(mc_dev->obj_desc.type, "dprc") != 0)) | |
444 | return -EINVAL; | |
445 | if (WARN_ON(!mc_dev->mc_io)) | |
446 | return -EINVAL; | |
447 | ||
448 | device_for_each_child(&mc_dev->dev, NULL, __fsl_mc_device_remove); | |
197f4d6a | 449 | dprc_cleanup_all_resource_pools(mc_dev); |
f2f2726b GR |
450 | error = dprc_close(mc_dev->mc_io, mc_dev->mc_handle); |
451 | if (error < 0) | |
452 | dev_err(&mc_dev->dev, "dprc_close() failed: %d\n", error); | |
453 | ||
454 | dev_info(&mc_dev->dev, "DPRC device unbound from driver"); | |
455 | return 0; | |
456 | } | |
457 | ||
458 | static const struct fsl_mc_device_match_id match_id_table[] = { | |
459 | { | |
460 | .vendor = FSL_MC_VENDOR_FREESCALE, | |
461 | .obj_type = "dprc", | |
462 | .ver_major = DPRC_VER_MAJOR, | |
463 | .ver_minor = DPRC_VER_MINOR}, | |
464 | {.vendor = 0x0}, | |
465 | }; | |
466 | ||
467 | static struct fsl_mc_driver dprc_driver = { | |
468 | .driver = { | |
469 | .name = FSL_MC_DPRC_DRIVER_NAME, | |
470 | .owner = THIS_MODULE, | |
471 | .pm = NULL, | |
472 | }, | |
473 | .match_id_table = match_id_table, | |
474 | .probe = dprc_probe, | |
475 | .remove = dprc_remove, | |
476 | }; | |
477 | ||
478 | int __init dprc_driver_init(void) | |
479 | { | |
480 | return fsl_mc_driver_register(&dprc_driver); | |
481 | } | |
482 | ||
483 | void __exit dprc_driver_exit(void) | |
484 | { | |
485 | fsl_mc_driver_unregister(&dprc_driver); | |
486 | } |