0 引言
初学者代码学习,作为笔记留存,感谢指正。
内核版本:4.19
1 文件
media_controller子系统,主要有以下几个文件。
media_entity.c
media_device.c
media_devnode.c
值得注意的是:在Linux内核5.15之后,这几个文件修改命名为mc-xxx了。
它主要服务于V4L2,帮助其建立拓扑图。避免因为修改链接,或者取消某个模块,而需要重新编译代码。并且,能够帮助检查数据流的完整性。
2 全局视野
老规矩,先看数据结构。

重要的数据结构如上图。
在v4l2的前几个章节中,每个subdev都会包含entity结构体,它会描述每个v4l2-subdev的连接方式。
每个subdev的功能。并且提供一个操作函数, const struct media_entity_operations *ops,这个操作函数,能让顶层的v4l2结构,查询链接是否完备。
一般是调用 link_validate回调函数,进行查询。所以大多的subdev在注册entity的时候,会实现这个回调,方便建立链接时进行检查。
3 mc-entity
3.1 数据结构
c
struct media_pad;
这个结构体,代表本entity的能力。源,或者宿。输入还是输出。拓扑图中的链接点,就是它。
struct media_entity *entity; 指向它自己所属的entity。
u16 index; 该pad是第几个pad,这里也代表每个entity,通常具有多个pad。
struct media_link:
代表链接,v4l2中,多个entity需要链接到一起,才是一整个数据流。它负责链接的方式。它里面有source和sink,分别代表源,宿。两个union的存在,使得该结构体既可以顺向查找,也可以反向查找。
struct media_gobj
meida子系统中的基础结构,基本所有的结构体,都会包含这个结构体。方便每个结构体,能挂到对应的media_device中
这几个结构体,都嵌入到media_entity结构体中,他们给media_entity提供了对应的能力,它嵌入到v4l2-subdev中,使得它也具备这个能力。注册v4l2-subdev的时候,初始化pad,初始化这个subdev的能力。
3.2 关键函数
media_entity_pads_init
media_create_pad_link
__media_pipeline_start
3.2.1 media_entity_pads_init
c
int media_entity_pads_init(struct media_entity *entity, u16 num_pads,
struct media_pad *pads)
{
struct media_device *mdev = entity->graph_obj.mdev;
unsigned int i;
if (num_pads >= MEDIA_ENTITY_MAX_PADS)
return -E2BIG;
entity->num_pads = num_pads;
entity->pads = pads;
if (mdev)
mutex_lock(&mdev->graph_mutex);
for (i = 0; i < num_pads; i++) {
pads[i].entity = entity;
pads[i].index = i;
if (mdev)
media_gobj_create(mdev, MEDIA_GRAPH_PAD,
&entity->pads[i].graph_obj);
}
if (mdev)
mutex_unlock(&mdev->graph_mutex);
return 0;
}
根据传入的pad,初始化传入的pad,也即赋值。这里有一个函数media_gobj_create,他会多次被调用,它的原型如下:
c
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);
}
3.2.2 media_create_pad_link
3.2.1的函数,初始化了pads,那么下一步,就是建立链接了。本函数应运而生。
c
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;
BUG_ON(source == NULL || sink == NULL);
BUG_ON(source_pad >= source->num_pads);
BUG_ON(sink_pad >= sink->num_pads);
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;
}
这个函数,其实是两个同样的操作。在 3.1的struct media_link:中,有两个一模一样的联合体。那时我们提到,它方便进行正向链接和反向链接。本函数,实际就是实现这个功能。
首先media_add_link 申请空间,然后将传入的pad,挂载到link的source里。然后调用我们3.2.1 中扩展的 media_gobj_create函数,添加到media_device的全局链表中。
第二部分同理,不过,它是挂到sink里,然后同样调用 media_gobj_create函数,添加到media_device的全局链表中。指的一提的是,backlink,会对is_backlink赋值。在后面的遍历查询的时候,可以依据此成员,标识出它是一个反向链接。
总结
本函数,把传入的两个entity,建立正向链接和反向链接。同时挂到media_device的全局链表中。
4 mc-device
4.1 数据结构
c
struct media_device {
/* dev->driver_data points to this struct. */
struct device *dev;
struct media_devnode *devnode;
char model[32];
char driver_name[32];
char serial[40];
char bus_info[32];
u32 hw_revision;
u64 topology_version;
u32 id;
struct ida entity_internal_idx;
int entity_internal_idx_max;
struct list_head entities;
struct list_head interfaces;
struct list_head pads;
struct list_head links;
/* notify callback list invoked when a new entity is registered */
struct list_head entity_notify;
/* Serializes graph operations. */
struct mutex graph_mutex;
struct media_graph pm_count_walk;
void *source_priv;
int (*enable_source)(struct media_entity *entity,
struct media_pipeline *pipe);
void (*disable_source)(struct media_entity *entity);
const struct media_device_ops *ops;
};
这里的四个结构体:
struct list_head entities;
struct list_head interfaces;
struct list_head pads;
struct list_head links;
正好对应了,我们在章节三 中所描述的,建立链接,pad的时候,挂一份到media_device的说法。
c
struct media_entity_notify {
struct list_head list;
void *notify_data;
void (*notify)(struct media_entity *entity, void *notify_data);
};
本结构体,也即对应上面的struct list_head entity_notify; 根据其他子系统的经验,本结构体,是提供一个异步通知的功能,注册entity后,会调用这里的notify函数,尝试完成注册。这个理论,在之前的章节xx中,也有提到。(i2c设备注册通常较为靠后)
4.2 核心函数
media_device_init
media_device_register_entity
4.2.1 media_device_init
c
void media_device_init(struct media_device *mdev)
{
INIT_LIST_HEAD(&mdev->entities);
INIT_LIST_HEAD(&mdev->interfaces);
INIT_LIST_HEAD(&mdev->pads);
INIT_LIST_HEAD(&mdev->links);
INIT_LIST_HEAD(&mdev->entity_notify);
mutex_init(&mdev->graph_mutex);
ida_init(&mdev->entity_internal_idx);
dev_dbg(mdev->dev, "Media device initialized\n");
}
没什么好说的,初始化链表。
4.2.2 media_device_register_entity
c
int __must_check media_device_register_entity(struct media_device *mdev,
struct media_entity *entity)
{
struct media_entity_notify *notify, *next;
unsigned int i;
int ret;
if (entity->function == MEDIA_ENT_F_V4L2_SUBDEV_UNKNOWN ||
entity->function == MEDIA_ENT_F_UNKNOWN)
dev_warn(mdev->dev,
"Entity type for entity %s was not initialized!\n",
entity->name);
/* Warn if we apparently re-register an entity */
WARN_ON(entity->graph_obj.mdev != NULL);
entity->graph_obj.mdev = mdev;
INIT_LIST_HEAD(&entity->links);
entity->num_links = 0;
entity->num_backlinks = 0;
ret = ida_alloc_min(&mdev->entity_internal_idx, 1, GFP_KERNEL);
if (ret < 0)
return ret;
entity->internal_idx = ret;
mutex_lock(&mdev->graph_mutex);
mdev->entity_internal_idx_max =
max(mdev->entity_internal_idx_max, entity->internal_idx);
/* Initialize media_gobj embedded at the entity */
media_gobj_create(mdev, MEDIA_GRAPH_ENTITY, &entity->graph_obj);
/* Initialize objects at the pads */
for (i = 0; i < entity->num_pads; i++)
media_gobj_create(mdev, MEDIA_GRAPH_PAD,
&entity->pads[i].graph_obj);
/* invoke entity_notify callbacks */
list_for_each_entry_safe(notify, next, &mdev->entity_notify, list)
notify->notify(entity, notify->notify_data);
if (mdev->entity_internal_idx_max
>= mdev->pm_count_walk.ent_enum.idx_max) {
struct media_graph new = { .top = 0 };
/*
* Initialise the new graph walk before cleaning up
* the old one in order not to spoil the graph walk
* object of the media device if graph walk init fails.
*/
ret = media_graph_walk_init(&new, mdev);
if (ret) {
__media_device_unregister_entity(entity);
mutex_unlock(&mdev->graph_mutex);
return ret;
}
media_graph_walk_cleanup(&mdev->pm_count_walk);
mdev->pm_count_walk = new;
}
mutex_unlock(&mdev->graph_mutex);
return 0;
}
- 将entity的medv指针,赋值为本
media_device,和我们上面讲述media_entity的成员吻合。 - 把注册的这个entity,挂到
meida_device的链表中。 - 将entity的pad,挂一份到
meida_device的链表中。 - 遍历所有的notify函数,调用对应的notify函数。和我们上述分析的调用notify函数的时机温和。
如果你看过某个尝试对应的bsp,肯定会发现,这个函数很少被调用,或者压根没有。那么它到底在哪里被调用呢。我们直接给出来:
video_register_media_controller
v4l2_device_register_subdev
v4l2_device_register_subdev 这个函数,几乎是必然调用的了,在每个subdev初始化的时候,都会调用它。它我们在之前【Linux】V4L2框架分析《二》v4l2-device.c中介绍过了。( emm,貌似有问题,待我修改下。)
5 mc-devnode
5.1 数据结构
c
struct media_file_operations {
struct module *owner;
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
__poll_t (*poll) (struct file *, struct poll_table_struct *);
long (*ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*open) (struct file *);
int (*release) (struct file *);
};
struct media_devnode {
struct media_device *media_dev;
/* device ops */
const struct media_file_operations *fops;
/* sysfs */
struct device dev; /* media device */
struct cdev cdev; /* character device */
struct device *parent; /* device parent */
/* device info */
int minor;
unsigned long flags; /* Use bitops to access flags */
/* callbacks */
void (*release)(struct media_devnode *devnode);
};
嗯,这个文件很简单,只是创建bus,提供文件操作。const struct media_file_operations *fops;它的作用,和我们之前提到的 video_device是一致的,也是分发,最终调用到这里。
它创建一个bus,让后续的media_device_create函数,能够索引的到。
文件不复杂,这里就不着重分析了。
6 总结
我们在前五章节,基本已经介绍完了数据结构,和重点的函数。一路走下来,entity的注册,link,pad的注册都已经介绍了。那么问题随之而来。注册好后,该怎么使用呢,它的作用是啥呢,毕竟我们现在全都是创建,,并没提供系统的函数给外面使用。
其实它藏在
章节3 __media_pipeline_start中。
c
__must_check int __media_pipeline_start(struct media_entity *entity,
struct media_pipeline *pipe)
{
struct media_device *mdev = entity->graph_obj.mdev;
struct media_graph *graph = &pipe->graph;
struct media_entity *entity_err = entity;
struct media_link *link;
int ret;
if (!pipe->streaming_count++) {
ret = media_graph_walk_init(&pipe->graph, mdev);
if (ret)
goto error_graph_walk_start;
}
media_graph_walk_start(&pipe->graph, entity);
while ((entity = media_graph_walk_next(graph))) {
DECLARE_BITMAP(active, MEDIA_ENTITY_MAX_PADS);
DECLARE_BITMAP(has_no_links, MEDIA_ENTITY_MAX_PADS);
entity->stream_count++;
if (WARN_ON(entity->pipe && entity->pipe != pipe)) {
ret = -EBUSY;
goto error;
}
entity->pipe = pipe;
/* Already streaming --- no need to check. */
if (entity->stream_count > 1)
continue;
if (!entity->ops || !entity->ops->link_validate)
continue;
bitmap_zero(active, entity->num_pads);
bitmap_fill(has_no_links, entity->num_pads);
list_for_each_entry(link, &entity->links, list) {
struct media_pad *pad = link->sink->entity == entity
? link->sink : link->source;
/* Mark that a pad is connected by a link. */
bitmap_clear(has_no_links, pad->index, 1);
/*
* Pads that either do not need to connect or
* are connected through an enabled link are
* fine.
*/
if (!(pad->flags & MEDIA_PAD_FL_MUST_CONNECT) ||
link->flags & MEDIA_LNK_FL_ENABLED)
bitmap_set(active, pad->index, 1);
/*
* Link validation will only take place for
* sink ends of the link that are enabled.
*/
if (link->sink != pad ||
!(link->flags & MEDIA_LNK_FL_ENABLED))
continue;
ret = entity->ops->link_validate(link);
if (ret < 0 && ret != -ENOIOCTLCMD) {
dev_dbg(entity->graph_obj.mdev->dev,
"link validation failed for '%s':%u -> '%s':%u, error %d\n",
link->source->entity->name,
link->source->index,
entity->name, link->sink->index, ret);
goto error;
}
}
/* Either no links or validated links are fine. */
bitmap_or(active, active, has_no_links, entity->num_pads);
if (!bitmap_full(active, entity->num_pads)) {
ret = -ENOLINK;
dev_dbg(entity->graph_obj.mdev->dev,
"'%s':%u must be connected by an enabled link\n",
entity->name,
(unsigned)find_first_zero_bit(
active, entity->num_pads));
goto error;
}
}
return 0;
error:
/*
* Link validation on graph failed. We revert what we did and
* return the error.
*/
media_graph_walk_start(graph, entity_err);
while ((entity_err = media_graph_walk_next(graph))) {
/* Sanity check for negative stream_count */
if (!WARN_ON_ONCE(entity_err->stream_count <= 0)) {
entity_err->stream_count--;
if (entity_err->stream_count == 0)
entity_err->pipe = NULL;
}
/*
* We haven't increased stream_count further than this
* so we quit here.
*/
if (entity_err == entity)
break;
}
error_graph_walk_start:
if (!--pipe->streaming_count)
media_graph_walk_cleanup(graph);
return ret;
}
EXPORT_SYMBOL_GPL(__media_pipeline_start);
这个函数,会遍历整个链路,调用 ret = entity->ops->link_validate(link);检测,查看链路的有效性。
一般在启动流的时候,会调用__media_pipeline_start进行链路的检查。