1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Media entity |
4 | * |
5 | * Copyright (C) 2010 Nokia Corporation |
6 | * |
7 | * Contacts: Laurent Pinchart <laurent.pinchart@ideasonboard.com> |
8 | * Sakari Ailus <sakari.ailus@iki.fi> |
9 | */ |
10 | |
11 | #include <linux/bitmap.h> |
12 | #include <linux/list.h> |
13 | #include <linux/property.h> |
14 | #include <linux/slab.h> |
15 | #include <media/media-entity.h> |
16 | #include <media/media-device.h> |
17 | |
18 | static inline const char *intf_type(struct media_interface *intf) |
19 | { |
20 | switch (intf->type) { |
21 | case MEDIA_INTF_T_DVB_FE: |
22 | return "dvb-frontend" ; |
23 | case MEDIA_INTF_T_DVB_DEMUX: |
24 | return "dvb-demux" ; |
25 | case MEDIA_INTF_T_DVB_DVR: |
26 | return "dvb-dvr" ; |
27 | case MEDIA_INTF_T_DVB_CA: |
28 | return "dvb-ca" ; |
29 | case MEDIA_INTF_T_DVB_NET: |
30 | return "dvb-net" ; |
31 | case MEDIA_INTF_T_V4L_VIDEO: |
32 | return "v4l-video" ; |
33 | case MEDIA_INTF_T_V4L_VBI: |
34 | return "v4l-vbi" ; |
35 | case MEDIA_INTF_T_V4L_RADIO: |
36 | return "v4l-radio" ; |
37 | case MEDIA_INTF_T_V4L_SUBDEV: |
38 | return "v4l-subdev" ; |
39 | case MEDIA_INTF_T_V4L_SWRADIO: |
40 | return "v4l-swradio" ; |
41 | case MEDIA_INTF_T_V4L_TOUCH: |
42 | return "v4l-touch" ; |
43 | default: |
44 | return "unknown-intf" ; |
45 | } |
46 | }; |
47 | |
48 | static inline const char *link_type_name(struct media_link *link) |
49 | { |
50 | switch (link->flags & MEDIA_LNK_FL_LINK_TYPE) { |
51 | case MEDIA_LNK_FL_DATA_LINK: |
52 | return "data" ; |
53 | case MEDIA_LNK_FL_INTERFACE_LINK: |
54 | return "interface" ; |
55 | case MEDIA_LNK_FL_ANCILLARY_LINK: |
56 | return "ancillary" ; |
57 | default: |
58 | return "unknown" ; |
59 | } |
60 | } |
61 | |
62 | __must_check int media_entity_enum_init(struct media_entity_enum *ent_enum, |
63 | struct media_device *mdev) |
64 | { |
65 | int idx_max; |
66 | |
67 | idx_max = ALIGN(mdev->entity_internal_idx_max + 1, BITS_PER_LONG); |
68 | ent_enum->bmap = bitmap_zalloc(nbits: idx_max, GFP_KERNEL); |
69 | if (!ent_enum->bmap) |
70 | return -ENOMEM; |
71 | |
72 | ent_enum->idx_max = idx_max; |
73 | |
74 | return 0; |
75 | } |
76 | EXPORT_SYMBOL_GPL(media_entity_enum_init); |
77 | |
78 | void media_entity_enum_cleanup(struct media_entity_enum *ent_enum) |
79 | { |
80 | bitmap_free(bitmap: ent_enum->bmap); |
81 | } |
82 | EXPORT_SYMBOL_GPL(media_entity_enum_cleanup); |
83 | |
84 | /** |
85 | * dev_dbg_obj - Prints in debug mode a change on some object |
86 | * |
87 | * @event_name: Name of the event to report. Could be __func__ |
88 | * @gobj: Pointer to the object |
89 | * |
90 | * Enabled only if DEBUG or CONFIG_DYNAMIC_DEBUG. Otherwise, it |
91 | * won't produce any code. |
92 | */ |
93 | static void dev_dbg_obj(const char *event_name, struct media_gobj *gobj) |
94 | { |
95 | #if defined(DEBUG) || defined (CONFIG_DYNAMIC_DEBUG) |
96 | switch (media_type(gobj)) { |
97 | case MEDIA_GRAPH_ENTITY: |
98 | dev_dbg(gobj->mdev->dev, |
99 | "%s id %u: entity '%s'\n" , |
100 | event_name, media_id(gobj), |
101 | gobj_to_entity(gobj)->name); |
102 | break; |
103 | case MEDIA_GRAPH_LINK: |
104 | { |
105 | struct media_link *link = gobj_to_link(gobj); |
106 | |
107 | dev_dbg(gobj->mdev->dev, |
108 | "%s id %u: %s link id %u ==> id %u\n" , |
109 | event_name, media_id(gobj), link_type_name(link), |
110 | media_id(link->gobj0), |
111 | media_id(link->gobj1)); |
112 | break; |
113 | } |
114 | case MEDIA_GRAPH_PAD: |
115 | { |
116 | struct media_pad *pad = gobj_to_pad(gobj); |
117 | |
118 | dev_dbg(gobj->mdev->dev, |
119 | "%s id %u: %s%spad '%s':%d\n" , |
120 | event_name, media_id(gobj), |
121 | pad->flags & MEDIA_PAD_FL_SINK ? "sink " : "" , |
122 | pad->flags & MEDIA_PAD_FL_SOURCE ? "source " : "" , |
123 | pad->entity->name, pad->index); |
124 | break; |
125 | } |
126 | case MEDIA_GRAPH_INTF_DEVNODE: |
127 | { |
128 | struct media_interface *intf = gobj_to_intf(gobj); |
129 | struct media_intf_devnode *devnode = intf_to_devnode(intf); |
130 | |
131 | dev_dbg(gobj->mdev->dev, |
132 | "%s id %u: intf_devnode %s - major: %d, minor: %d\n" , |
133 | event_name, media_id(gobj), |
134 | intf_type(intf), |
135 | devnode->major, devnode->minor); |
136 | break; |
137 | } |
138 | } |
139 | #endif |
140 | } |
141 | |
142 | void media_gobj_create(struct media_device *mdev, |
143 | enum media_gobj_type type, |
144 | struct media_gobj *gobj) |
145 | { |
146 | BUG_ON(!mdev); |
147 | |
148 | gobj->mdev = mdev; |
149 | |
150 | /* Create a per-type unique object ID */ |
151 | gobj->id = media_gobj_gen_id(type, local_id: ++mdev->id); |
152 | |
153 | switch (type) { |
154 | case MEDIA_GRAPH_ENTITY: |
155 | list_add_tail(new: &gobj->list, head: &mdev->entities); |
156 | break; |
157 | case MEDIA_GRAPH_PAD: |
158 | list_add_tail(new: &gobj->list, head: &mdev->pads); |
159 | break; |
160 | case MEDIA_GRAPH_LINK: |
161 | list_add_tail(new: &gobj->list, head: &mdev->links); |
162 | break; |
163 | case MEDIA_GRAPH_INTF_DEVNODE: |
164 | list_add_tail(new: &gobj->list, head: &mdev->interfaces); |
165 | break; |
166 | } |
167 | |
168 | mdev->topology_version++; |
169 | |
170 | dev_dbg_obj(event_name: __func__, gobj); |
171 | } |
172 | |
173 | void media_gobj_destroy(struct media_gobj *gobj) |
174 | { |
175 | /* Do nothing if the object is not linked. */ |
176 | if (gobj->mdev == NULL) |
177 | return; |
178 | |
179 | dev_dbg_obj(event_name: __func__, gobj); |
180 | |
181 | gobj->mdev->topology_version++; |
182 | |
183 | /* Remove the object from mdev list */ |
184 | list_del(entry: &gobj->list); |
185 | |
186 | gobj->mdev = NULL; |
187 | } |
188 | |
189 | /* |
190 | * TODO: Get rid of this. |
191 | */ |
192 | #define MEDIA_ENTITY_MAX_PADS 512 |
193 | |
194 | int media_entity_pads_init(struct media_entity *entity, u16 num_pads, |
195 | struct media_pad *pads) |
196 | { |
197 | struct media_device *mdev = entity->graph_obj.mdev; |
198 | struct media_pad *iter; |
199 | unsigned int i = 0; |
200 | int ret = 0; |
201 | |
202 | if (num_pads >= MEDIA_ENTITY_MAX_PADS) |
203 | return -E2BIG; |
204 | |
205 | entity->num_pads = num_pads; |
206 | entity->pads = pads; |
207 | |
208 | if (mdev) |
209 | mutex_lock(&mdev->graph_mutex); |
210 | |
211 | media_entity_for_each_pad(entity, iter) { |
212 | iter->entity = entity; |
213 | iter->index = i++; |
214 | |
215 | if (hweight32(iter->flags & (MEDIA_PAD_FL_SINK | |
216 | MEDIA_PAD_FL_SOURCE)) != 1) { |
217 | ret = -EINVAL; |
218 | break; |
219 | } |
220 | |
221 | if (mdev) |
222 | media_gobj_create(mdev, type: MEDIA_GRAPH_PAD, |
223 | gobj: &iter->graph_obj); |
224 | } |
225 | |
226 | if (ret && mdev) { |
227 | media_entity_for_each_pad(entity, iter) |
228 | media_gobj_destroy(gobj: &iter->graph_obj); |
229 | } |
230 | |
231 | if (mdev) |
232 | mutex_unlock(lock: &mdev->graph_mutex); |
233 | |
234 | return ret; |
235 | } |
236 | EXPORT_SYMBOL_GPL(media_entity_pads_init); |
237 | |
238 | /* ----------------------------------------------------------------------------- |
239 | * Graph traversal |
240 | */ |
241 | |
242 | /** |
243 | * media_entity_has_pad_interdep - Check interdependency between two pads |
244 | * |
245 | * @entity: The entity |
246 | * @pad0: The first pad index |
247 | * @pad1: The second pad index |
248 | * |
249 | * This function checks the interdependency inside the entity between @pad0 |
250 | * and @pad1. If two pads are interdependent they are part of the same pipeline |
251 | * and enabling one of the pads means that the other pad will become "locked" |
252 | * and doesn't allow configuration changes. |
253 | * |
254 | * This function uses the &media_entity_operations.has_pad_interdep() operation |
255 | * to check the dependency inside the entity between @pad0 and @pad1. If the |
256 | * has_pad_interdep operation is not implemented, all pads of the entity are |
257 | * considered to be interdependent. |
258 | * |
259 | * One of @pad0 and @pad1 must be a sink pad and the other one a source pad. |
260 | * The function returns false if both pads are sinks or sources. |
261 | * |
262 | * The caller must hold entity->graph_obj.mdev->mutex. |
263 | * |
264 | * Return: true if the pads are connected internally and false otherwise. |
265 | */ |
266 | static bool media_entity_has_pad_interdep(struct media_entity *entity, |
267 | unsigned int pad0, unsigned int pad1) |
268 | { |
269 | if (pad0 >= entity->num_pads || pad1 >= entity->num_pads) |
270 | return false; |
271 | |
272 | if (entity->pads[pad0].flags & entity->pads[pad1].flags & |
273 | (MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_SOURCE)) |
274 | return false; |
275 | |
276 | if (!entity->ops || !entity->ops->has_pad_interdep) |
277 | return true; |
278 | |
279 | return entity->ops->has_pad_interdep(entity, pad0, pad1); |
280 | } |
281 | |
282 | static struct media_entity * |
283 | media_entity_other(struct media_entity *entity, struct media_link *link) |
284 | { |
285 | if (link->source->entity == entity) |
286 | return link->sink->entity; |
287 | else |
288 | return link->source->entity; |
289 | } |
290 | |
291 | /* push an entity to traversal stack */ |
292 | static void stack_push(struct media_graph *graph, |
293 | struct media_entity *entity) |
294 | { |
295 | if (graph->top == MEDIA_ENTITY_ENUM_MAX_DEPTH - 1) { |
296 | WARN_ON(1); |
297 | return; |
298 | } |
299 | graph->top++; |
300 | graph->stack[graph->top].link = entity->links.next; |
301 | graph->stack[graph->top].entity = entity; |
302 | } |
303 | |
304 | static struct media_entity *stack_pop(struct media_graph *graph) |
305 | { |
306 | struct media_entity *entity; |
307 | |
308 | entity = graph->stack[graph->top].entity; |
309 | graph->top--; |
310 | |
311 | return entity; |
312 | } |
313 | |
314 | #define link_top(en) ((en)->stack[(en)->top].link) |
315 | #define stack_top(en) ((en)->stack[(en)->top].entity) |
316 | |
317 | /** |
318 | * media_graph_walk_init - Allocate resources for graph walk |
319 | * @graph: Media graph structure that will be used to walk the graph |
320 | * @mdev: Media device |
321 | * |
322 | * Reserve resources for graph walk in media device's current |
323 | * state. The memory must be released using |
324 | * media_graph_walk_cleanup(). |
325 | * |
326 | * Returns error on failure, zero on success. |
327 | */ |
328 | __must_check int media_graph_walk_init( |
329 | struct media_graph *graph, struct media_device *mdev) |
330 | { |
331 | return media_entity_enum_init(&graph->ent_enum, mdev); |
332 | } |
333 | EXPORT_SYMBOL_GPL(media_graph_walk_init); |
334 | |
335 | /** |
336 | * media_graph_walk_cleanup - Release resources related to graph walking |
337 | * @graph: Media graph structure that was used to walk the graph |
338 | */ |
339 | void media_graph_walk_cleanup(struct media_graph *graph) |
340 | { |
341 | media_entity_enum_cleanup(&graph->ent_enum); |
342 | } |
343 | EXPORT_SYMBOL_GPL(media_graph_walk_cleanup); |
344 | |
345 | void media_graph_walk_start(struct media_graph *graph, |
346 | struct media_entity *entity) |
347 | { |
348 | media_entity_enum_zero(ent_enum: &graph->ent_enum); |
349 | media_entity_enum_set(ent_enum: &graph->ent_enum, entity); |
350 | |
351 | graph->top = 0; |
352 | graph->stack[graph->top].entity = NULL; |
353 | stack_push(graph, entity); |
354 | dev_dbg(entity->graph_obj.mdev->dev, |
355 | "begin graph walk at '%s'\n" , entity->name); |
356 | } |
357 | EXPORT_SYMBOL_GPL(media_graph_walk_start); |
358 | |
359 | static void media_graph_walk_iter(struct media_graph *graph) |
360 | { |
361 | struct media_entity *entity = stack_top(graph); |
362 | struct media_link *link; |
363 | struct media_entity *next; |
364 | |
365 | link = list_entry(link_top(graph), typeof(*link), list); |
366 | |
367 | /* If the link is not a data link, don't follow it */ |
368 | if ((link->flags & MEDIA_LNK_FL_LINK_TYPE) != MEDIA_LNK_FL_DATA_LINK) { |
369 | link_top(graph) = link_top(graph)->next; |
370 | return; |
371 | } |
372 | |
373 | /* The link is not enabled so we do not follow. */ |
374 | if (!(link->flags & MEDIA_LNK_FL_ENABLED)) { |
375 | link_top(graph) = link_top(graph)->next; |
376 | dev_dbg(entity->graph_obj.mdev->dev, |
377 | "walk: skipping disabled link '%s':%u -> '%s':%u\n" , |
378 | link->source->entity->name, link->source->index, |
379 | link->sink->entity->name, link->sink->index); |
380 | return; |
381 | } |
382 | |
383 | /* Get the entity at the other end of the link. */ |
384 | next = media_entity_other(entity, link); |
385 | |
386 | /* Has the entity already been visited? */ |
387 | if (media_entity_enum_test_and_set(ent_enum: &graph->ent_enum, entity: next)) { |
388 | link_top(graph) = link_top(graph)->next; |
389 | dev_dbg(entity->graph_obj.mdev->dev, |
390 | "walk: skipping entity '%s' (already seen)\n" , |
391 | next->name); |
392 | return; |
393 | } |
394 | |
395 | /* Push the new entity to stack and start over. */ |
396 | link_top(graph) = link_top(graph)->next; |
397 | stack_push(graph, entity: next); |
398 | dev_dbg(entity->graph_obj.mdev->dev, "walk: pushing '%s' on stack\n" , |
399 | next->name); |
400 | lockdep_assert_held(&entity->graph_obj.mdev->graph_mutex); |
401 | } |
402 | |
403 | struct media_entity *media_graph_walk_next(struct media_graph *graph) |
404 | { |
405 | struct media_entity *entity; |
406 | |
407 | if (stack_top(graph) == NULL) |
408 | return NULL; |
409 | |
410 | /* |
411 | * Depth first search. Push entity to stack and continue from |
412 | * top of the stack until no more entities on the level can be |
413 | * found. |
414 | */ |
415 | while (link_top(graph) != &stack_top(graph)->links) |
416 | media_graph_walk_iter(graph); |
417 | |
418 | entity = stack_pop(graph); |
419 | dev_dbg(entity->graph_obj.mdev->dev, |
420 | "walk: returning entity '%s'\n" , entity->name); |
421 | |
422 | return entity; |
423 | } |
424 | EXPORT_SYMBOL_GPL(media_graph_walk_next); |
425 | |
426 | /* ----------------------------------------------------------------------------- |
427 | * Pipeline management |
428 | */ |
429 | |
430 | /* |
431 | * The pipeline traversal stack stores pads that are reached during graph |
432 | * traversal, with a list of links to be visited to continue the traversal. |
433 | * When a new pad is reached, an entry is pushed on the top of the stack and |
434 | * points to the incoming pad and the first link of the entity. |
435 | * |
436 | * To find further pads in the pipeline, the traversal algorithm follows |
437 | * internal pad dependencies in the entity, and then links in the graph. It |
438 | * does so by iterating over all links of the entity, and following enabled |
439 | * links that originate from a pad that is internally connected to the incoming |
440 | * pad, as reported by the media_entity_has_pad_interdep() function. |
441 | */ |
442 | |
443 | /** |
444 | * struct media_pipeline_walk_entry - Entry in the pipeline traversal stack |
445 | * |
446 | * @pad: The media pad being visited |
447 | * @links: Links left to be visited |
448 | */ |
449 | struct media_pipeline_walk_entry { |
450 | struct media_pad *pad; |
451 | struct list_head *links; |
452 | }; |
453 | |
454 | /** |
455 | * struct media_pipeline_walk - State used by the media pipeline traversal |
456 | * algorithm |
457 | * |
458 | * @mdev: The media device |
459 | * @stack: Depth-first search stack |
460 | * @stack.size: Number of allocated entries in @stack.entries |
461 | * @stack.top: Index of the top stack entry (-1 if the stack is empty) |
462 | * @stack.entries: Stack entries |
463 | */ |
464 | struct media_pipeline_walk { |
465 | struct media_device *mdev; |
466 | |
467 | struct { |
468 | unsigned int size; |
469 | int top; |
470 | struct media_pipeline_walk_entry *entries; |
471 | } stack; |
472 | }; |
473 | |
474 | #define MEDIA_PIPELINE_STACK_GROW_STEP 16 |
475 | |
476 | static struct media_pipeline_walk_entry * |
477 | media_pipeline_walk_top(struct media_pipeline_walk *walk) |
478 | { |
479 | return &walk->stack.entries[walk->stack.top]; |
480 | } |
481 | |
482 | static bool media_pipeline_walk_empty(struct media_pipeline_walk *walk) |
483 | { |
484 | return walk->stack.top == -1; |
485 | } |
486 | |
487 | /* Increase the stack size by MEDIA_PIPELINE_STACK_GROW_STEP elements. */ |
488 | static int media_pipeline_walk_resize(struct media_pipeline_walk *walk) |
489 | { |
490 | struct media_pipeline_walk_entry *entries; |
491 | unsigned int new_size; |
492 | |
493 | /* Safety check, to avoid stack overflows in case of bugs. */ |
494 | if (walk->stack.size >= 256) |
495 | return -E2BIG; |
496 | |
497 | new_size = walk->stack.size + MEDIA_PIPELINE_STACK_GROW_STEP; |
498 | |
499 | entries = krealloc(objp: walk->stack.entries, |
500 | new_size: new_size * sizeof(*walk->stack.entries), |
501 | GFP_KERNEL); |
502 | if (!entries) |
503 | return -ENOMEM; |
504 | |
505 | walk->stack.entries = entries; |
506 | walk->stack.size = new_size; |
507 | |
508 | return 0; |
509 | } |
510 | |
511 | /* Push a new entry on the stack. */ |
512 | static int media_pipeline_walk_push(struct media_pipeline_walk *walk, |
513 | struct media_pad *pad) |
514 | { |
515 | struct media_pipeline_walk_entry *entry; |
516 | int ret; |
517 | |
518 | if (walk->stack.top + 1 >= walk->stack.size) { |
519 | ret = media_pipeline_walk_resize(walk); |
520 | if (ret) |
521 | return ret; |
522 | } |
523 | |
524 | walk->stack.top++; |
525 | entry = media_pipeline_walk_top(walk); |
526 | entry->pad = pad; |
527 | entry->links = pad->entity->links.next; |
528 | |
529 | dev_dbg(walk->mdev->dev, |
530 | "media pipeline: pushed entry %u: '%s':%u\n" , |
531 | walk->stack.top, pad->entity->name, pad->index); |
532 | |
533 | return 0; |
534 | } |
535 | |
536 | /* |
537 | * Move the top entry link cursor to the next link. If all links of the entry |
538 | * have been visited, pop the entry itself. |
539 | */ |
540 | static void media_pipeline_walk_pop(struct media_pipeline_walk *walk) |
541 | { |
542 | struct media_pipeline_walk_entry *entry; |
543 | |
544 | if (WARN_ON(walk->stack.top < 0)) |
545 | return; |
546 | |
547 | entry = media_pipeline_walk_top(walk); |
548 | |
549 | if (entry->links->next == &entry->pad->entity->links) { |
550 | dev_dbg(walk->mdev->dev, |
551 | "media pipeline: entry %u has no more links, popping\n" , |
552 | walk->stack.top); |
553 | |
554 | walk->stack.top--; |
555 | return; |
556 | } |
557 | |
558 | entry->links = entry->links->next; |
559 | |
560 | dev_dbg(walk->mdev->dev, |
561 | "media pipeline: moved entry %u to next link\n" , |
562 | walk->stack.top); |
563 | } |
564 | |
565 | /* Free all memory allocated while walking the pipeline. */ |
566 | static void media_pipeline_walk_destroy(struct media_pipeline_walk *walk) |
567 | { |
568 | kfree(objp: walk->stack.entries); |
569 | } |
570 | |
571 | /* Add a pad to the pipeline and push it to the stack. */ |
572 | static int media_pipeline_add_pad(struct media_pipeline *pipe, |
573 | struct media_pipeline_walk *walk, |
574 | struct media_pad *pad) |
575 | { |
576 | struct media_pipeline_pad *ppad; |
577 | |
578 | list_for_each_entry(ppad, &pipe->pads, list) { |
579 | if (ppad->pad == pad) { |
580 | dev_dbg(pad->graph_obj.mdev->dev, |
581 | "media pipeline: already contains pad '%s':%u\n" , |
582 | pad->entity->name, pad->index); |
583 | return 0; |
584 | } |
585 | } |
586 | |
587 | ppad = kzalloc(size: sizeof(*ppad), GFP_KERNEL); |
588 | if (!ppad) |
589 | return -ENOMEM; |
590 | |
591 | ppad->pipe = pipe; |
592 | ppad->pad = pad; |
593 | |
594 | list_add_tail(new: &ppad->list, head: &pipe->pads); |
595 | |
596 | dev_dbg(pad->graph_obj.mdev->dev, |
597 | "media pipeline: added pad '%s':%u\n" , |
598 | pad->entity->name, pad->index); |
599 | |
600 | return media_pipeline_walk_push(walk, pad); |
601 | } |
602 | |
603 | /* Explore the next link of the entity at the top of the stack. */ |
604 | static int media_pipeline_explore_next_link(struct media_pipeline *pipe, |
605 | struct media_pipeline_walk *walk) |
606 | { |
607 | struct media_pipeline_walk_entry *entry = media_pipeline_walk_top(walk); |
608 | struct media_pad *pad; |
609 | struct media_link *link; |
610 | struct media_pad *local; |
611 | struct media_pad *remote; |
612 | int ret; |
613 | |
614 | pad = entry->pad; |
615 | link = list_entry(entry->links, typeof(*link), list); |
616 | media_pipeline_walk_pop(walk); |
617 | |
618 | dev_dbg(walk->mdev->dev, |
619 | "media pipeline: exploring link '%s':%u -> '%s':%u\n" , |
620 | link->source->entity->name, link->source->index, |
621 | link->sink->entity->name, link->sink->index); |
622 | |
623 | /* Skip links that are not enabled. */ |
624 | if (!(link->flags & MEDIA_LNK_FL_ENABLED)) { |
625 | dev_dbg(walk->mdev->dev, |
626 | "media pipeline: skipping link (disabled)\n" ); |
627 | return 0; |
628 | } |
629 | |
630 | /* Get the local pad and remote pad. */ |
631 | if (link->source->entity == pad->entity) { |
632 | local = link->source; |
633 | remote = link->sink; |
634 | } else { |
635 | local = link->sink; |
636 | remote = link->source; |
637 | } |
638 | |
639 | /* |
640 | * Skip links that originate from a different pad than the incoming pad |
641 | * that is not connected internally in the entity to the incoming pad. |
642 | */ |
643 | if (pad != local && |
644 | !media_entity_has_pad_interdep(entity: pad->entity, pad0: pad->index, pad1: local->index)) { |
645 | dev_dbg(walk->mdev->dev, |
646 | "media pipeline: skipping link (no route)\n" ); |
647 | return 0; |
648 | } |
649 | |
650 | /* |
651 | * Add the local and remote pads of the link to the pipeline and push |
652 | * them to the stack, if they're not already present. |
653 | */ |
654 | ret = media_pipeline_add_pad(pipe, walk, pad: local); |
655 | if (ret) |
656 | return ret; |
657 | |
658 | ret = media_pipeline_add_pad(pipe, walk, pad: remote); |
659 | if (ret) |
660 | return ret; |
661 | |
662 | return 0; |
663 | } |
664 | |
665 | static void media_pipeline_cleanup(struct media_pipeline *pipe) |
666 | { |
667 | while (!list_empty(head: &pipe->pads)) { |
668 | struct media_pipeline_pad *ppad; |
669 | |
670 | ppad = list_first_entry(&pipe->pads, typeof(*ppad), list); |
671 | list_del(entry: &ppad->list); |
672 | kfree(objp: ppad); |
673 | } |
674 | } |
675 | |
676 | static int media_pipeline_populate(struct media_pipeline *pipe, |
677 | struct media_pad *pad) |
678 | { |
679 | struct media_pipeline_walk walk = { }; |
680 | struct media_pipeline_pad *ppad; |
681 | int ret; |
682 | |
683 | /* |
684 | * Populate the media pipeline by walking the media graph, starting |
685 | * from @pad. |
686 | */ |
687 | INIT_LIST_HEAD(list: &pipe->pads); |
688 | pipe->mdev = pad->graph_obj.mdev; |
689 | |
690 | walk.mdev = pipe->mdev; |
691 | walk.stack.top = -1; |
692 | ret = media_pipeline_add_pad(pipe, walk: &walk, pad); |
693 | if (ret) |
694 | goto done; |
695 | |
696 | /* |
697 | * Use a depth-first search algorithm: as long as the stack is not |
698 | * empty, explore the next link of the top entry. The |
699 | * media_pipeline_explore_next_link() function will either move to the |
700 | * next link, pop the entry if fully visited, or add new entries on |
701 | * top. |
702 | */ |
703 | while (!media_pipeline_walk_empty(walk: &walk)) { |
704 | ret = media_pipeline_explore_next_link(pipe, walk: &walk); |
705 | if (ret) |
706 | goto done; |
707 | } |
708 | |
709 | dev_dbg(pad->graph_obj.mdev->dev, |
710 | "media pipeline populated, found pads:\n" ); |
711 | |
712 | list_for_each_entry(ppad, &pipe->pads, list) |
713 | dev_dbg(pad->graph_obj.mdev->dev, "- '%s':%u\n" , |
714 | ppad->pad->entity->name, ppad->pad->index); |
715 | |
716 | WARN_ON(walk.stack.top != -1); |
717 | |
718 | ret = 0; |
719 | |
720 | done: |
721 | media_pipeline_walk_destroy(walk: &walk); |
722 | |
723 | if (ret) |
724 | media_pipeline_cleanup(pipe); |
725 | |
726 | return ret; |
727 | } |
728 | |
729 | __must_check int __media_pipeline_start(struct media_pad *pad, |
730 | struct media_pipeline *pipe) |
731 | { |
732 | struct media_device *mdev = pad->graph_obj.mdev; |
733 | struct media_pipeline_pad *err_ppad; |
734 | struct media_pipeline_pad *ppad; |
735 | int ret; |
736 | |
737 | lockdep_assert_held(&mdev->graph_mutex); |
738 | |
739 | /* |
740 | * If the pad is already part of a pipeline, that pipeline must be the |
741 | * same as the pipe given to media_pipeline_start(). |
742 | */ |
743 | if (WARN_ON(pad->pipe && pad->pipe != pipe)) |
744 | return -EINVAL; |
745 | |
746 | /* |
747 | * If the pipeline has already been started, it is guaranteed to be |
748 | * valid, so just increase the start count. |
749 | */ |
750 | if (pipe->start_count) { |
751 | pipe->start_count++; |
752 | return 0; |
753 | } |
754 | |
755 | /* |
756 | * Populate the pipeline. This populates the media_pipeline pads list |
757 | * with media_pipeline_pad instances for each pad found during graph |
758 | * walk. |
759 | */ |
760 | ret = media_pipeline_populate(pipe, pad); |
761 | if (ret) |
762 | return ret; |
763 | |
764 | /* |
765 | * Now that all the pads in the pipeline have been gathered, perform |
766 | * the validation steps. |
767 | */ |
768 | |
769 | list_for_each_entry(ppad, &pipe->pads, list) { |
770 | struct media_pad *pad = ppad->pad; |
771 | struct media_entity *entity = pad->entity; |
772 | bool has_enabled_link = false; |
773 | bool has_link = false; |
774 | struct media_link *link; |
775 | |
776 | dev_dbg(mdev->dev, "Validating pad '%s':%u\n" , pad->entity->name, |
777 | pad->index); |
778 | |
779 | /* |
780 | * 1. Ensure that the pad doesn't already belong to a different |
781 | * pipeline. |
782 | */ |
783 | if (pad->pipe) { |
784 | dev_dbg(mdev->dev, "Failed to start pipeline: pad '%s':%u busy\n" , |
785 | pad->entity->name, pad->index); |
786 | ret = -EBUSY; |
787 | goto error; |
788 | } |
789 | |
790 | /* |
791 | * 2. Validate all active links whose sink is the current pad. |
792 | * Validation of the source pads is performed in the context of |
793 | * the connected sink pad to avoid duplicating checks. |
794 | */ |
795 | for_each_media_entity_data_link(entity, link) { |
796 | /* Skip links unrelated to the current pad. */ |
797 | if (link->sink != pad && link->source != pad) |
798 | continue; |
799 | |
800 | /* Record if the pad has links and enabled links. */ |
801 | if (link->flags & MEDIA_LNK_FL_ENABLED) |
802 | has_enabled_link = true; |
803 | has_link = true; |
804 | |
805 | /* |
806 | * Validate the link if it's enabled and has the |
807 | * current pad as its sink. |
808 | */ |
809 | if (!(link->flags & MEDIA_LNK_FL_ENABLED)) |
810 | continue; |
811 | |
812 | if (link->sink != pad) |
813 | continue; |
814 | |
815 | if (!entity->ops || !entity->ops->link_validate) |
816 | continue; |
817 | |
818 | ret = entity->ops->link_validate(link); |
819 | if (ret) { |
820 | dev_dbg(mdev->dev, |
821 | "Link '%s':%u -> '%s':%u failed validation: %d\n" , |
822 | link->source->entity->name, |
823 | link->source->index, |
824 | link->sink->entity->name, |
825 | link->sink->index, ret); |
826 | goto error; |
827 | } |
828 | |
829 | dev_dbg(mdev->dev, |
830 | "Link '%s':%u -> '%s':%u is valid\n" , |
831 | link->source->entity->name, |
832 | link->source->index, |
833 | link->sink->entity->name, |
834 | link->sink->index); |
835 | } |
836 | |
837 | /* |
838 | * 3. If the pad has the MEDIA_PAD_FL_MUST_CONNECT flag set, |
839 | * ensure that it has either no link or an enabled link. |
840 | */ |
841 | if ((pad->flags & MEDIA_PAD_FL_MUST_CONNECT) && has_link && |
842 | !has_enabled_link) { |
843 | dev_dbg(mdev->dev, |
844 | "Pad '%s':%u must be connected by an enabled link\n" , |
845 | pad->entity->name, pad->index); |
846 | ret = -ENOLINK; |
847 | goto error; |
848 | } |
849 | |
850 | /* Validation passed, store the pipe pointer in the pad. */ |
851 | pad->pipe = pipe; |
852 | } |
853 | |
854 | pipe->start_count++; |
855 | |
856 | return 0; |
857 | |
858 | error: |
859 | /* |
860 | * Link validation on graph failed. We revert what we did and |
861 | * return the error. |
862 | */ |
863 | |
864 | list_for_each_entry(err_ppad, &pipe->pads, list) { |
865 | if (err_ppad == ppad) |
866 | break; |
867 | |
868 | err_ppad->pad->pipe = NULL; |
869 | } |
870 | |
871 | media_pipeline_cleanup(pipe); |
872 | |
873 | return ret; |
874 | } |
875 | EXPORT_SYMBOL_GPL(__media_pipeline_start); |
876 | |
877 | __must_check int media_pipeline_start(struct media_pad *pad, |
878 | struct media_pipeline *pipe) |
879 | { |
880 | struct media_device *mdev = pad->graph_obj.mdev; |
881 | int ret; |
882 | |
883 | mutex_lock(&mdev->graph_mutex); |
884 | ret = __media_pipeline_start(pad, pipe); |
885 | mutex_unlock(lock: &mdev->graph_mutex); |
886 | return ret; |
887 | } |
888 | EXPORT_SYMBOL_GPL(media_pipeline_start); |
889 | |
890 | void __media_pipeline_stop(struct media_pad *pad) |
891 | { |
892 | struct media_pipeline *pipe = pad->pipe; |
893 | struct media_pipeline_pad *ppad; |
894 | |
895 | /* |
896 | * If the following check fails, the driver has performed an |
897 | * unbalanced call to media_pipeline_stop() |
898 | */ |
899 | if (WARN_ON(!pipe)) |
900 | return; |
901 | |
902 | if (--pipe->start_count) |
903 | return; |
904 | |
905 | list_for_each_entry(ppad, &pipe->pads, list) |
906 | ppad->pad->pipe = NULL; |
907 | |
908 | media_pipeline_cleanup(pipe); |
909 | |
910 | if (pipe->allocated) |
911 | kfree(objp: pipe); |
912 | } |
913 | EXPORT_SYMBOL_GPL(__media_pipeline_stop); |
914 | |
915 | void media_pipeline_stop(struct media_pad *pad) |
916 | { |
917 | struct media_device *mdev = pad->graph_obj.mdev; |
918 | |
919 | mutex_lock(&mdev->graph_mutex); |
920 | __media_pipeline_stop(pad); |
921 | mutex_unlock(lock: &mdev->graph_mutex); |
922 | } |
923 | EXPORT_SYMBOL_GPL(media_pipeline_stop); |
924 | |
925 | __must_check int media_pipeline_alloc_start(struct media_pad *pad) |
926 | { |
927 | struct media_device *mdev = pad->graph_obj.mdev; |
928 | struct media_pipeline *new_pipe = NULL; |
929 | struct media_pipeline *pipe; |
930 | int ret; |
931 | |
932 | mutex_lock(&mdev->graph_mutex); |
933 | |
934 | /* |
935 | * Is the pad already part of a pipeline? If not, we need to allocate |
936 | * a pipe. |
937 | */ |
938 | pipe = media_pad_pipeline(pad); |
939 | if (!pipe) { |
940 | new_pipe = kzalloc(size: sizeof(*new_pipe), GFP_KERNEL); |
941 | if (!new_pipe) { |
942 | ret = -ENOMEM; |
943 | goto out; |
944 | } |
945 | |
946 | pipe = new_pipe; |
947 | pipe->allocated = true; |
948 | } |
949 | |
950 | ret = __media_pipeline_start(pad, pipe); |
951 | if (ret) |
952 | kfree(objp: new_pipe); |
953 | |
954 | out: |
955 | mutex_unlock(lock: &mdev->graph_mutex); |
956 | |
957 | return ret; |
958 | } |
959 | EXPORT_SYMBOL_GPL(media_pipeline_alloc_start); |
960 | |
961 | struct media_pad * |
962 | __media_pipeline_pad_iter_next(struct media_pipeline *pipe, |
963 | struct media_pipeline_pad_iter *iter, |
964 | struct media_pad *pad) |
965 | { |
966 | if (!pad) |
967 | iter->cursor = pipe->pads.next; |
968 | |
969 | if (iter->cursor == &pipe->pads) |
970 | return NULL; |
971 | |
972 | pad = list_entry(iter->cursor, struct media_pipeline_pad, list)->pad; |
973 | iter->cursor = iter->cursor->next; |
974 | |
975 | return pad; |
976 | } |
977 | EXPORT_SYMBOL_GPL(__media_pipeline_pad_iter_next); |
978 | |
979 | int media_pipeline_entity_iter_init(struct media_pipeline *pipe, |
980 | struct media_pipeline_entity_iter *iter) |
981 | { |
982 | return media_entity_enum_init(&iter->ent_enum, pipe->mdev); |
983 | } |
984 | EXPORT_SYMBOL_GPL(media_pipeline_entity_iter_init); |
985 | |
986 | void media_pipeline_entity_iter_cleanup(struct media_pipeline_entity_iter *iter) |
987 | { |
988 | media_entity_enum_cleanup(&iter->ent_enum); |
989 | } |
990 | EXPORT_SYMBOL_GPL(media_pipeline_entity_iter_cleanup); |
991 | |
992 | struct media_entity * |
993 | __media_pipeline_entity_iter_next(struct media_pipeline *pipe, |
994 | struct media_pipeline_entity_iter *iter, |
995 | struct media_entity *entity) |
996 | { |
997 | if (!entity) |
998 | iter->cursor = pipe->pads.next; |
999 | |
1000 | while (iter->cursor != &pipe->pads) { |
1001 | struct media_pipeline_pad *ppad; |
1002 | struct media_entity *entity; |
1003 | |
1004 | ppad = list_entry(iter->cursor, struct media_pipeline_pad, list); |
1005 | entity = ppad->pad->entity; |
1006 | iter->cursor = iter->cursor->next; |
1007 | |
1008 | if (!media_entity_enum_test_and_set(ent_enum: &iter->ent_enum, entity)) |
1009 | return entity; |
1010 | } |
1011 | |
1012 | return NULL; |
1013 | } |
1014 | EXPORT_SYMBOL_GPL(__media_pipeline_entity_iter_next); |
1015 | |
1016 | /* ----------------------------------------------------------------------------- |
1017 | * Links management |
1018 | */ |
1019 | |
1020 | static struct media_link *media_add_link(struct list_head *head) |
1021 | { |
1022 | struct media_link *link; |
1023 | |
1024 | link = kzalloc(size: sizeof(*link), GFP_KERNEL); |
1025 | if (link == NULL) |
1026 | return NULL; |
1027 | |
1028 | list_add_tail(new: &link->list, head); |
1029 | |
1030 | return link; |
1031 | } |
1032 | |
1033 | static void __media_entity_remove_link(struct media_entity *entity, |
1034 | struct media_link *link) |
1035 | { |
1036 | struct media_link *rlink, *tmp; |
1037 | struct media_entity *remote; |
1038 | |
1039 | /* Remove the reverse links for a data link. */ |
1040 | if ((link->flags & MEDIA_LNK_FL_LINK_TYPE) == MEDIA_LNK_FL_DATA_LINK) { |
1041 | if (link->source->entity == entity) |
1042 | remote = link->sink->entity; |
1043 | else |
1044 | remote = link->source->entity; |
1045 | |
1046 | list_for_each_entry_safe(rlink, tmp, &remote->links, list) { |
1047 | if (rlink != link->reverse) |
1048 | continue; |
1049 | |
1050 | if (link->source->entity == entity) |
1051 | remote->num_backlinks--; |
1052 | |
1053 | /* Remove the remote link */ |
1054 | list_del(entry: &rlink->list); |
1055 | media_gobj_destroy(gobj: &rlink->graph_obj); |
1056 | kfree(objp: rlink); |
1057 | |
1058 | if (--remote->num_links == 0) |
1059 | break; |
1060 | } |
1061 | } |
1062 | |
1063 | list_del(entry: &link->list); |
1064 | media_gobj_destroy(gobj: &link->graph_obj); |
1065 | kfree(objp: link); |
1066 | } |
1067 | |
1068 | int media_get_pad_index(struct media_entity *entity, u32 pad_type, |
1069 | enum media_pad_signal_type sig_type) |
1070 | { |
1071 | unsigned int i; |
1072 | |
1073 | if (!entity) |
1074 | return -EINVAL; |
1075 | |
1076 | for (i = 0; i < entity->num_pads; i++) { |
1077 | if ((entity->pads[i].flags & |
1078 | (MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_SOURCE)) != pad_type) |
1079 | continue; |
1080 | |
1081 | if (entity->pads[i].sig_type == sig_type) |
1082 | return i; |
1083 | } |
1084 | return -EINVAL; |
1085 | } |
1086 | EXPORT_SYMBOL_GPL(media_get_pad_index); |
1087 | |
1088 | int |
1089 | media_create_pad_link(struct media_entity *source, u16 source_pad, |
1090 | struct media_entity *sink, u16 sink_pad, u32 flags) |
1091 | { |
1092 | struct media_link *link; |
1093 | struct media_link *backlink; |
1094 | |
1095 | if (WARN_ON(!source || !sink) || |
1096 | WARN_ON(source_pad >= source->num_pads) || |
1097 | WARN_ON(sink_pad >= sink->num_pads)) |
1098 | return -EINVAL; |
1099 | if (WARN_ON(!(source->pads[source_pad].flags & MEDIA_PAD_FL_SOURCE))) |
1100 | return -EINVAL; |
1101 | if (WARN_ON(!(sink->pads[sink_pad].flags & MEDIA_PAD_FL_SINK))) |
1102 | return -EINVAL; |
1103 | |
1104 | link = media_add_link(head: &source->links); |
1105 | if (link == NULL) |
1106 | return -ENOMEM; |
1107 | |
1108 | link->source = &source->pads[source_pad]; |
1109 | link->sink = &sink->pads[sink_pad]; |
1110 | link->flags = flags & ~MEDIA_LNK_FL_INTERFACE_LINK; |
1111 | |
1112 | /* Initialize graph object embedded at the new link */ |
1113 | media_gobj_create(mdev: source->graph_obj.mdev, type: MEDIA_GRAPH_LINK, |
1114 | gobj: &link->graph_obj); |
1115 | |
1116 | /* Create the backlink. Backlinks are used to help graph traversal and |
1117 | * are not reported to userspace. |
1118 | */ |
1119 | backlink = media_add_link(head: &sink->links); |
1120 | if (backlink == NULL) { |
1121 | __media_entity_remove_link(entity: source, link); |
1122 | return -ENOMEM; |
1123 | } |
1124 | |
1125 | backlink->source = &source->pads[source_pad]; |
1126 | backlink->sink = &sink->pads[sink_pad]; |
1127 | backlink->flags = flags; |
1128 | backlink->is_backlink = true; |
1129 | |
1130 | /* Initialize graph object embedded at the new link */ |
1131 | media_gobj_create(mdev: sink->graph_obj.mdev, type: MEDIA_GRAPH_LINK, |
1132 | gobj: &backlink->graph_obj); |
1133 | |
1134 | link->reverse = backlink; |
1135 | backlink->reverse = link; |
1136 | |
1137 | sink->num_backlinks++; |
1138 | sink->num_links++; |
1139 | source->num_links++; |
1140 | |
1141 | return 0; |
1142 | } |
1143 | EXPORT_SYMBOL_GPL(media_create_pad_link); |
1144 | |
1145 | int media_create_pad_links(const struct media_device *mdev, |
1146 | const u32 source_function, |
1147 | struct media_entity *source, |
1148 | const u16 source_pad, |
1149 | const u32 sink_function, |
1150 | struct media_entity *sink, |
1151 | const u16 sink_pad, |
1152 | u32 flags, |
1153 | const bool allow_both_undefined) |
1154 | { |
1155 | struct media_entity *entity; |
1156 | unsigned function; |
1157 | int ret; |
1158 | |
1159 | /* Trivial case: 1:1 relation */ |
1160 | if (source && sink) |
1161 | return media_create_pad_link(source, source_pad, |
1162 | sink, sink_pad, flags); |
1163 | |
1164 | /* Worse case scenario: n:n relation */ |
1165 | if (!source && !sink) { |
1166 | if (!allow_both_undefined) |
1167 | return 0; |
1168 | media_device_for_each_entity(source, mdev) { |
1169 | if (source->function != source_function) |
1170 | continue; |
1171 | media_device_for_each_entity(sink, mdev) { |
1172 | if (sink->function != sink_function) |
1173 | continue; |
1174 | ret = media_create_pad_link(source, source_pad, |
1175 | sink, sink_pad, |
1176 | flags); |
1177 | if (ret) |
1178 | return ret; |
1179 | flags &= ~(MEDIA_LNK_FL_ENABLED | |
1180 | MEDIA_LNK_FL_IMMUTABLE); |
1181 | } |
1182 | } |
1183 | return 0; |
1184 | } |
1185 | |
1186 | /* Handle 1:n and n:1 cases */ |
1187 | if (source) |
1188 | function = sink_function; |
1189 | else |
1190 | function = source_function; |
1191 | |
1192 | media_device_for_each_entity(entity, mdev) { |
1193 | if (entity->function != function) |
1194 | continue; |
1195 | |
1196 | if (source) |
1197 | ret = media_create_pad_link(source, source_pad, |
1198 | entity, sink_pad, flags); |
1199 | else |
1200 | ret = media_create_pad_link(entity, source_pad, |
1201 | sink, sink_pad, flags); |
1202 | if (ret) |
1203 | return ret; |
1204 | flags &= ~(MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE); |
1205 | } |
1206 | return 0; |
1207 | } |
1208 | EXPORT_SYMBOL_GPL(media_create_pad_links); |
1209 | |
1210 | void __media_entity_remove_links(struct media_entity *entity) |
1211 | { |
1212 | struct media_link *link, *tmp; |
1213 | |
1214 | list_for_each_entry_safe(link, tmp, &entity->links, list) |
1215 | __media_entity_remove_link(entity, link); |
1216 | |
1217 | entity->num_links = 0; |
1218 | entity->num_backlinks = 0; |
1219 | } |
1220 | EXPORT_SYMBOL_GPL(__media_entity_remove_links); |
1221 | |
1222 | void media_entity_remove_links(struct media_entity *entity) |
1223 | { |
1224 | struct media_device *mdev = entity->graph_obj.mdev; |
1225 | |
1226 | /* Do nothing if the entity is not registered. */ |
1227 | if (mdev == NULL) |
1228 | return; |
1229 | |
1230 | mutex_lock(&mdev->graph_mutex); |
1231 | __media_entity_remove_links(entity); |
1232 | mutex_unlock(lock: &mdev->graph_mutex); |
1233 | } |
1234 | EXPORT_SYMBOL_GPL(media_entity_remove_links); |
1235 | |
1236 | static int __media_entity_setup_link_notify(struct media_link *link, u32 flags) |
1237 | { |
1238 | int ret; |
1239 | |
1240 | /* Notify both entities. */ |
1241 | ret = media_entity_call(link->source->entity, link_setup, |
1242 | link->source, link->sink, flags); |
1243 | if (ret < 0 && ret != -ENOIOCTLCMD) |
1244 | return ret; |
1245 | |
1246 | ret = media_entity_call(link->sink->entity, link_setup, |
1247 | link->sink, link->source, flags); |
1248 | if (ret < 0 && ret != -ENOIOCTLCMD) { |
1249 | media_entity_call(link->source->entity, link_setup, |
1250 | link->source, link->sink, link->flags); |
1251 | return ret; |
1252 | } |
1253 | |
1254 | link->flags = flags; |
1255 | link->reverse->flags = link->flags; |
1256 | |
1257 | return 0; |
1258 | } |
1259 | |
1260 | int __media_entity_setup_link(struct media_link *link, u32 flags) |
1261 | { |
1262 | const u32 mask = MEDIA_LNK_FL_ENABLED; |
1263 | struct media_device *mdev; |
1264 | struct media_pad *source, *sink; |
1265 | int ret = -EBUSY; |
1266 | |
1267 | if (link == NULL) |
1268 | return -EINVAL; |
1269 | |
1270 | /* The non-modifiable link flags must not be modified. */ |
1271 | if ((link->flags & ~mask) != (flags & ~mask)) |
1272 | return -EINVAL; |
1273 | |
1274 | if (link->flags & MEDIA_LNK_FL_IMMUTABLE) |
1275 | return link->flags == flags ? 0 : -EINVAL; |
1276 | |
1277 | if (link->flags == flags) |
1278 | return 0; |
1279 | |
1280 | source = link->source; |
1281 | sink = link->sink; |
1282 | |
1283 | if (!(link->flags & MEDIA_LNK_FL_DYNAMIC) && |
1284 | (media_pad_is_streaming(pad: source) || media_pad_is_streaming(pad: sink))) |
1285 | return -EBUSY; |
1286 | |
1287 | mdev = source->graph_obj.mdev; |
1288 | |
1289 | if (mdev->ops && mdev->ops->link_notify) { |
1290 | ret = mdev->ops->link_notify(link, flags, |
1291 | MEDIA_DEV_NOTIFY_PRE_LINK_CH); |
1292 | if (ret < 0) |
1293 | return ret; |
1294 | } |
1295 | |
1296 | ret = __media_entity_setup_link_notify(link, flags); |
1297 | |
1298 | if (mdev->ops && mdev->ops->link_notify) |
1299 | mdev->ops->link_notify(link, flags, |
1300 | MEDIA_DEV_NOTIFY_POST_LINK_CH); |
1301 | |
1302 | return ret; |
1303 | } |
1304 | EXPORT_SYMBOL_GPL(__media_entity_setup_link); |
1305 | |
1306 | int media_entity_setup_link(struct media_link *link, u32 flags) |
1307 | { |
1308 | int ret; |
1309 | |
1310 | mutex_lock(&link->graph_obj.mdev->graph_mutex); |
1311 | ret = __media_entity_setup_link(link, flags); |
1312 | mutex_unlock(lock: &link->graph_obj.mdev->graph_mutex); |
1313 | |
1314 | return ret; |
1315 | } |
1316 | EXPORT_SYMBOL_GPL(media_entity_setup_link); |
1317 | |
1318 | struct media_link * |
1319 | media_entity_find_link(struct media_pad *source, struct media_pad *sink) |
1320 | { |
1321 | struct media_link *link; |
1322 | |
1323 | for_each_media_entity_data_link(source->entity, link) { |
1324 | if (link->source->entity == source->entity && |
1325 | link->source->index == source->index && |
1326 | link->sink->entity == sink->entity && |
1327 | link->sink->index == sink->index) |
1328 | return link; |
1329 | } |
1330 | |
1331 | return NULL; |
1332 | } |
1333 | EXPORT_SYMBOL_GPL(media_entity_find_link); |
1334 | |
1335 | struct media_pad *media_pad_remote_pad_first(const struct media_pad *pad) |
1336 | { |
1337 | struct media_link *link; |
1338 | |
1339 | for_each_media_entity_data_link(pad->entity, link) { |
1340 | if (!(link->flags & MEDIA_LNK_FL_ENABLED)) |
1341 | continue; |
1342 | |
1343 | if (link->source == pad) |
1344 | return link->sink; |
1345 | |
1346 | if (link->sink == pad) |
1347 | return link->source; |
1348 | } |
1349 | |
1350 | return NULL; |
1351 | |
1352 | } |
1353 | EXPORT_SYMBOL_GPL(media_pad_remote_pad_first); |
1354 | |
1355 | struct media_pad * |
1356 | media_entity_remote_pad_unique(const struct media_entity *entity, |
1357 | unsigned int type) |
1358 | { |
1359 | struct media_pad *pad = NULL; |
1360 | struct media_link *link; |
1361 | |
1362 | list_for_each_entry(link, &entity->links, list) { |
1363 | struct media_pad *local_pad; |
1364 | struct media_pad *remote_pad; |
1365 | |
1366 | if (((link->flags & MEDIA_LNK_FL_LINK_TYPE) != |
1367 | MEDIA_LNK_FL_DATA_LINK) || |
1368 | !(link->flags & MEDIA_LNK_FL_ENABLED)) |
1369 | continue; |
1370 | |
1371 | if (type == MEDIA_PAD_FL_SOURCE) { |
1372 | local_pad = link->sink; |
1373 | remote_pad = link->source; |
1374 | } else { |
1375 | local_pad = link->source; |
1376 | remote_pad = link->sink; |
1377 | } |
1378 | |
1379 | if (local_pad->entity == entity) { |
1380 | if (pad) |
1381 | return ERR_PTR(error: -ENOTUNIQ); |
1382 | |
1383 | pad = remote_pad; |
1384 | } |
1385 | } |
1386 | |
1387 | if (!pad) |
1388 | return ERR_PTR(error: -ENOLINK); |
1389 | |
1390 | return pad; |
1391 | } |
1392 | EXPORT_SYMBOL_GPL(media_entity_remote_pad_unique); |
1393 | |
1394 | struct media_pad *media_pad_remote_pad_unique(const struct media_pad *pad) |
1395 | { |
1396 | struct media_pad *found_pad = NULL; |
1397 | struct media_link *link; |
1398 | |
1399 | list_for_each_entry(link, &pad->entity->links, list) { |
1400 | struct media_pad *remote_pad; |
1401 | |
1402 | if (!(link->flags & MEDIA_LNK_FL_ENABLED)) |
1403 | continue; |
1404 | |
1405 | if (link->sink == pad) |
1406 | remote_pad = link->source; |
1407 | else if (link->source == pad) |
1408 | remote_pad = link->sink; |
1409 | else |
1410 | continue; |
1411 | |
1412 | if (found_pad) |
1413 | return ERR_PTR(error: -ENOTUNIQ); |
1414 | |
1415 | found_pad = remote_pad; |
1416 | } |
1417 | |
1418 | if (!found_pad) |
1419 | return ERR_PTR(error: -ENOLINK); |
1420 | |
1421 | return found_pad; |
1422 | } |
1423 | EXPORT_SYMBOL_GPL(media_pad_remote_pad_unique); |
1424 | |
1425 | int media_entity_get_fwnode_pad(struct media_entity *entity, |
1426 | const struct fwnode_handle *fwnode, |
1427 | unsigned long direction_flags) |
1428 | { |
1429 | struct fwnode_endpoint endpoint; |
1430 | unsigned int i; |
1431 | int ret; |
1432 | |
1433 | if (!entity->ops || !entity->ops->get_fwnode_pad) { |
1434 | for (i = 0; i < entity->num_pads; i++) { |
1435 | if (entity->pads[i].flags & direction_flags) |
1436 | return i; |
1437 | } |
1438 | |
1439 | return -ENXIO; |
1440 | } |
1441 | |
1442 | ret = fwnode_graph_parse_endpoint(fwnode, endpoint: &endpoint); |
1443 | if (ret) |
1444 | return ret; |
1445 | |
1446 | ret = entity->ops->get_fwnode_pad(entity, &endpoint); |
1447 | if (ret < 0) |
1448 | return ret; |
1449 | |
1450 | if (ret >= entity->num_pads) |
1451 | return -ENXIO; |
1452 | |
1453 | if (!(entity->pads[ret].flags & direction_flags)) |
1454 | return -ENXIO; |
1455 | |
1456 | return ret; |
1457 | } |
1458 | EXPORT_SYMBOL_GPL(media_entity_get_fwnode_pad); |
1459 | |
1460 | struct media_pipeline *media_entity_pipeline(struct media_entity *entity) |
1461 | { |
1462 | struct media_pad *pad; |
1463 | |
1464 | media_entity_for_each_pad(entity, pad) { |
1465 | if (pad->pipe) |
1466 | return pad->pipe; |
1467 | } |
1468 | |
1469 | return NULL; |
1470 | } |
1471 | EXPORT_SYMBOL_GPL(media_entity_pipeline); |
1472 | |
1473 | struct media_pipeline *media_pad_pipeline(struct media_pad *pad) |
1474 | { |
1475 | return pad->pipe; |
1476 | } |
1477 | EXPORT_SYMBOL_GPL(media_pad_pipeline); |
1478 | |
1479 | static void media_interface_init(struct media_device *mdev, |
1480 | struct media_interface *intf, |
1481 | u32 gobj_type, |
1482 | u32 intf_type, u32 flags) |
1483 | { |
1484 | intf->type = intf_type; |
1485 | intf->flags = flags; |
1486 | INIT_LIST_HEAD(list: &intf->links); |
1487 | |
1488 | media_gobj_create(mdev, type: gobj_type, gobj: &intf->graph_obj); |
1489 | } |
1490 | |
1491 | /* Functions related to the media interface via device nodes */ |
1492 | |
1493 | struct media_intf_devnode *media_devnode_create(struct media_device *mdev, |
1494 | u32 type, u32 flags, |
1495 | u32 major, u32 minor) |
1496 | { |
1497 | struct media_intf_devnode *devnode; |
1498 | |
1499 | devnode = kzalloc(size: sizeof(*devnode), GFP_KERNEL); |
1500 | if (!devnode) |
1501 | return NULL; |
1502 | |
1503 | devnode->major = major; |
1504 | devnode->minor = minor; |
1505 | |
1506 | media_interface_init(mdev, intf: &devnode->intf, gobj_type: MEDIA_GRAPH_INTF_DEVNODE, |
1507 | intf_type: type, flags); |
1508 | |
1509 | return devnode; |
1510 | } |
1511 | EXPORT_SYMBOL_GPL(media_devnode_create); |
1512 | |
1513 | void media_devnode_remove(struct media_intf_devnode *devnode) |
1514 | { |
1515 | media_remove_intf_links(intf: &devnode->intf); |
1516 | media_gobj_destroy(gobj: &devnode->intf.graph_obj); |
1517 | kfree(objp: devnode); |
1518 | } |
1519 | EXPORT_SYMBOL_GPL(media_devnode_remove); |
1520 | |
1521 | struct media_link *media_create_intf_link(struct media_entity *entity, |
1522 | struct media_interface *intf, |
1523 | u32 flags) |
1524 | { |
1525 | struct media_link *link; |
1526 | |
1527 | link = media_add_link(head: &intf->links); |
1528 | if (link == NULL) |
1529 | return NULL; |
1530 | |
1531 | link->intf = intf; |
1532 | link->entity = entity; |
1533 | link->flags = flags | MEDIA_LNK_FL_INTERFACE_LINK; |
1534 | |
1535 | /* Initialize graph object embedded at the new link */ |
1536 | media_gobj_create(mdev: intf->graph_obj.mdev, type: MEDIA_GRAPH_LINK, |
1537 | gobj: &link->graph_obj); |
1538 | |
1539 | return link; |
1540 | } |
1541 | EXPORT_SYMBOL_GPL(media_create_intf_link); |
1542 | |
1543 | void __media_remove_intf_link(struct media_link *link) |
1544 | { |
1545 | list_del(entry: &link->list); |
1546 | media_gobj_destroy(gobj: &link->graph_obj); |
1547 | kfree(objp: link); |
1548 | } |
1549 | EXPORT_SYMBOL_GPL(__media_remove_intf_link); |
1550 | |
1551 | void media_remove_intf_link(struct media_link *link) |
1552 | { |
1553 | struct media_device *mdev = link->graph_obj.mdev; |
1554 | |
1555 | /* Do nothing if the intf is not registered. */ |
1556 | if (mdev == NULL) |
1557 | return; |
1558 | |
1559 | mutex_lock(&mdev->graph_mutex); |
1560 | __media_remove_intf_link(link); |
1561 | mutex_unlock(lock: &mdev->graph_mutex); |
1562 | } |
1563 | EXPORT_SYMBOL_GPL(media_remove_intf_link); |
1564 | |
1565 | void __media_remove_intf_links(struct media_interface *intf) |
1566 | { |
1567 | struct media_link *link, *tmp; |
1568 | |
1569 | list_for_each_entry_safe(link, tmp, &intf->links, list) |
1570 | __media_remove_intf_link(link); |
1571 | |
1572 | } |
1573 | EXPORT_SYMBOL_GPL(__media_remove_intf_links); |
1574 | |
1575 | void media_remove_intf_links(struct media_interface *intf) |
1576 | { |
1577 | struct media_device *mdev = intf->graph_obj.mdev; |
1578 | |
1579 | /* Do nothing if the intf is not registered. */ |
1580 | if (mdev == NULL) |
1581 | return; |
1582 | |
1583 | mutex_lock(&mdev->graph_mutex); |
1584 | __media_remove_intf_links(intf); |
1585 | mutex_unlock(lock: &mdev->graph_mutex); |
1586 | } |
1587 | EXPORT_SYMBOL_GPL(media_remove_intf_links); |
1588 | |
1589 | struct media_link *media_create_ancillary_link(struct media_entity *primary, |
1590 | struct media_entity *ancillary) |
1591 | { |
1592 | struct media_link *link; |
1593 | |
1594 | link = media_add_link(head: &primary->links); |
1595 | if (!link) |
1596 | return ERR_PTR(error: -ENOMEM); |
1597 | |
1598 | link->gobj0 = &primary->graph_obj; |
1599 | link->gobj1 = &ancillary->graph_obj; |
1600 | link->flags = MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED | |
1601 | MEDIA_LNK_FL_ANCILLARY_LINK; |
1602 | |
1603 | /* Initialize graph object embedded in the new link */ |
1604 | media_gobj_create(mdev: primary->graph_obj.mdev, type: MEDIA_GRAPH_LINK, |
1605 | gobj: &link->graph_obj); |
1606 | |
1607 | return link; |
1608 | } |
1609 | EXPORT_SYMBOL_GPL(media_create_ancillary_link); |
1610 | |
1611 | struct media_link *__media_entity_next_link(struct media_entity *entity, |
1612 | struct media_link *link, |
1613 | unsigned long link_type) |
1614 | { |
1615 | link = link ? list_next_entry(link, list) |
1616 | : list_first_entry(&entity->links, typeof(*link), list); |
1617 | |
1618 | list_for_each_entry_from(link, &entity->links, list) |
1619 | if ((link->flags & MEDIA_LNK_FL_LINK_TYPE) == link_type) |
1620 | return link; |
1621 | |
1622 | return NULL; |
1623 | } |
1624 | EXPORT_SYMBOL_GPL(__media_entity_next_link); |
1625 | |