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 | ||
1663e809 GR |
114 | /** |
115 | * check_plugged_state_change - Check change in an MC object's plugged state | |
116 | * | |
117 | * @mc_dev: pointer to the fsl-mc device for a given MC object | |
118 | * @obj_desc: pointer to the MC object's descriptor in the MC | |
119 | * | |
120 | * If the plugged state has changed from unplugged to plugged, the fsl-mc | |
121 | * device is bound to the corresponding device driver. | |
122 | * If the plugged state has changed from plugged to unplugged, the fsl-mc | |
123 | * device is unbound from the corresponding device driver. | |
124 | */ | |
125 | static void check_plugged_state_change(struct fsl_mc_device *mc_dev, | |
126 | struct dprc_obj_desc *obj_desc) | |
127 | { | |
128 | int error; | |
ba72f25b | 129 | u32 plugged_flag_at_mc = |
1663e809 GR |
130 | (obj_desc->state & DPRC_OBJ_STATE_PLUGGED); |
131 | ||
132 | if (plugged_flag_at_mc != | |
133 | (mc_dev->obj_desc.state & DPRC_OBJ_STATE_PLUGGED)) { | |
134 | if (plugged_flag_at_mc) { | |
135 | mc_dev->obj_desc.state |= DPRC_OBJ_STATE_PLUGGED; | |
136 | error = device_attach(&mc_dev->dev); | |
137 | if (error < 0) { | |
138 | dev_err(&mc_dev->dev, | |
139 | "device_attach() failed: %d\n", | |
140 | error); | |
141 | } | |
142 | } else { | |
143 | mc_dev->obj_desc.state &= ~DPRC_OBJ_STATE_PLUGGED; | |
144 | device_release_driver(&mc_dev->dev); | |
145 | } | |
146 | } | |
147 | } | |
148 | ||
f2f2726b GR |
149 | /** |
150 | * dprc_add_new_devices - Adds devices to the logical bus for a DPRC | |
151 | * | |
152 | * @mc_bus_dev: pointer to the fsl-mc device that represents a DPRC object | |
153 | * @obj_desc_array: array of device descriptors for child devices currently | |
154 | * present in the physical DPRC. | |
155 | * @num_child_objects_in_mc: number of entries in obj_desc_array | |
156 | * | |
157 | * Synchronizes the state of the Linux bus driver with the actual | |
158 | * state of the MC by adding objects that have been newly discovered | |
159 | * in the physical DPRC. | |
160 | */ | |
161 | static void dprc_add_new_devices(struct fsl_mc_device *mc_bus_dev, | |
162 | struct dprc_obj_desc *obj_desc_array, | |
163 | int num_child_objects_in_mc) | |
164 | { | |
165 | int error; | |
166 | int i; | |
167 | ||
168 | for (i = 0; i < num_child_objects_in_mc; i++) { | |
169 | struct fsl_mc_device *child_dev; | |
f2f2726b GR |
170 | struct dprc_obj_desc *obj_desc = &obj_desc_array[i]; |
171 | ||
172 | if (strlen(obj_desc->type) == 0) | |
173 | continue; | |
174 | ||
175 | /* | |
176 | * Check if device is already known to Linux: | |
177 | */ | |
178 | child_dev = fsl_mc_device_lookup(obj_desc, mc_bus_dev); | |
1663e809 GR |
179 | if (child_dev) { |
180 | check_plugged_state_change(child_dev, obj_desc); | |
f2f2726b | 181 | continue; |
1663e809 | 182 | } |
f2f2726b | 183 | |
2bdc55d9 | 184 | error = fsl_mc_device_add(obj_desc, NULL, &mc_bus_dev->dev, |
f2f2726b | 185 | &child_dev); |
2bdc55d9 | 186 | if (error < 0) |
f2f2726b | 187 | continue; |
f2f2726b GR |
188 | } |
189 | } | |
190 | ||
197f4d6a GR |
191 | static void dprc_init_all_resource_pools(struct fsl_mc_device *mc_bus_dev) |
192 | { | |
193 | int pool_type; | |
194 | struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_bus_dev); | |
195 | ||
196 | for (pool_type = 0; pool_type < FSL_MC_NUM_POOL_TYPES; pool_type++) { | |
197 | struct fsl_mc_resource_pool *res_pool = | |
198 | &mc_bus->resource_pools[pool_type]; | |
199 | ||
200 | res_pool->type = pool_type; | |
201 | res_pool->max_count = 0; | |
202 | res_pool->free_count = 0; | |
203 | res_pool->mc_bus = mc_bus; | |
204 | INIT_LIST_HEAD(&res_pool->free_list); | |
205 | mutex_init(&res_pool->mutex); | |
206 | } | |
207 | } | |
208 | ||
209 | static void dprc_cleanup_resource_pool(struct fsl_mc_device *mc_bus_dev, | |
210 | enum fsl_mc_pool_type pool_type) | |
211 | { | |
212 | struct fsl_mc_resource *resource; | |
213 | struct fsl_mc_resource *next; | |
214 | struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_bus_dev); | |
215 | struct fsl_mc_resource_pool *res_pool = | |
216 | &mc_bus->resource_pools[pool_type]; | |
217 | int free_count = 0; | |
218 | ||
219 | WARN_ON(res_pool->type != pool_type); | |
220 | WARN_ON(res_pool->free_count != res_pool->max_count); | |
221 | ||
222 | list_for_each_entry_safe(resource, next, &res_pool->free_list, node) { | |
223 | free_count++; | |
224 | WARN_ON(resource->type != res_pool->type); | |
225 | WARN_ON(resource->parent_pool != res_pool); | |
226 | devm_kfree(&mc_bus_dev->dev, resource); | |
227 | } | |
228 | ||
229 | WARN_ON(free_count != res_pool->free_count); | |
230 | } | |
231 | ||
232 | static void dprc_cleanup_all_resource_pools(struct fsl_mc_device *mc_bus_dev) | |
233 | { | |
234 | int pool_type; | |
235 | ||
236 | for (pool_type = 0; pool_type < FSL_MC_NUM_POOL_TYPES; pool_type++) | |
237 | dprc_cleanup_resource_pool(mc_bus_dev, pool_type); | |
238 | } | |
239 | ||
f2f2726b GR |
240 | /** |
241 | * dprc_scan_objects - Discover objects in a DPRC | |
242 | * | |
243 | * @mc_bus_dev: pointer to the fsl-mc device that represents a DPRC object | |
244 | * | |
245 | * Detects objects added and removed from a DPRC and synchronizes the | |
246 | * state of the Linux bus driver, MC by adding and removing | |
247 | * devices accordingly. | |
197f4d6a GR |
248 | * Two types of devices can be found in a DPRC: allocatable objects (e.g., |
249 | * dpbp, dpmcp) and non-allocatable devices (e.g., dprc, dpni). | |
250 | * All allocatable devices needed to be probed before all non-allocatable | |
251 | * devices, to ensure that device drivers for non-allocatable | |
252 | * devices can allocate any type of allocatable devices. | |
253 | * That is, we need to ensure that the corresponding resource pools are | |
254 | * populated before they can get allocation requests from probe callbacks | |
255 | * of the device drivers for the non-allocatable devices. | |
f2f2726b GR |
256 | */ |
257 | int dprc_scan_objects(struct fsl_mc_device *mc_bus_dev) | |
258 | { | |
259 | int num_child_objects; | |
260 | int dprc_get_obj_failures; | |
261 | int error; | |
262 | struct dprc_obj_desc *child_obj_desc_array = NULL; | |
263 | ||
264 | error = dprc_get_obj_count(mc_bus_dev->mc_io, | |
1ee695fa | 265 | 0, |
f2f2726b GR |
266 | mc_bus_dev->mc_handle, |
267 | &num_child_objects); | |
268 | if (error < 0) { | |
269 | dev_err(&mc_bus_dev->dev, "dprc_get_obj_count() failed: %d\n", | |
270 | error); | |
271 | return error; | |
272 | } | |
273 | ||
274 | if (num_child_objects != 0) { | |
275 | int i; | |
276 | ||
277 | child_obj_desc_array = | |
278 | devm_kmalloc_array(&mc_bus_dev->dev, num_child_objects, | |
279 | sizeof(*child_obj_desc_array), | |
280 | GFP_KERNEL); | |
281 | if (!child_obj_desc_array) | |
282 | return -ENOMEM; | |
283 | ||
284 | /* | |
285 | * Discover objects currently present in the physical DPRC: | |
286 | */ | |
287 | dprc_get_obj_failures = 0; | |
288 | for (i = 0; i < num_child_objects; i++) { | |
289 | struct dprc_obj_desc *obj_desc = | |
290 | &child_obj_desc_array[i]; | |
291 | ||
292 | error = dprc_get_obj(mc_bus_dev->mc_io, | |
1ee695fa | 293 | 0, |
f2f2726b GR |
294 | mc_bus_dev->mc_handle, |
295 | i, obj_desc); | |
296 | if (error < 0) { | |
297 | dev_err(&mc_bus_dev->dev, | |
298 | "dprc_get_obj(i=%d) failed: %d\n", | |
299 | i, error); | |
300 | /* | |
301 | * Mark the obj entry as "invalid", by using the | |
302 | * empty string as obj type: | |
303 | */ | |
304 | obj_desc->type[0] = '\0'; | |
305 | obj_desc->id = error; | |
306 | dprc_get_obj_failures++; | |
307 | continue; | |
308 | } | |
309 | ||
310 | dev_dbg(&mc_bus_dev->dev, | |
311 | "Discovered object: type %s, id %d\n", | |
312 | obj_desc->type, obj_desc->id); | |
313 | } | |
314 | ||
315 | if (dprc_get_obj_failures != 0) { | |
316 | dev_err(&mc_bus_dev->dev, | |
317 | "%d out of %d devices could not be retrieved\n", | |
318 | dprc_get_obj_failures, num_child_objects); | |
319 | } | |
320 | } | |
321 | ||
322 | dprc_remove_devices(mc_bus_dev, child_obj_desc_array, | |
323 | num_child_objects); | |
324 | ||
325 | dprc_add_new_devices(mc_bus_dev, child_obj_desc_array, | |
326 | num_child_objects); | |
327 | ||
328 | if (child_obj_desc_array) | |
329 | devm_kfree(&mc_bus_dev->dev, child_obj_desc_array); | |
330 | ||
331 | return 0; | |
332 | } | |
333 | EXPORT_SYMBOL_GPL(dprc_scan_objects); | |
334 | ||
335 | /** | |
336 | * dprc_scan_container - Scans a physical DPRC and synchronizes Linux bus state | |
337 | * | |
338 | * @mc_bus_dev: pointer to the fsl-mc device that represents a DPRC object | |
339 | * | |
340 | * Scans the physical DPRC and synchronizes the state of the Linux | |
341 | * bus driver with the actual state of the MC by adding and removing | |
342 | * devices as appropriate. | |
343 | */ | |
344 | int dprc_scan_container(struct fsl_mc_device *mc_bus_dev) | |
345 | { | |
346 | int error; | |
347 | struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_bus_dev); | |
348 | ||
197f4d6a GR |
349 | dprc_init_all_resource_pools(mc_bus_dev); |
350 | ||
f2f2726b GR |
351 | /* |
352 | * Discover objects in the DPRC: | |
353 | */ | |
354 | mutex_lock(&mc_bus->scan_mutex); | |
355 | error = dprc_scan_objects(mc_bus_dev); | |
356 | mutex_unlock(&mc_bus->scan_mutex); | |
197f4d6a GR |
357 | if (error < 0) |
358 | goto error; | |
359 | ||
360 | return 0; | |
361 | error: | |
362 | dprc_cleanup_all_resource_pools(mc_bus_dev); | |
f2f2726b GR |
363 | return error; |
364 | } | |
365 | EXPORT_SYMBOL_GPL(dprc_scan_container); | |
366 | ||
367 | /** | |
368 | * dprc_probe - callback invoked when a DPRC is being bound to this driver | |
369 | * | |
370 | * @mc_dev: Pointer to fsl-mc device representing a DPRC | |
371 | * | |
372 | * It opens the physical DPRC in the MC. | |
373 | * It scans the DPRC to discover the MC objects contained in it. | |
197f4d6a GR |
374 | * It creates the interrupt pool for the MC bus associated with the DPRC. |
375 | * It configures the interrupts for the DPRC device itself. | |
f2f2726b GR |
376 | */ |
377 | static int dprc_probe(struct fsl_mc_device *mc_dev) | |
378 | { | |
379 | int error; | |
380 | size_t region_size; | |
381 | struct fsl_mc_bus *mc_bus = to_fsl_mc_bus(mc_dev); | |
382 | ||
383 | if (WARN_ON(strcmp(mc_dev->obj_desc.type, "dprc") != 0)) | |
384 | return -EINVAL; | |
385 | ||
386 | if (!mc_dev->mc_io) { | |
387 | /* | |
388 | * This is a child DPRC: | |
389 | */ | |
390 | if (WARN_ON(mc_dev->obj_desc.region_count == 0)) | |
391 | return -EINVAL; | |
392 | ||
393 | region_size = mc_dev->regions[0].end - | |
394 | mc_dev->regions[0].start + 1; | |
395 | ||
396 | error = fsl_create_mc_io(&mc_dev->dev, | |
397 | mc_dev->regions[0].start, | |
398 | region_size, | |
197f4d6a | 399 | NULL, 0, &mc_dev->mc_io); |
f2f2726b GR |
400 | if (error < 0) |
401 | return error; | |
402 | } | |
403 | ||
1ee695fa | 404 | error = dprc_open(mc_dev->mc_io, 0, mc_dev->obj_desc.id, |
f2f2726b GR |
405 | &mc_dev->mc_handle); |
406 | if (error < 0) { | |
407 | dev_err(&mc_dev->dev, "dprc_open() failed: %d\n", error); | |
408 | goto error_cleanup_mc_io; | |
409 | } | |
410 | ||
411 | mutex_init(&mc_bus->scan_mutex); | |
412 | ||
413 | /* | |
414 | * Discover MC objects in DPRC object: | |
415 | */ | |
416 | error = dprc_scan_container(mc_dev); | |
417 | if (error < 0) | |
418 | goto error_cleanup_open; | |
419 | ||
420 | dev_info(&mc_dev->dev, "DPRC device bound to driver"); | |
421 | return 0; | |
422 | ||
423 | error_cleanup_open: | |
1ee695fa | 424 | (void)dprc_close(mc_dev->mc_io, 0, mc_dev->mc_handle); |
f2f2726b GR |
425 | |
426 | error_cleanup_mc_io: | |
427 | fsl_destroy_mc_io(mc_dev->mc_io); | |
428 | return error; | |
429 | } | |
430 | ||
431 | /** | |
432 | * dprc_remove - callback invoked when a DPRC is being unbound from this driver | |
433 | * | |
434 | * @mc_dev: Pointer to fsl-mc device representing the DPRC | |
435 | * | |
436 | * It removes the DPRC's child objects from Linux (not from the MC) and | |
437 | * closes the DPRC device in the MC. | |
197f4d6a GR |
438 | * It tears down the interrupts that were configured for the DPRC device. |
439 | * It destroys the interrupt pool associated with this MC bus. | |
f2f2726b GR |
440 | */ |
441 | static int dprc_remove(struct fsl_mc_device *mc_dev) | |
442 | { | |
443 | int error; | |
444 | ||
445 | if (WARN_ON(strcmp(mc_dev->obj_desc.type, "dprc") != 0)) | |
446 | return -EINVAL; | |
447 | if (WARN_ON(!mc_dev->mc_io)) | |
448 | return -EINVAL; | |
449 | ||
450 | device_for_each_child(&mc_dev->dev, NULL, __fsl_mc_device_remove); | |
197f4d6a | 451 | dprc_cleanup_all_resource_pools(mc_dev); |
1ee695fa | 452 | error = dprc_close(mc_dev->mc_io, 0, mc_dev->mc_handle); |
f2f2726b GR |
453 | if (error < 0) |
454 | dev_err(&mc_dev->dev, "dprc_close() failed: %d\n", error); | |
455 | ||
456 | dev_info(&mc_dev->dev, "DPRC device unbound from driver"); | |
457 | return 0; | |
458 | } | |
459 | ||
460 | static const struct fsl_mc_device_match_id match_id_table[] = { | |
461 | { | |
462 | .vendor = FSL_MC_VENDOR_FREESCALE, | |
463 | .obj_type = "dprc", | |
464 | .ver_major = DPRC_VER_MAJOR, | |
465 | .ver_minor = DPRC_VER_MINOR}, | |
466 | {.vendor = 0x0}, | |
467 | }; | |
468 | ||
469 | static struct fsl_mc_driver dprc_driver = { | |
470 | .driver = { | |
471 | .name = FSL_MC_DPRC_DRIVER_NAME, | |
472 | .owner = THIS_MODULE, | |
473 | .pm = NULL, | |
474 | }, | |
475 | .match_id_table = match_id_table, | |
476 | .probe = dprc_probe, | |
477 | .remove = dprc_remove, | |
478 | }; | |
479 | ||
480 | int __init dprc_driver_init(void) | |
481 | { | |
482 | return fsl_mc_driver_register(&dprc_driver); | |
483 | } | |
484 | ||
3c7b67f9 | 485 | void dprc_driver_exit(void) |
f2f2726b GR |
486 | { |
487 | fsl_mc_driver_unregister(&dprc_driver); | |
488 | } |