diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:27:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:27:49 +0000 |
commit | ace9429bb58fd418f0c81d4c2835699bddf6bde6 (patch) | |
tree | b2d64bc10158fdd5497876388cd68142ca374ed3 /drivers/media/mc/mc-entity.c | |
parent | Initial commit. (diff) | |
download | linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.tar.xz linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.zip |
Adding upstream version 6.6.15.upstream/6.6.15
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/media/mc/mc-entity.c')
-rw-r--r-- | drivers/media/mc/mc-entity.c | 1611 |
1 files changed, 1611 insertions, 0 deletions
diff --git a/drivers/media/mc/mc-entity.c b/drivers/media/mc/mc-entity.c new file mode 100644 index 0000000000..83468d4a44 --- /dev/null +++ b/drivers/media/mc/mc-entity.c @@ -0,0 +1,1611 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Media entity + * + * Copyright (C) 2010 Nokia Corporation + * + * Contacts: Laurent Pinchart <laurent.pinchart@ideasonboard.com> + * Sakari Ailus <sakari.ailus@iki.fi> + */ + +#include <linux/bitmap.h> +#include <linux/list.h> +#include <linux/property.h> +#include <linux/slab.h> +#include <media/media-entity.h> +#include <media/media-device.h> + +static inline const char *intf_type(struct media_interface *intf) +{ + switch (intf->type) { + case MEDIA_INTF_T_DVB_FE: + return "dvb-frontend"; + case MEDIA_INTF_T_DVB_DEMUX: + return "dvb-demux"; + case MEDIA_INTF_T_DVB_DVR: + return "dvb-dvr"; + case MEDIA_INTF_T_DVB_CA: + return "dvb-ca"; + case MEDIA_INTF_T_DVB_NET: + return "dvb-net"; + case MEDIA_INTF_T_V4L_VIDEO: + return "v4l-video"; + case MEDIA_INTF_T_V4L_VBI: + return "v4l-vbi"; + case MEDIA_INTF_T_V4L_RADIO: + return "v4l-radio"; + case MEDIA_INTF_T_V4L_SUBDEV: + return "v4l-subdev"; + case MEDIA_INTF_T_V4L_SWRADIO: + return "v4l-swradio"; + case MEDIA_INTF_T_V4L_TOUCH: + return "v4l-touch"; + default: + return "unknown-intf"; + } +}; + +static inline const char *link_type_name(struct media_link *link) +{ + switch (link->flags & MEDIA_LNK_FL_LINK_TYPE) { + case MEDIA_LNK_FL_DATA_LINK: + return "data"; + case MEDIA_LNK_FL_INTERFACE_LINK: + return "interface"; + case MEDIA_LNK_FL_ANCILLARY_LINK: + return "ancillary"; + default: + return "unknown"; + } +} + +__must_check int media_entity_enum_init(struct media_entity_enum *ent_enum, + struct media_device *mdev) +{ + int idx_max; + + idx_max = ALIGN(mdev->entity_internal_idx_max + 1, BITS_PER_LONG); + ent_enum->bmap = bitmap_zalloc(idx_max, GFP_KERNEL); + if (!ent_enum->bmap) + return -ENOMEM; + + ent_enum->idx_max = idx_max; + + return 0; +} +EXPORT_SYMBOL_GPL(media_entity_enum_init); + +void media_entity_enum_cleanup(struct media_entity_enum *ent_enum) +{ + bitmap_free(ent_enum->bmap); +} +EXPORT_SYMBOL_GPL(media_entity_enum_cleanup); + +/** + * dev_dbg_obj - Prints in debug mode a change on some object + * + * @event_name: Name of the event to report. Could be __func__ + * @gobj: Pointer to the object + * + * Enabled only if DEBUG or CONFIG_DYNAMIC_DEBUG. Otherwise, it + * won't produce any code. + */ +static void dev_dbg_obj(const char *event_name, struct media_gobj *gobj) +{ +#if defined(DEBUG) || defined (CONFIG_DYNAMIC_DEBUG) + switch (media_type(gobj)) { + case MEDIA_GRAPH_ENTITY: + dev_dbg(gobj->mdev->dev, + "%s id %u: entity '%s'\n", + event_name, media_id(gobj), + gobj_to_entity(gobj)->name); + break; + case MEDIA_GRAPH_LINK: + { + struct media_link *link = gobj_to_link(gobj); + + dev_dbg(gobj->mdev->dev, + "%s id %u: %s link id %u ==> id %u\n", + event_name, media_id(gobj), link_type_name(link), + media_id(link->gobj0), + media_id(link->gobj1)); + break; + } + case MEDIA_GRAPH_PAD: + { + struct media_pad *pad = gobj_to_pad(gobj); + + dev_dbg(gobj->mdev->dev, + "%s id %u: %s%spad '%s':%d\n", + event_name, media_id(gobj), + pad->flags & MEDIA_PAD_FL_SINK ? "sink " : "", + pad->flags & MEDIA_PAD_FL_SOURCE ? "source " : "", + pad->entity->name, pad->index); + break; + } + case MEDIA_GRAPH_INTF_DEVNODE: + { + struct media_interface *intf = gobj_to_intf(gobj); + struct media_intf_devnode *devnode = intf_to_devnode(intf); + + dev_dbg(gobj->mdev->dev, + "%s id %u: intf_devnode %s - major: %d, minor: %d\n", + event_name, media_id(gobj), + intf_type(intf), + devnode->major, devnode->minor); + break; + } + } +#endif +} + +void media_gobj_create(struct media_device *mdev, + enum media_gobj_type type, + struct media_gobj *gobj) +{ + BUG_ON(!mdev); + + gobj->mdev = mdev; + + /* Create a per-type unique object ID */ + gobj->id = media_gobj_gen_id(type, ++mdev->id); + + switch (type) { + case MEDIA_GRAPH_ENTITY: + list_add_tail(&gobj->list, &mdev->entities); + break; + case MEDIA_GRAPH_PAD: + list_add_tail(&gobj->list, &mdev->pads); + break; + case MEDIA_GRAPH_LINK: + list_add_tail(&gobj->list, &mdev->links); + break; + case MEDIA_GRAPH_INTF_DEVNODE: + list_add_tail(&gobj->list, &mdev->interfaces); + break; + } + + mdev->topology_version++; + + dev_dbg_obj(__func__, gobj); +} + +void media_gobj_destroy(struct media_gobj *gobj) +{ + /* Do nothing if the object is not linked. */ + if (gobj->mdev == NULL) + return; + + dev_dbg_obj(__func__, gobj); + + gobj->mdev->topology_version++; + + /* Remove the object from mdev list */ + list_del(&gobj->list); + + gobj->mdev = NULL; +} + +/* + * TODO: Get rid of this. + */ +#define MEDIA_ENTITY_MAX_PADS 512 + +int media_entity_pads_init(struct media_entity *entity, u16 num_pads, + struct media_pad *pads) +{ + struct media_device *mdev = entity->graph_obj.mdev; + struct media_pad *iter; + unsigned int i = 0; + + if (num_pads >= MEDIA_ENTITY_MAX_PADS) + return -E2BIG; + + entity->num_pads = num_pads; + entity->pads = pads; + + if (mdev) + mutex_lock(&mdev->graph_mutex); + + media_entity_for_each_pad(entity, iter) { + iter->entity = entity; + iter->index = i++; + if (mdev) + media_gobj_create(mdev, MEDIA_GRAPH_PAD, + &iter->graph_obj); + } + + if (mdev) + mutex_unlock(&mdev->graph_mutex); + + return 0; +} +EXPORT_SYMBOL_GPL(media_entity_pads_init); + +/* ----------------------------------------------------------------------------- + * Graph traversal + */ + +/** + * media_entity_has_pad_interdep - Check interdependency between two pads + * + * @entity: The entity + * @pad0: The first pad index + * @pad1: The second pad index + * + * This function checks the interdependency inside the entity between @pad0 + * and @pad1. If two pads are interdependent they are part of the same pipeline + * and enabling one of the pads means that the other pad will become "locked" + * and doesn't allow configuration changes. + * + * This function uses the &media_entity_operations.has_pad_interdep() operation + * to check the dependency inside the entity between @pad0 and @pad1. If the + * has_pad_interdep operation is not implemented, all pads of the entity are + * considered to be interdependent. + * + * One of @pad0 and @pad1 must be a sink pad and the other one a source pad. + * The function returns false if both pads are sinks or sources. + * + * The caller must hold entity->graph_obj.mdev->mutex. + * + * Return: true if the pads are connected internally and false otherwise. + */ +static bool media_entity_has_pad_interdep(struct media_entity *entity, + unsigned int pad0, unsigned int pad1) +{ + if (pad0 >= entity->num_pads || pad1 >= entity->num_pads) + return false; + + if (entity->pads[pad0].flags & entity->pads[pad1].flags & + (MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_SOURCE)) + return false; + + if (!entity->ops || !entity->ops->has_pad_interdep) + return true; + + return entity->ops->has_pad_interdep(entity, pad0, pad1); +} + +static struct media_entity * +media_entity_other(struct media_entity *entity, struct media_link *link) +{ + if (link->source->entity == entity) + return link->sink->entity; + else + return link->source->entity; +} + +/* push an entity to traversal stack */ +static void stack_push(struct media_graph *graph, + struct media_entity *entity) +{ + if (graph->top == MEDIA_ENTITY_ENUM_MAX_DEPTH - 1) { + WARN_ON(1); + return; + } + graph->top++; + graph->stack[graph->top].link = entity->links.next; + graph->stack[graph->top].entity = entity; +} + +static struct media_entity *stack_pop(struct media_graph *graph) +{ + struct media_entity *entity; + + entity = graph->stack[graph->top].entity; + graph->top--; + + return entity; +} + +#define link_top(en) ((en)->stack[(en)->top].link) +#define stack_top(en) ((en)->stack[(en)->top].entity) + +/** + * media_graph_walk_init - Allocate resources for graph walk + * @graph: Media graph structure that will be used to walk the graph + * @mdev: Media device + * + * Reserve resources for graph walk in media device's current + * state. The memory must be released using + * media_graph_walk_cleanup(). + * + * Returns error on failure, zero on success. + */ +__must_check int media_graph_walk_init( + struct media_graph *graph, struct media_device *mdev) +{ + return media_entity_enum_init(&graph->ent_enum, mdev); +} +EXPORT_SYMBOL_GPL(media_graph_walk_init); + +/** + * media_graph_walk_cleanup - Release resources related to graph walking + * @graph: Media graph structure that was used to walk the graph + */ +void media_graph_walk_cleanup(struct media_graph *graph) +{ + media_entity_enum_cleanup(&graph->ent_enum); +} +EXPORT_SYMBOL_GPL(media_graph_walk_cleanup); + +void media_graph_walk_start(struct media_graph *graph, + struct media_entity *entity) +{ + media_entity_enum_zero(&graph->ent_enum); + media_entity_enum_set(&graph->ent_enum, entity); + + graph->top = 0; + graph->stack[graph->top].entity = NULL; + stack_push(graph, entity); + dev_dbg(entity->graph_obj.mdev->dev, + "begin graph walk at '%s'\n", entity->name); +} +EXPORT_SYMBOL_GPL(media_graph_walk_start); + +static void media_graph_walk_iter(struct media_graph *graph) +{ + struct media_entity *entity = stack_top(graph); + struct media_link *link; + struct media_entity *next; + + link = list_entry(link_top(graph), typeof(*link), list); + + /* If the link is not a data link, don't follow it */ + if ((link->flags & MEDIA_LNK_FL_LINK_TYPE) != MEDIA_LNK_FL_DATA_LINK) { + link_top(graph) = link_top(graph)->next; + return; + } + + /* The link is not enabled so we do not follow. */ + if (!(link->flags & MEDIA_LNK_FL_ENABLED)) { + link_top(graph) = link_top(graph)->next; + dev_dbg(entity->graph_obj.mdev->dev, + "walk: skipping disabled link '%s':%u -> '%s':%u\n", + link->source->entity->name, link->source->index, + link->sink->entity->name, link->sink->index); + return; + } + + /* Get the entity at the other end of the link. */ + next = media_entity_other(entity, link); + + /* Has the entity already been visited? */ + if (media_entity_enum_test_and_set(&graph->ent_enum, next)) { + link_top(graph) = link_top(graph)->next; + dev_dbg(entity->graph_obj.mdev->dev, + "walk: skipping entity '%s' (already seen)\n", + next->name); + return; + } + + /* Push the new entity to stack and start over. */ + link_top(graph) = link_top(graph)->next; + stack_push(graph, next); + dev_dbg(entity->graph_obj.mdev->dev, "walk: pushing '%s' on stack\n", + next->name); + lockdep_assert_held(&entity->graph_obj.mdev->graph_mutex); +} + +struct media_entity *media_graph_walk_next(struct media_graph *graph) +{ + struct media_entity *entity; + + if (stack_top(graph) == NULL) + return NULL; + + /* + * Depth first search. Push entity to stack and continue from + * top of the stack until no more entities on the level can be + * found. + */ + while (link_top(graph) != &stack_top(graph)->links) + media_graph_walk_iter(graph); + + entity = stack_pop(graph); + dev_dbg(entity->graph_obj.mdev->dev, + "walk: returning entity '%s'\n", entity->name); + + return entity; +} +EXPORT_SYMBOL_GPL(media_graph_walk_next); + +/* ----------------------------------------------------------------------------- + * Pipeline management + */ + +/* + * The pipeline traversal stack stores pads that are reached during graph + * traversal, with a list of links to be visited to continue the traversal. + * When a new pad is reached, an entry is pushed on the top of the stack and + * points to the incoming pad and the first link of the entity. + * + * To find further pads in the pipeline, the traversal algorithm follows + * internal pad dependencies in the entity, and then links in the graph. It + * does so by iterating over all links of the entity, and following enabled + * links that originate from a pad that is internally connected to the incoming + * pad, as reported by the media_entity_has_pad_interdep() function. + */ + +/** + * struct media_pipeline_walk_entry - Entry in the pipeline traversal stack + * + * @pad: The media pad being visited + * @links: Links left to be visited + */ +struct media_pipeline_walk_entry { + struct media_pad *pad; + struct list_head *links; +}; + +/** + * struct media_pipeline_walk - State used by the media pipeline traversal + * algorithm + * + * @mdev: The media device + * @stack: Depth-first search stack + * @stack.size: Number of allocated entries in @stack.entries + * @stack.top: Index of the top stack entry (-1 if the stack is empty) + * @stack.entries: Stack entries + */ +struct media_pipeline_walk { + struct media_device *mdev; + + struct { + unsigned int size; + int top; + struct media_pipeline_walk_entry *entries; + } stack; +}; + +#define MEDIA_PIPELINE_STACK_GROW_STEP 16 + +static struct media_pipeline_walk_entry * +media_pipeline_walk_top(struct media_pipeline_walk *walk) +{ + return &walk->stack.entries[walk->stack.top]; +} + +static bool media_pipeline_walk_empty(struct media_pipeline_walk *walk) +{ + return walk->stack.top == -1; +} + +/* Increase the stack size by MEDIA_PIPELINE_STACK_GROW_STEP elements. */ +static int media_pipeline_walk_resize(struct media_pipeline_walk *walk) +{ + struct media_pipeline_walk_entry *entries; + unsigned int new_size; + + /* Safety check, to avoid stack overflows in case of bugs. */ + if (walk->stack.size >= 256) + return -E2BIG; + + new_size = walk->stack.size + MEDIA_PIPELINE_STACK_GROW_STEP; + + entries = krealloc(walk->stack.entries, + new_size * sizeof(*walk->stack.entries), + GFP_KERNEL); + if (!entries) + return -ENOMEM; + + walk->stack.entries = entries; + walk->stack.size = new_size; + + return 0; +} + +/* Push a new entry on the stack. */ +static int media_pipeline_walk_push(struct media_pipeline_walk *walk, + struct media_pad *pad) +{ + struct media_pipeline_walk_entry *entry; + int ret; + + if (walk->stack.top + 1 >= walk->stack.size) { + ret = media_pipeline_walk_resize(walk); + if (ret) + return ret; + } + + walk->stack.top++; + entry = media_pipeline_walk_top(walk); + entry->pad = pad; + entry->links = pad->entity->links.next; + + dev_dbg(walk->mdev->dev, + "media pipeline: pushed entry %u: '%s':%u\n", + walk->stack.top, pad->entity->name, pad->index); + + return 0; +} + +/* + * Move the top entry link cursor to the next link. If all links of the entry + * have been visited, pop the entry itself. + */ +static void media_pipeline_walk_pop(struct media_pipeline_walk *walk) +{ + struct media_pipeline_walk_entry *entry; + + if (WARN_ON(walk->stack.top < 0)) + return; + + entry = media_pipeline_walk_top(walk); + + if (entry->links->next == &entry->pad->entity->links) { + dev_dbg(walk->mdev->dev, + "media pipeline: entry %u has no more links, popping\n", + walk->stack.top); + + walk->stack.top--; + return; + } + + entry->links = entry->links->next; + + dev_dbg(walk->mdev->dev, + "media pipeline: moved entry %u to next link\n", + walk->stack.top); +} + +/* Free all memory allocated while walking the pipeline. */ +static void media_pipeline_walk_destroy(struct media_pipeline_walk *walk) +{ + kfree(walk->stack.entries); +} + +/* Add a pad to the pipeline and push it to the stack. */ +static int media_pipeline_add_pad(struct media_pipeline *pipe, + struct media_pipeline_walk *walk, + struct media_pad *pad) +{ + struct media_pipeline_pad *ppad; + + list_for_each_entry(ppad, &pipe->pads, list) { + if (ppad->pad == pad) { + dev_dbg(pad->graph_obj.mdev->dev, + "media pipeline: already contains pad '%s':%u\n", + pad->entity->name, pad->index); + return 0; + } + } + + ppad = kzalloc(sizeof(*ppad), GFP_KERNEL); + if (!ppad) + return -ENOMEM; + + ppad->pipe = pipe; + ppad->pad = pad; + + list_add_tail(&ppad->list, &pipe->pads); + + dev_dbg(pad->graph_obj.mdev->dev, + "media pipeline: added pad '%s':%u\n", + pad->entity->name, pad->index); + + return media_pipeline_walk_push(walk, pad); +} + +/* Explore the next link of the entity at the top of the stack. */ +static int media_pipeline_explore_next_link(struct media_pipeline *pipe, + struct media_pipeline_walk *walk) +{ + struct media_pipeline_walk_entry *entry = media_pipeline_walk_top(walk); + struct media_pad *pad; + struct media_link *link; + struct media_pad *local; + struct media_pad *remote; + int ret; + + pad = entry->pad; + link = list_entry(entry->links, typeof(*link), list); + media_pipeline_walk_pop(walk); + + dev_dbg(walk->mdev->dev, + "media pipeline: exploring link '%s':%u -> '%s':%u\n", + link->source->entity->name, link->source->index, + link->sink->entity->name, link->sink->index); + + /* Skip links that are not enabled. */ + if (!(link->flags & MEDIA_LNK_FL_ENABLED)) { + dev_dbg(walk->mdev->dev, + "media pipeline: skipping link (disabled)\n"); + return 0; + } + + /* Get the local pad and remote pad. */ + if (link->source->entity == pad->entity) { + local = link->source; + remote = link->sink; + } else { + local = link->sink; + remote = link->source; + } + + /* + * Skip links that originate from a different pad than the incoming pad + * that is not connected internally in the entity to the incoming pad. + */ + if (pad != local && + !media_entity_has_pad_interdep(pad->entity, pad->index, local->index)) { + dev_dbg(walk->mdev->dev, + "media pipeline: skipping link (no route)\n"); + return 0; + } + + /* + * Add the local and remote pads of the link to the pipeline and push + * them to the stack, if they're not already present. + */ + ret = media_pipeline_add_pad(pipe, walk, local); + if (ret) + return ret; + + ret = media_pipeline_add_pad(pipe, walk, remote); + if (ret) + return ret; + + return 0; +} + +static void media_pipeline_cleanup(struct media_pipeline *pipe) +{ + while (!list_empty(&pipe->pads)) { + struct media_pipeline_pad *ppad; + + ppad = list_first_entry(&pipe->pads, typeof(*ppad), list); + list_del(&ppad->list); + kfree(ppad); + } +} + +static int media_pipeline_populate(struct media_pipeline *pipe, + struct media_pad *pad) +{ + struct media_pipeline_walk walk = { }; + struct media_pipeline_pad *ppad; + int ret; + + /* + * Populate the media pipeline by walking the media graph, starting + * from @pad. + */ + INIT_LIST_HEAD(&pipe->pads); + pipe->mdev = pad->graph_obj.mdev; + + walk.mdev = pipe->mdev; + walk.stack.top = -1; + ret = media_pipeline_add_pad(pipe, &walk, pad); + if (ret) + goto done; + + /* + * Use a depth-first search algorithm: as long as the stack is not + * empty, explore the next link of the top entry. The + * media_pipeline_explore_next_link() function will either move to the + * next link, pop the entry if fully visited, or add new entries on + * top. + */ + while (!media_pipeline_walk_empty(&walk)) { + ret = media_pipeline_explore_next_link(pipe, &walk); + if (ret) + goto done; + } + + dev_dbg(pad->graph_obj.mdev->dev, + "media pipeline populated, found pads:\n"); + + list_for_each_entry(ppad, &pipe->pads, list) + dev_dbg(pad->graph_obj.mdev->dev, "- '%s':%u\n", + ppad->pad->entity->name, ppad->pad->index); + + WARN_ON(walk.stack.top != -1); + + ret = 0; + +done: + media_pipeline_walk_destroy(&walk); + + if (ret) + media_pipeline_cleanup(pipe); + + return ret; +} + +__must_check int __media_pipeline_start(struct media_pad *pad, + struct media_pipeline *pipe) +{ + struct media_device *mdev = pad->graph_obj.mdev; + struct media_pipeline_pad *err_ppad; + struct media_pipeline_pad *ppad; + int ret; + + lockdep_assert_held(&mdev->graph_mutex); + + /* + * If the pad is already part of a pipeline, that pipeline must be the + * same as the pipe given to media_pipeline_start(). + */ + if (WARN_ON(pad->pipe && pad->pipe != pipe)) + return -EINVAL; + + /* + * If the pipeline has already been started, it is guaranteed to be + * valid, so just increase the start count. + */ + if (pipe->start_count) { + pipe->start_count++; + return 0; + } + + /* + * Populate the pipeline. This populates the media_pipeline pads list + * with media_pipeline_pad instances for each pad found during graph + * walk. + */ + ret = media_pipeline_populate(pipe, pad); + if (ret) + return ret; + + /* + * Now that all the pads in the pipeline have been gathered, perform + * the validation steps. + */ + + list_for_each_entry(ppad, &pipe->pads, list) { + struct media_pad *pad = ppad->pad; + struct media_entity *entity = pad->entity; + bool has_enabled_link = false; + bool has_link = false; + struct media_link *link; + + dev_dbg(mdev->dev, "Validating pad '%s':%u\n", pad->entity->name, + pad->index); + + /* + * 1. Ensure that the pad doesn't already belong to a different + * pipeline. + */ + if (pad->pipe) { + dev_dbg(mdev->dev, "Failed to start pipeline: pad '%s':%u busy\n", + pad->entity->name, pad->index); + ret = -EBUSY; + goto error; + } + + /* + * 2. Validate all active links whose sink is the current pad. + * Validation of the source pads is performed in the context of + * the connected sink pad to avoid duplicating checks. + */ + for_each_media_entity_data_link(entity, link) { + /* Skip links unrelated to the current pad. */ + if (link->sink != pad && link->source != pad) + continue; + + /* Record if the pad has links and enabled links. */ + if (link->flags & MEDIA_LNK_FL_ENABLED) + has_enabled_link = true; + has_link = true; + + /* + * Validate the link if it's enabled and has the + * current pad as its sink. + */ + if (!(link->flags & MEDIA_LNK_FL_ENABLED)) + continue; + + if (link->sink != pad) + continue; + + if (!entity->ops || !entity->ops->link_validate) + continue; + + ret = entity->ops->link_validate(link); + if (ret) { + dev_dbg(mdev->dev, + "Link '%s':%u -> '%s':%u failed validation: %d\n", + link->source->entity->name, + link->source->index, + link->sink->entity->name, + link->sink->index, ret); + goto error; + } + + dev_dbg(mdev->dev, + "Link '%s':%u -> '%s':%u is valid\n", + link->source->entity->name, + link->source->index, + link->sink->entity->name, + link->sink->index); + } + + /* + * 3. If the pad has the MEDIA_PAD_FL_MUST_CONNECT flag set, + * ensure that it has either no link or an enabled link. + */ + if ((pad->flags & MEDIA_PAD_FL_MUST_CONNECT) && has_link && + !has_enabled_link) { + dev_dbg(mdev->dev, + "Pad '%s':%u must be connected by an enabled link\n", + pad->entity->name, pad->index); + ret = -ENOLINK; + goto error; + } + + /* Validation passed, store the pipe pointer in the pad. */ + pad->pipe = pipe; + } + + pipe->start_count++; + + return 0; + +error: + /* + * Link validation on graph failed. We revert what we did and + * return the error. + */ + + list_for_each_entry(err_ppad, &pipe->pads, list) { + if (err_ppad == ppad) + break; + + err_ppad->pad->pipe = NULL; + } + + media_pipeline_cleanup(pipe); + + return ret; +} +EXPORT_SYMBOL_GPL(__media_pipeline_start); + +__must_check int media_pipeline_start(struct media_pad *pad, + struct media_pipeline *pipe) +{ + struct media_device *mdev = pad->graph_obj.mdev; + int ret; + + mutex_lock(&mdev->graph_mutex); + ret = __media_pipeline_start(pad, pipe); + mutex_unlock(&mdev->graph_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(media_pipeline_start); + +void __media_pipeline_stop(struct media_pad *pad) +{ + struct media_pipeline *pipe = pad->pipe; + struct media_pipeline_pad *ppad; + + /* + * If the following check fails, the driver has performed an + * unbalanced call to media_pipeline_stop() + */ + if (WARN_ON(!pipe)) + return; + + if (--pipe->start_count) + return; + + list_for_each_entry(ppad, &pipe->pads, list) + ppad->pad->pipe = NULL; + + media_pipeline_cleanup(pipe); + + if (pipe->allocated) + kfree(pipe); +} +EXPORT_SYMBOL_GPL(__media_pipeline_stop); + +void media_pipeline_stop(struct media_pad *pad) +{ + struct media_device *mdev = pad->graph_obj.mdev; + + mutex_lock(&mdev->graph_mutex); + __media_pipeline_stop(pad); + mutex_unlock(&mdev->graph_mutex); +} +EXPORT_SYMBOL_GPL(media_pipeline_stop); + +__must_check int media_pipeline_alloc_start(struct media_pad *pad) +{ + struct media_device *mdev = pad->graph_obj.mdev; + struct media_pipeline *new_pipe = NULL; + struct media_pipeline *pipe; + int ret; + + mutex_lock(&mdev->graph_mutex); + + /* + * Is the pad already part of a pipeline? If not, we need to allocate + * a pipe. + */ + pipe = media_pad_pipeline(pad); + if (!pipe) { + new_pipe = kzalloc(sizeof(*new_pipe), GFP_KERNEL); + if (!new_pipe) { + ret = -ENOMEM; + goto out; + } + + pipe = new_pipe; + pipe->allocated = true; + } + + ret = __media_pipeline_start(pad, pipe); + if (ret) + kfree(new_pipe); + +out: + mutex_unlock(&mdev->graph_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(media_pipeline_alloc_start); + +struct media_pad * +__media_pipeline_pad_iter_next(struct media_pipeline *pipe, + struct media_pipeline_pad_iter *iter, + struct media_pad *pad) +{ + if (!pad) + iter->cursor = pipe->pads.next; + + if (iter->cursor == &pipe->pads) + return NULL; + + pad = list_entry(iter->cursor, struct media_pipeline_pad, list)->pad; + iter->cursor = iter->cursor->next; + + return pad; +} +EXPORT_SYMBOL_GPL(__media_pipeline_pad_iter_next); + +int media_pipeline_entity_iter_init(struct media_pipeline *pipe, + struct media_pipeline_entity_iter *iter) +{ + return media_entity_enum_init(&iter->ent_enum, pipe->mdev); +} +EXPORT_SYMBOL_GPL(media_pipeline_entity_iter_init); + +void media_pipeline_entity_iter_cleanup(struct media_pipeline_entity_iter *iter) +{ + media_entity_enum_cleanup(&iter->ent_enum); +} +EXPORT_SYMBOL_GPL(media_pipeline_entity_iter_cleanup); + +struct media_entity * +__media_pipeline_entity_iter_next(struct media_pipeline *pipe, + struct media_pipeline_entity_iter *iter, + struct media_entity *entity) +{ + if (!entity) + iter->cursor = pipe->pads.next; + + while (iter->cursor != &pipe->pads) { + struct media_pipeline_pad *ppad; + struct media_entity *entity; + + ppad = list_entry(iter->cursor, struct media_pipeline_pad, list); + entity = ppad->pad->entity; + iter->cursor = iter->cursor->next; + + if (!media_entity_enum_test_and_set(&iter->ent_enum, entity)) + return entity; + } + + return NULL; +} +EXPORT_SYMBOL_GPL(__media_pipeline_entity_iter_next); + +/* ----------------------------------------------------------------------------- + * Links management + */ + +static struct media_link *media_add_link(struct list_head *head) +{ + struct media_link *link; + + link = kzalloc(sizeof(*link), GFP_KERNEL); + if (link == NULL) + return NULL; + + list_add_tail(&link->list, head); + + return link; +} + +static void __media_entity_remove_link(struct media_entity *entity, + struct media_link *link) +{ + struct media_link *rlink, *tmp; + struct media_entity *remote; + + /* Remove the reverse links for a data link. */ + if ((link->flags & MEDIA_LNK_FL_LINK_TYPE) == MEDIA_LNK_FL_DATA_LINK) { + if (link->source->entity == entity) + remote = link->sink->entity; + else + remote = link->source->entity; + + list_for_each_entry_safe(rlink, tmp, &remote->links, list) { + if (rlink != link->reverse) + continue; + + if (link->source->entity == entity) + remote->num_backlinks--; + + /* Remove the remote link */ + list_del(&rlink->list); + media_gobj_destroy(&rlink->graph_obj); + kfree(rlink); + + if (--remote->num_links == 0) + break; + } + } + + list_del(&link->list); + media_gobj_destroy(&link->graph_obj); + kfree(link); +} + +int media_get_pad_index(struct media_entity *entity, u32 pad_type, + enum media_pad_signal_type sig_type) +{ + unsigned int i; + + if (!entity) + return -EINVAL; + + for (i = 0; i < entity->num_pads; i++) { + if ((entity->pads[i].flags & + (MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_SOURCE)) != pad_type) + continue; + + if (entity->pads[i].sig_type == sig_type) + return i; + } + return -EINVAL; +} +EXPORT_SYMBOL_GPL(media_get_pad_index); + +int +media_create_pad_link(struct media_entity *source, u16 source_pad, + struct media_entity *sink, u16 sink_pad, u32 flags) +{ + struct media_link *link; + struct media_link *backlink; + + if (WARN_ON(!source || !sink) || + WARN_ON(source_pad >= source->num_pads) || + WARN_ON(sink_pad >= sink->num_pads)) + return -EINVAL; + if (WARN_ON(!(source->pads[source_pad].flags & MEDIA_PAD_FL_SOURCE))) + return -EINVAL; + if (WARN_ON(!(sink->pads[sink_pad].flags & MEDIA_PAD_FL_SINK))) + return -EINVAL; + + link = media_add_link(&source->links); + if (link == NULL) + return -ENOMEM; + + link->source = &source->pads[source_pad]; + link->sink = &sink->pads[sink_pad]; + link->flags = flags & ~MEDIA_LNK_FL_INTERFACE_LINK; + + /* Initialize graph object embedded at the new link */ + media_gobj_create(source->graph_obj.mdev, MEDIA_GRAPH_LINK, + &link->graph_obj); + + /* Create the backlink. Backlinks are used to help graph traversal and + * are not reported to userspace. + */ + backlink = media_add_link(&sink->links); + if (backlink == NULL) { + __media_entity_remove_link(source, link); + return -ENOMEM; + } + + backlink->source = &source->pads[source_pad]; + backlink->sink = &sink->pads[sink_pad]; + backlink->flags = flags; + backlink->is_backlink = true; + + /* Initialize graph object embedded at the new link */ + media_gobj_create(sink->graph_obj.mdev, MEDIA_GRAPH_LINK, + &backlink->graph_obj); + + link->reverse = backlink; + backlink->reverse = link; + + sink->num_backlinks++; + sink->num_links++; + source->num_links++; + + return 0; +} +EXPORT_SYMBOL_GPL(media_create_pad_link); + +int media_create_pad_links(const struct media_device *mdev, + const u32 source_function, + struct media_entity *source, + const u16 source_pad, + const u32 sink_function, + struct media_entity *sink, + const u16 sink_pad, + u32 flags, + const bool allow_both_undefined) +{ + struct media_entity *entity; + unsigned function; + int ret; + + /* Trivial case: 1:1 relation */ + if (source && sink) + return media_create_pad_link(source, source_pad, + sink, sink_pad, flags); + + /* Worse case scenario: n:n relation */ + if (!source && !sink) { + if (!allow_both_undefined) + return 0; + media_device_for_each_entity(source, mdev) { + if (source->function != source_function) + continue; + media_device_for_each_entity(sink, mdev) { + if (sink->function != sink_function) + continue; + ret = media_create_pad_link(source, source_pad, + sink, sink_pad, + flags); + if (ret) + return ret; + flags &= ~(MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + } + } + return 0; + } + + /* Handle 1:n and n:1 cases */ + if (source) + function = sink_function; + else + function = source_function; + + media_device_for_each_entity(entity, mdev) { + if (entity->function != function) + continue; + + if (source) + ret = media_create_pad_link(source, source_pad, + entity, sink_pad, flags); + else + ret = media_create_pad_link(entity, source_pad, + sink, sink_pad, flags); + if (ret) + return ret; + flags &= ~(MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE); + } + return 0; +} +EXPORT_SYMBOL_GPL(media_create_pad_links); + +void __media_entity_remove_links(struct media_entity *entity) +{ + struct media_link *link, *tmp; + + list_for_each_entry_safe(link, tmp, &entity->links, list) + __media_entity_remove_link(entity, link); + + entity->num_links = 0; + entity->num_backlinks = 0; +} +EXPORT_SYMBOL_GPL(__media_entity_remove_links); + +void media_entity_remove_links(struct media_entity *entity) +{ + struct media_device *mdev = entity->graph_obj.mdev; + + /* Do nothing if the entity is not registered. */ + if (mdev == NULL) + return; + + mutex_lock(&mdev->graph_mutex); + __media_entity_remove_links(entity); + mutex_unlock(&mdev->graph_mutex); +} +EXPORT_SYMBOL_GPL(media_entity_remove_links); + +static int __media_entity_setup_link_notify(struct media_link *link, u32 flags) +{ + int ret; + + /* Notify both entities. */ + ret = media_entity_call(link->source->entity, link_setup, + link->source, link->sink, flags); + if (ret < 0 && ret != -ENOIOCTLCMD) + return ret; + + ret = media_entity_call(link->sink->entity, link_setup, + link->sink, link->source, flags); + if (ret < 0 && ret != -ENOIOCTLCMD) { + media_entity_call(link->source->entity, link_setup, + link->source, link->sink, link->flags); + return ret; + } + + link->flags = flags; + link->reverse->flags = link->flags; + + return 0; +} + +int __media_entity_setup_link(struct media_link *link, u32 flags) +{ + const u32 mask = MEDIA_LNK_FL_ENABLED; + struct media_device *mdev; + struct media_pad *source, *sink; + int ret = -EBUSY; + + if (link == NULL) + return -EINVAL; + + /* The non-modifiable link flags must not be modified. */ + if ((link->flags & ~mask) != (flags & ~mask)) + return -EINVAL; + + if (link->flags & MEDIA_LNK_FL_IMMUTABLE) + return link->flags == flags ? 0 : -EINVAL; + + if (link->flags == flags) + return 0; + + source = link->source; + sink = link->sink; + + if (!(link->flags & MEDIA_LNK_FL_DYNAMIC) && + (media_pad_is_streaming(source) || media_pad_is_streaming(sink))) + return -EBUSY; + + mdev = source->graph_obj.mdev; + + if (mdev->ops && mdev->ops->link_notify) { + ret = mdev->ops->link_notify(link, flags, + MEDIA_DEV_NOTIFY_PRE_LINK_CH); + if (ret < 0) + return ret; + } + + ret = __media_entity_setup_link_notify(link, flags); + + if (mdev->ops && mdev->ops->link_notify) + mdev->ops->link_notify(link, flags, + MEDIA_DEV_NOTIFY_POST_LINK_CH); + + return ret; +} +EXPORT_SYMBOL_GPL(__media_entity_setup_link); + +int media_entity_setup_link(struct media_link *link, u32 flags) +{ + int ret; + + mutex_lock(&link->graph_obj.mdev->graph_mutex); + ret = __media_entity_setup_link(link, flags); + mutex_unlock(&link->graph_obj.mdev->graph_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(media_entity_setup_link); + +struct media_link * +media_entity_find_link(struct media_pad *source, struct media_pad *sink) +{ + struct media_link *link; + + for_each_media_entity_data_link(source->entity, link) { + if (link->source->entity == source->entity && + link->source->index == source->index && + link->sink->entity == sink->entity && + link->sink->index == sink->index) + return link; + } + + return NULL; +} +EXPORT_SYMBOL_GPL(media_entity_find_link); + +struct media_pad *media_pad_remote_pad_first(const struct media_pad *pad) +{ + struct media_link *link; + + for_each_media_entity_data_link(pad->entity, link) { + if (!(link->flags & MEDIA_LNK_FL_ENABLED)) + continue; + + if (link->source == pad) + return link->sink; + + if (link->sink == pad) + return link->source; + } + + return NULL; + +} +EXPORT_SYMBOL_GPL(media_pad_remote_pad_first); + +struct media_pad * +media_entity_remote_pad_unique(const struct media_entity *entity, + unsigned int type) +{ + struct media_pad *pad = NULL; + struct media_link *link; + + list_for_each_entry(link, &entity->links, list) { + struct media_pad *local_pad; + struct media_pad *remote_pad; + + if (((link->flags & MEDIA_LNK_FL_LINK_TYPE) != + MEDIA_LNK_FL_DATA_LINK) || + !(link->flags & MEDIA_LNK_FL_ENABLED)) + continue; + + if (type == MEDIA_PAD_FL_SOURCE) { + local_pad = link->sink; + remote_pad = link->source; + } else { + local_pad = link->source; + remote_pad = link->sink; + } + + if (local_pad->entity == entity) { + if (pad) + return ERR_PTR(-ENOTUNIQ); + + pad = remote_pad; + } + } + + if (!pad) + return ERR_PTR(-ENOLINK); + + return pad; +} +EXPORT_SYMBOL_GPL(media_entity_remote_pad_unique); + +struct media_pad *media_pad_remote_pad_unique(const struct media_pad *pad) +{ + struct media_pad *found_pad = NULL; + struct media_link *link; + + list_for_each_entry(link, &pad->entity->links, list) { + struct media_pad *remote_pad; + + if (!(link->flags & MEDIA_LNK_FL_ENABLED)) + continue; + + if (link->sink == pad) + remote_pad = link->source; + else if (link->source == pad) + remote_pad = link->sink; + else + continue; + + if (found_pad) + return ERR_PTR(-ENOTUNIQ); + + found_pad = remote_pad; + } + + if (!found_pad) + return ERR_PTR(-ENOLINK); + + return found_pad; +} +EXPORT_SYMBOL_GPL(media_pad_remote_pad_unique); + +int media_entity_get_fwnode_pad(struct media_entity *entity, + const struct fwnode_handle *fwnode, + unsigned long direction_flags) +{ + struct fwnode_endpoint endpoint; + unsigned int i; + int ret; + + if (!entity->ops || !entity->ops->get_fwnode_pad) { + for (i = 0; i < entity->num_pads; i++) { + if (entity->pads[i].flags & direction_flags) + return i; + } + + return -ENXIO; + } + + ret = fwnode_graph_parse_endpoint(fwnode, &endpoint); + if (ret) + return ret; + + ret = entity->ops->get_fwnode_pad(entity, &endpoint); + if (ret < 0) + return ret; + + if (ret >= entity->num_pads) + return -ENXIO; + + if (!(entity->pads[ret].flags & direction_flags)) + return -ENXIO; + + return ret; +} +EXPORT_SYMBOL_GPL(media_entity_get_fwnode_pad); + +struct media_pipeline *media_entity_pipeline(struct media_entity *entity) +{ + struct media_pad *pad; + + media_entity_for_each_pad(entity, pad) { + if (pad->pipe) + return pad->pipe; + } + + return NULL; +} +EXPORT_SYMBOL_GPL(media_entity_pipeline); + +struct media_pipeline *media_pad_pipeline(struct media_pad *pad) +{ + return pad->pipe; +} +EXPORT_SYMBOL_GPL(media_pad_pipeline); + +static void media_interface_init(struct media_device *mdev, + struct media_interface *intf, + u32 gobj_type, + u32 intf_type, u32 flags) +{ + intf->type = intf_type; + intf->flags = flags; + INIT_LIST_HEAD(&intf->links); + + media_gobj_create(mdev, gobj_type, &intf->graph_obj); +} + +/* Functions related to the media interface via device nodes */ + +struct media_intf_devnode *media_devnode_create(struct media_device *mdev, + u32 type, u32 flags, + u32 major, u32 minor) +{ + struct media_intf_devnode *devnode; + + devnode = kzalloc(sizeof(*devnode), GFP_KERNEL); + if (!devnode) + return NULL; + + devnode->major = major; + devnode->minor = minor; + + media_interface_init(mdev, &devnode->intf, MEDIA_GRAPH_INTF_DEVNODE, + type, flags); + + return devnode; +} +EXPORT_SYMBOL_GPL(media_devnode_create); + +void media_devnode_remove(struct media_intf_devnode *devnode) +{ + media_remove_intf_links(&devnode->intf); + media_gobj_destroy(&devnode->intf.graph_obj); + kfree(devnode); +} +EXPORT_SYMBOL_GPL(media_devnode_remove); + +struct media_link *media_create_intf_link(struct media_entity *entity, + struct media_interface *intf, + u32 flags) +{ + struct media_link *link; + + link = media_add_link(&intf->links); + if (link == NULL) + return NULL; + + link->intf = intf; + link->entity = entity; + link->flags = flags | MEDIA_LNK_FL_INTERFACE_LINK; + + /* Initialize graph object embedded at the new link */ + media_gobj_create(intf->graph_obj.mdev, MEDIA_GRAPH_LINK, + &link->graph_obj); + + return link; +} +EXPORT_SYMBOL_GPL(media_create_intf_link); + +void __media_remove_intf_link(struct media_link *link) +{ + list_del(&link->list); + media_gobj_destroy(&link->graph_obj); + kfree(link); +} +EXPORT_SYMBOL_GPL(__media_remove_intf_link); + +void media_remove_intf_link(struct media_link *link) +{ + struct media_device *mdev = link->graph_obj.mdev; + + /* Do nothing if the intf is not registered. */ + if (mdev == NULL) + return; + + mutex_lock(&mdev->graph_mutex); + __media_remove_intf_link(link); + mutex_unlock(&mdev->graph_mutex); +} +EXPORT_SYMBOL_GPL(media_remove_intf_link); + +void __media_remove_intf_links(struct media_interface *intf) +{ + struct media_link *link, *tmp; + + list_for_each_entry_safe(link, tmp, &intf->links, list) + __media_remove_intf_link(link); + +} +EXPORT_SYMBOL_GPL(__media_remove_intf_links); + +void media_remove_intf_links(struct media_interface *intf) +{ + struct media_device *mdev = intf->graph_obj.mdev; + + /* Do nothing if the intf is not registered. */ + if (mdev == NULL) + return; + + mutex_lock(&mdev->graph_mutex); + __media_remove_intf_links(intf); + mutex_unlock(&mdev->graph_mutex); +} +EXPORT_SYMBOL_GPL(media_remove_intf_links); + +struct media_link *media_create_ancillary_link(struct media_entity *primary, + struct media_entity *ancillary) +{ + struct media_link *link; + + link = media_add_link(&primary->links); + if (!link) + return ERR_PTR(-ENOMEM); + + link->gobj0 = &primary->graph_obj; + link->gobj1 = &ancillary->graph_obj; + link->flags = MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_ANCILLARY_LINK; + + /* Initialize graph object embedded in the new link */ + media_gobj_create(primary->graph_obj.mdev, MEDIA_GRAPH_LINK, + &link->graph_obj); + + return link; +} +EXPORT_SYMBOL_GPL(media_create_ancillary_link); + +struct media_link *__media_entity_next_link(struct media_entity *entity, + struct media_link *link, + unsigned long link_type) +{ + link = link ? list_next_entry(link, list) + : list_first_entry(&entity->links, typeof(*link), list); + + list_for_each_entry_from(link, &entity->links, list) + if ((link->flags & MEDIA_LNK_FL_LINK_TYPE) == link_type) + return link; + + return NULL; +} +EXPORT_SYMBOL_GPL(__media_entity_next_link); |