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 
__fsl_mc_device_remove_if_not_in_mc(struct device * dev,void * data)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 
__fsl_mc_device_remove(struct device * dev,void * data)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  */
dprc_remove_devices(struct fsl_mc_device * mc_bus_dev,struct dprc_obj_desc * obj_desc_array,int num_child_objects_in_mc)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 
__fsl_mc_device_match(struct device * dev,void * data)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 
fsl_mc_device_lookup(struct dprc_obj_desc * obj_desc,struct fsl_mc_device * mc_bus_dev)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  * 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  */
check_plugged_state_change(struct fsl_mc_device * mc_dev,struct dprc_obj_desc * obj_desc)125 static void check_plugged_state_change(struct fsl_mc_device *mc_dev,
126 				       struct dprc_obj_desc *obj_desc)
127 {
128 	int error;
129 	u32 plugged_flag_at_mc =
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 
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  */
dprc_add_new_devices(struct fsl_mc_device * mc_bus_dev,struct dprc_obj_desc * obj_desc_array,int num_child_objects_in_mc)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;
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);
179 		if (child_dev) {
180 			check_plugged_state_change(child_dev, obj_desc);
181 			continue;
182 		}
183 
184 		error = fsl_mc_device_add(obj_desc, NULL, &mc_bus_dev->dev,
185 					  &child_dev);
186 		if (error < 0)
187 			continue;
188 	}
189 }
190 
dprc_init_all_resource_pools(struct fsl_mc_device * mc_bus_dev)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 
dprc_cleanup_resource_pool(struct fsl_mc_device * mc_bus_dev,enum fsl_mc_pool_type pool_type)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 
dprc_cleanup_all_resource_pools(struct fsl_mc_device * mc_bus_dev)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 
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.
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.
256  */
dprc_scan_objects(struct fsl_mc_device * mc_bus_dev)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,
265 				   0,
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,
293 					     0,
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  */
dprc_scan_container(struct fsl_mc_device * mc_bus_dev)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 
349 	dprc_init_all_resource_pools(mc_bus_dev);
350 
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);
357 	if (error < 0)
358 		goto error;
359 
360 	return 0;
361 error:
362 	dprc_cleanup_all_resource_pools(mc_bus_dev);
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.
374  * It creates the interrupt pool for the MC bus associated with the DPRC.
375  * It configures the interrupts for the DPRC device itself.
376  */
dprc_probe(struct fsl_mc_device * mc_dev)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,
399 					 NULL, 0, &mc_dev->mc_io);
400 		if (error < 0)
401 			return error;
402 	}
403 
404 	error = dprc_open(mc_dev->mc_io, 0, mc_dev->obj_desc.id,
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:
424 	(void)dprc_close(mc_dev->mc_io, 0, mc_dev->mc_handle);
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.
438  * It tears down the interrupts that were configured for the DPRC device.
439  * It destroys the interrupt pool associated with this MC bus.
440  */
dprc_remove(struct fsl_mc_device * mc_dev)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);
451 	dprc_cleanup_all_resource_pools(mc_dev);
452 	error = dprc_close(mc_dev->mc_io, 0, mc_dev->mc_handle);
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 
dprc_driver_init(void)480 int __init dprc_driver_init(void)
481 {
482 	return fsl_mc_driver_register(&dprc_driver);
483 }
484 
dprc_driver_exit(void)485 void dprc_driver_exit(void)
486 {
487 	fsl_mc_driver_unregister(&dprc_driver);
488 }
489