【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进行链路的检查。

相关推荐
小成202303202652 小时前
Linux高级03
linux·开发语言
南境十里·墨染春水2 小时前
linux学习进展 进程
linux·运维·学习
cyber_两只龙宝2 小时前
【Oracle】Oracle之DQL中SELECT的基础使用
linux·运维·服务器·数据库·云原生·oracle
云栖梦泽2 小时前
Linux内核与驱动:10.平台总线platform
linux
Deitymoon2 小时前
linux——TCP多进程并发服务器
linux·服务器·tcp/ip
网络安全许木2 小时前
自学渗透测试第15天(基础复习与漏洞原理入门)
linux·网络安全·渗透测试·kali linux
Hello World . .3 小时前
linux驱动编程2 : uboot、Linux内核、rootfs来源及制作流程
linux·运维·服务器
啦啦啦_99993 小时前
1. Linux常用命令
linux·运维·服务器
大白菜和MySQL3 小时前
openEuler-20.03-LTS系统 nextcloud网盘搭建
linux