]>
Commit | Line | Data |
---|---|---|
2a41e607 RK |
1 | /* |
2 | * Componentized device handling. | |
3 | * | |
4 | * This program is free software; you can redistribute it and/or modify | |
5 | * it under the terms of the GNU General Public License version 2 as | |
6 | * published by the Free Software Foundation. | |
7 | * | |
8 | * This is work in progress. We gather up the component devices into a list, | |
9 | * and bind them when instructed. At the moment, we're specific to the DRM | |
10 | * subsystem, and only handles one master device, but this doesn't have to be | |
11 | * the case. | |
12 | */ | |
13 | #include <linux/component.h> | |
14 | #include <linux/device.h> | |
15 | #include <linux/kref.h> | |
16 | #include <linux/list.h> | |
17 | #include <linux/module.h> | |
18 | #include <linux/mutex.h> | |
19 | #include <linux/slab.h> | |
20 | ||
ffc30b74 RK |
21 | struct component; |
22 | ||
6955b582 RK |
23 | struct component_match { |
24 | size_t alloc; | |
25 | size_t num; | |
26 | struct { | |
27 | void *data; | |
28 | int (*fn)(struct device *, void *); | |
ffc30b74 RK |
29 | struct component *component; |
30 | bool duplicate; | |
6955b582 RK |
31 | } compare[0]; |
32 | }; | |
33 | ||
2a41e607 RK |
34 | struct master { |
35 | struct list_head node; | |
2a41e607 RK |
36 | bool bound; |
37 | ||
38 | const struct component_master_ops *ops; | |
39 | struct device *dev; | |
6955b582 | 40 | struct component_match *match; |
2a41e607 RK |
41 | }; |
42 | ||
43 | struct component { | |
44 | struct list_head node; | |
2a41e607 RK |
45 | struct master *master; |
46 | bool bound; | |
47 | ||
48 | const struct component_ops *ops; | |
49 | struct device *dev; | |
50 | }; | |
51 | ||
52 | static DEFINE_MUTEX(component_mutex); | |
53 | static LIST_HEAD(component_list); | |
54 | static LIST_HEAD(masters); | |
55 | ||
56 | static struct master *__master_find(struct device *dev, | |
57 | const struct component_master_ops *ops) | |
58 | { | |
59 | struct master *m; | |
60 | ||
61 | list_for_each_entry(m, &masters, node) | |
62 | if (m->dev == dev && (!ops || m->ops == ops)) | |
63 | return m; | |
64 | ||
65 | return NULL; | |
66 | } | |
67 | ||
ffc30b74 | 68 | static struct component *find_component(struct master *master, |
2a41e607 RK |
69 | int (*compare)(struct device *, void *), void *compare_data) |
70 | { | |
71 | struct component *c; | |
2a41e607 RK |
72 | |
73 | list_for_each_entry(c, &component_list, node) { | |
fcbcebce | 74 | if (c->master && c->master != master) |
2a41e607 RK |
75 | continue; |
76 | ||
ffc30b74 RK |
77 | if (compare(c->dev, compare_data)) |
78 | return c; | |
2a41e607 RK |
79 | } |
80 | ||
ffc30b74 | 81 | return NULL; |
2a41e607 | 82 | } |
2a41e607 | 83 | |
6955b582 RK |
84 | static int find_components(struct master *master) |
85 | { | |
86 | struct component_match *match = master->match; | |
87 | size_t i; | |
88 | int ret = 0; | |
89 | ||
6955b582 RK |
90 | /* |
91 | * Scan the array of match functions and attach | |
92 | * any components which are found to this master. | |
93 | */ | |
94 | for (i = 0; i < match->num; i++) { | |
ffc30b74 RK |
95 | struct component *c; |
96 | ||
97 | dev_dbg(master->dev, "Looking for component %zu\n", i); | |
98 | ||
99 | if (match->compare[i].component) | |
100 | continue; | |
101 | ||
102 | c = find_component(master, match->compare[i].fn, | |
103 | match->compare[i].data); | |
104 | if (!c) { | |
105 | ret = -ENXIO; | |
6955b582 | 106 | break; |
ffc30b74 RK |
107 | } |
108 | ||
109 | dev_dbg(master->dev, "found component %s, duplicate %u\n", dev_name(c->dev), !!c->master); | |
110 | ||
111 | /* Attach this component to the master */ | |
112 | match->compare[i].duplicate = !!c->master; | |
113 | match->compare[i].component = c; | |
114 | c->master = master; | |
6955b582 RK |
115 | } |
116 | return ret; | |
117 | } | |
118 | ||
ffc30b74 RK |
119 | /* Detach component from associated master */ |
120 | static void remove_component(struct master *master, struct component *c) | |
2a41e607 | 121 | { |
ffc30b74 | 122 | size_t i; |
2a41e607 | 123 | |
ffc30b74 RK |
124 | /* Detach the component from this master. */ |
125 | for (i = 0; i < master->match->num; i++) | |
126 | if (master->match->compare[i].component == c) | |
127 | master->match->compare[i].component = NULL; | |
2a41e607 RK |
128 | } |
129 | ||
130 | /* | |
131 | * Try to bring up a master. If component is NULL, we're interested in | |
132 | * this master, otherwise it's a component which must be present to try | |
133 | * and bring up the master. | |
134 | * | |
135 | * Returns 1 for successful bringup, 0 if not ready, or -ve errno. | |
136 | */ | |
137 | static int try_to_bring_up_master(struct master *master, | |
138 | struct component *component) | |
139 | { | |
c334940e RK |
140 | int ret; |
141 | ||
ffc30b74 RK |
142 | dev_dbg(master->dev, "trying to bring up master\n"); |
143 | ||
6955b582 | 144 | if (find_components(master)) { |
ffc30b74 RK |
145 | dev_dbg(master->dev, "master has incomplete components\n"); |
146 | return 0; | |
c334940e | 147 | } |
2a41e607 | 148 | |
c334940e | 149 | if (component && component->master != master) { |
ffc30b74 RK |
150 | dev_dbg(master->dev, "master is not for this component (%s)\n", |
151 | dev_name(component->dev)); | |
152 | return 0; | |
c334940e | 153 | } |
2a41e607 | 154 | |
ffc30b74 RK |
155 | if (!devres_open_group(master->dev, NULL, GFP_KERNEL)) |
156 | return -ENOMEM; | |
2a41e607 | 157 | |
c334940e RK |
158 | /* Found all components */ |
159 | ret = master->ops->bind(master->dev); | |
160 | if (ret < 0) { | |
161 | devres_release_group(master->dev, NULL); | |
162 | dev_info(master->dev, "master bind failed: %d\n", ret); | |
ffc30b74 | 163 | return ret; |
c334940e | 164 | } |
9e1ccb4a | 165 | |
c334940e RK |
166 | master->bound = true; |
167 | return 1; | |
2a41e607 RK |
168 | } |
169 | ||
170 | static int try_to_bring_up_masters(struct component *component) | |
171 | { | |
172 | struct master *m; | |
173 | int ret = 0; | |
174 | ||
175 | list_for_each_entry(m, &masters, node) { | |
29f1c7fd RK |
176 | if (!m->bound) { |
177 | ret = try_to_bring_up_master(m, component); | |
178 | if (ret != 0) | |
179 | break; | |
180 | } | |
2a41e607 RK |
181 | } |
182 | ||
183 | return ret; | |
184 | } | |
185 | ||
186 | static void take_down_master(struct master *master) | |
187 | { | |
188 | if (master->bound) { | |
189 | master->ops->unbind(master->dev); | |
9e1ccb4a | 190 | devres_release_group(master->dev, NULL); |
2a41e607 RK |
191 | master->bound = false; |
192 | } | |
2a41e607 RK |
193 | } |
194 | ||
6955b582 RK |
195 | static size_t component_match_size(size_t num) |
196 | { | |
197 | return offsetof(struct component_match, compare[num]); | |
198 | } | |
199 | ||
200 | static struct component_match *component_match_realloc(struct device *dev, | |
201 | struct component_match *match, size_t num) | |
202 | { | |
203 | struct component_match *new; | |
204 | ||
205 | if (match && match->alloc == num) | |
206 | return match; | |
207 | ||
208 | new = devm_kmalloc(dev, component_match_size(num), GFP_KERNEL); | |
209 | if (!new) | |
210 | return ERR_PTR(-ENOMEM); | |
211 | ||
212 | if (match) { | |
213 | memcpy(new, match, component_match_size(min(match->num, num))); | |
214 | devm_kfree(dev, match); | |
215 | } else { | |
216 | new->num = 0; | |
217 | } | |
218 | ||
219 | new->alloc = num; | |
220 | ||
221 | return new; | |
222 | } | |
223 | ||
224 | /* | |
225 | * Add a component to be matched. | |
226 | * | |
227 | * The match array is first created or extended if necessary. | |
228 | */ | |
229 | void component_match_add(struct device *dev, struct component_match **matchptr, | |
230 | int (*compare)(struct device *, void *), void *compare_data) | |
231 | { | |
232 | struct component_match *match = *matchptr; | |
233 | ||
234 | if (IS_ERR(match)) | |
235 | return; | |
236 | ||
237 | if (!match || match->num == match->alloc) { | |
238 | size_t new_size = match ? match->alloc + 16 : 15; | |
239 | ||
240 | match = component_match_realloc(dev, match, new_size); | |
241 | ||
242 | *matchptr = match; | |
243 | ||
244 | if (IS_ERR(match)) | |
245 | return; | |
246 | } | |
247 | ||
248 | match->compare[match->num].fn = compare; | |
249 | match->compare[match->num].data = compare_data; | |
ffc30b74 | 250 | match->compare[match->num].component = NULL; |
6955b582 RK |
251 | match->num++; |
252 | } | |
253 | EXPORT_SYMBOL(component_match_add); | |
254 | ||
255 | int component_master_add_with_match(struct device *dev, | |
256 | const struct component_master_ops *ops, | |
257 | struct component_match *match) | |
2a41e607 RK |
258 | { |
259 | struct master *master; | |
260 | int ret; | |
261 | ||
fae9e2e0 RK |
262 | /* Reallocate the match array for its true size */ |
263 | match = component_match_realloc(dev, match, match->num); | |
264 | if (IS_ERR(match)) | |
265 | return PTR_ERR(match); | |
6955b582 | 266 | |
2a41e607 RK |
267 | master = kzalloc(sizeof(*master), GFP_KERNEL); |
268 | if (!master) | |
269 | return -ENOMEM; | |
270 | ||
271 | master->dev = dev; | |
272 | master->ops = ops; | |
6955b582 | 273 | master->match = match; |
2a41e607 RK |
274 | |
275 | /* Add to the list of available masters. */ | |
276 | mutex_lock(&component_mutex); | |
277 | list_add(&master->node, &masters); | |
278 | ||
279 | ret = try_to_bring_up_master(master, NULL); | |
280 | ||
281 | if (ret < 0) { | |
282 | /* Delete off the list if we weren't successful */ | |
283 | list_del(&master->node); | |
284 | kfree(master); | |
285 | } | |
286 | mutex_unlock(&component_mutex); | |
287 | ||
288 | return ret < 0 ? ret : 0; | |
289 | } | |
6955b582 RK |
290 | EXPORT_SYMBOL_GPL(component_master_add_with_match); |
291 | ||
2a41e607 RK |
292 | void component_master_del(struct device *dev, |
293 | const struct component_master_ops *ops) | |
294 | { | |
295 | struct master *master; | |
ffc30b74 | 296 | int i; |
2a41e607 RK |
297 | |
298 | mutex_lock(&component_mutex); | |
299 | master = __master_find(dev, ops); | |
300 | if (master) { | |
ffc30b74 RK |
301 | struct component_match *match = master->match; |
302 | ||
2a41e607 RK |
303 | take_down_master(master); |
304 | ||
305 | list_del(&master->node); | |
ffc30b74 RK |
306 | |
307 | if (match) { | |
308 | for (i = 0; i < match->num; i++) { | |
309 | struct component *c = match->compare[i].component; | |
310 | if (c) | |
311 | c->master = NULL; | |
312 | } | |
313 | } | |
2a41e607 RK |
314 | kfree(master); |
315 | } | |
316 | mutex_unlock(&component_mutex); | |
317 | } | |
318 | EXPORT_SYMBOL_GPL(component_master_del); | |
319 | ||
320 | static void component_unbind(struct component *component, | |
321 | struct master *master, void *data) | |
322 | { | |
323 | WARN_ON(!component->bound); | |
324 | ||
325 | component->ops->unbind(component->dev, master->dev, data); | |
326 | component->bound = false; | |
327 | ||
328 | /* Release all resources claimed in the binding of this component */ | |
329 | devres_release_group(component->dev, component); | |
330 | } | |
331 | ||
332 | void component_unbind_all(struct device *master_dev, void *data) | |
333 | { | |
334 | struct master *master; | |
335 | struct component *c; | |
ffc30b74 | 336 | size_t i; |
2a41e607 RK |
337 | |
338 | WARN_ON(!mutex_is_locked(&component_mutex)); | |
339 | ||
340 | master = __master_find(master_dev, NULL); | |
341 | if (!master) | |
342 | return; | |
343 | ||
ffc30b74 RK |
344 | /* Unbind components in reverse order */ |
345 | for (i = master->match->num; i--; ) | |
346 | if (!master->match->compare[i].duplicate) { | |
347 | c = master->match->compare[i].component; | |
348 | component_unbind(c, master, data); | |
349 | } | |
2a41e607 RK |
350 | } |
351 | EXPORT_SYMBOL_GPL(component_unbind_all); | |
352 | ||
353 | static int component_bind(struct component *component, struct master *master, | |
354 | void *data) | |
355 | { | |
356 | int ret; | |
357 | ||
358 | /* | |
359 | * Each component initialises inside its own devres group. | |
360 | * This allows us to roll-back a failed component without | |
361 | * affecting anything else. | |
362 | */ | |
363 | if (!devres_open_group(master->dev, NULL, GFP_KERNEL)) | |
364 | return -ENOMEM; | |
365 | ||
366 | /* | |
367 | * Also open a group for the device itself: this allows us | |
368 | * to release the resources claimed against the sub-device | |
369 | * at the appropriate moment. | |
370 | */ | |
371 | if (!devres_open_group(component->dev, component, GFP_KERNEL)) { | |
372 | devres_release_group(master->dev, NULL); | |
373 | return -ENOMEM; | |
374 | } | |
375 | ||
376 | dev_dbg(master->dev, "binding %s (ops %ps)\n", | |
377 | dev_name(component->dev), component->ops); | |
378 | ||
379 | ret = component->ops->bind(component->dev, master->dev, data); | |
380 | if (!ret) { | |
381 | component->bound = true; | |
382 | ||
383 | /* | |
384 | * Close the component device's group so that resources | |
385 | * allocated in the binding are encapsulated for removal | |
386 | * at unbind. Remove the group on the DRM device as we | |
387 | * can clean those resources up independently. | |
388 | */ | |
389 | devres_close_group(component->dev, NULL); | |
390 | devres_remove_group(master->dev, NULL); | |
391 | ||
392 | dev_info(master->dev, "bound %s (ops %ps)\n", | |
393 | dev_name(component->dev), component->ops); | |
394 | } else { | |
395 | devres_release_group(component->dev, NULL); | |
396 | devres_release_group(master->dev, NULL); | |
397 | ||
398 | dev_err(master->dev, "failed to bind %s (ops %ps): %d\n", | |
399 | dev_name(component->dev), component->ops, ret); | |
400 | } | |
401 | ||
402 | return ret; | |
403 | } | |
404 | ||
405 | int component_bind_all(struct device *master_dev, void *data) | |
406 | { | |
407 | struct master *master; | |
408 | struct component *c; | |
ffc30b74 | 409 | size_t i; |
2a41e607 RK |
410 | int ret = 0; |
411 | ||
412 | WARN_ON(!mutex_is_locked(&component_mutex)); | |
413 | ||
414 | master = __master_find(master_dev, NULL); | |
415 | if (!master) | |
416 | return -EINVAL; | |
417 | ||
ffc30b74 RK |
418 | /* Bind components in match order */ |
419 | for (i = 0; i < master->match->num; i++) | |
420 | if (!master->match->compare[i].duplicate) { | |
421 | c = master->match->compare[i].component; | |
422 | ret = component_bind(c, master, data); | |
423 | if (ret) | |
424 | break; | |
425 | } | |
2a41e607 RK |
426 | |
427 | if (ret != 0) { | |
ffc30b74 RK |
428 | for (; i--; ) |
429 | if (!master->match->compare[i].duplicate) { | |
430 | c = master->match->compare[i].component; | |
431 | component_unbind(c, master, data); | |
432 | } | |
2a41e607 RK |
433 | } |
434 | ||
435 | return ret; | |
436 | } | |
437 | EXPORT_SYMBOL_GPL(component_bind_all); | |
438 | ||
439 | int component_add(struct device *dev, const struct component_ops *ops) | |
440 | { | |
441 | struct component *component; | |
442 | int ret; | |
443 | ||
444 | component = kzalloc(sizeof(*component), GFP_KERNEL); | |
445 | if (!component) | |
446 | return -ENOMEM; | |
447 | ||
448 | component->ops = ops; | |
449 | component->dev = dev; | |
450 | ||
451 | dev_dbg(dev, "adding component (ops %ps)\n", ops); | |
452 | ||
453 | mutex_lock(&component_mutex); | |
454 | list_add_tail(&component->node, &component_list); | |
455 | ||
456 | ret = try_to_bring_up_masters(component); | |
457 | if (ret < 0) { | |
458 | list_del(&component->node); | |
459 | ||
460 | kfree(component); | |
461 | } | |
462 | mutex_unlock(&component_mutex); | |
463 | ||
464 | return ret < 0 ? ret : 0; | |
465 | } | |
466 | EXPORT_SYMBOL_GPL(component_add); | |
467 | ||
468 | void component_del(struct device *dev, const struct component_ops *ops) | |
469 | { | |
470 | struct component *c, *component = NULL; | |
471 | ||
472 | mutex_lock(&component_mutex); | |
473 | list_for_each_entry(c, &component_list, node) | |
474 | if (c->dev == dev && c->ops == ops) { | |
475 | list_del(&c->node); | |
476 | component = c; | |
477 | break; | |
478 | } | |
479 | ||
ffc30b74 | 480 | if (component && component->master) { |
2a41e607 | 481 | take_down_master(component->master); |
ffc30b74 RK |
482 | remove_component(component->master, component); |
483 | } | |
2a41e607 RK |
484 | |
485 | mutex_unlock(&component_mutex); | |
486 | ||
487 | WARN_ON(!component); | |
488 | kfree(component); | |
489 | } | |
490 | EXPORT_SYMBOL_GPL(component_del); | |
491 | ||
492 | MODULE_LICENSE("GPL v2"); |