【Linux】V4L2框架分析《四》media_controller

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.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;
}
  1. 将entity的medv指针,赋值为本media_device,和我们上面讲述media_entity的成员吻合。
  2. 把注册的这个entity,挂到meida_device的链表中。
  3. 将entity的pad,挂一份到meida_device的链表中。
  4. 遍历所有的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进行链路的检查。

相关推荐
AlfredZhao14 小时前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户97183563346620 小时前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪21 小时前
linux 拷贝文件或目录到指定的位置
linux
摇滚侠2 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
bush42 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5202 天前
Linux 11 动态监控指令top
linux
不会C语言的男孩2 天前
Linux 系统编程 · 第 8 章:进程基础
linux·c语言
古城小栈2 天前
Unix 与 Linux 异同小叙
linux·服务器·unix
凡人叶枫2 天前
Effective C++ 条款42:了解 typename 的双重意义
java·linux·服务器·c++
2601_961875242 天前
决战申论100题2026|最新|范文
linux·容器·centos·debian·ssh·fabric·vagrant