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 	uint32_t 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 				   mc_bus_dev->mc_handle,
266 				   &num_child_objects);
267 	if (error < 0) {
268 		dev_err(&mc_bus_dev->dev, "dprc_get_obj_count() failed: %d\n",
269 			error);
270 		return error;
271 	}
272 
273 	if (num_child_objects != 0) {
274 		int i;
275 
276 		child_obj_desc_array =
277 		    devm_kmalloc_array(&mc_bus_dev->dev, num_child_objects,
278 				       sizeof(*child_obj_desc_array),
279 				       GFP_KERNEL);
280 		if (!child_obj_desc_array)
281 			return -ENOMEM;
282 
283 		/*
284 		 * Discover objects currently present in the physical DPRC:
285 		 */
286 		dprc_get_obj_failures = 0;
287 		for (i = 0; i < num_child_objects; i++) {
288 			struct dprc_obj_desc *obj_desc =
289 			    &child_obj_desc_array[i];
290 
291 			error = dprc_get_obj(mc_bus_dev->mc_io,
292 					     mc_bus_dev->mc_handle,
293 					     i, obj_desc);
294 			if (error < 0) {
295 				dev_err(&mc_bus_dev->dev,
296 					"dprc_get_obj(i=%d) failed: %d\n",
297 					i, error);
298 				/*
299 				 * Mark the obj entry as "invalid", by using the
300 				 * empty string as obj type:
301 				 */
302 				obj_desc->type[0] = '\0';
303 				obj_desc->id = error;
304 				dprc_get_obj_failures++;
305 				continue;
306 			}
307 
308 			dev_dbg(&mc_bus_dev->dev,
309 				"Discovered object: type %s, id %d\n",
310 				obj_desc->type, obj_desc->id);
311 		}
312 
313 		if (dprc_get_obj_failures != 0) {
314 			dev_err(&mc_bus_dev->dev,
315 				"%d out of %d devices could not be retrieved\n",
316 				dprc_get_obj_failures, num_child_objects);
317 		}
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  */
dprc_scan_container(struct fsl_mc_device * mc_bus_dev)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 
347 	dprc_init_all_resource_pools(mc_bus_dev);
348 
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);
355 	if (error < 0)
356 		goto error;
357 
358 	return 0;
359 error:
360 	dprc_cleanup_all_resource_pools(mc_bus_dev);
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.
372  * It creates the interrupt pool for the MC bus associated with the DPRC.
373  * It configures the interrupts for the DPRC device itself.
374  */
dprc_probe(struct fsl_mc_device * mc_dev)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,
397 					 NULL, 0, &mc_dev->mc_io);
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.
436  * It tears down the interrupts that were configured for the DPRC device.
437  * It destroys the interrupt pool associated with this MC bus.
438  */
dprc_remove(struct fsl_mc_device * mc_dev)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);
449 	dprc_cleanup_all_resource_pools(mc_dev);
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 
dprc_driver_init(void)478 int __init dprc_driver_init(void)
479 {
480 	return fsl_mc_driver_register(&dprc_driver);
481 }
482 
dprc_driver_exit(void)483 void __exit dprc_driver_exit(void)
484 {
485 	fsl_mc_driver_unregister(&dprc_driver);
486 }
487