v4l2子系统学习(五)subdev和media子系统

embedded/2025/2/26 17:38:24/

文章目录

1、声明

本文是在学习韦东山《驱动大全》V4L2子系统时,为梳理知识点和自己回看而记录,全部内容高度复制粘贴。

韦老师的《驱动大全》:商品详情

其对应的讲义资料:https://e.coding.net/weidongshan/linux/doc_and_source_for_drivers.git

subdevmedia_8">2、subdevmedia子系统

subdev_9">2.1、subdev的引入

v4l2最开始的学习中,我们编写了一个虚拟的摄像头驱动程序,其整个程序的核心就是一个video_device结构体。

后面学习uvc摄像头驱动程序框架,因为usb摄像头内部有各类Unit或Terminal,但是这些Unit或Terminal的独立性不强,并且它们遵守UVC规范,没必要单独为它们编写驱动程序。在UVC驱动程序里,每一个Unit或Terminal都有一个"struct uvc_entity"结构体,每一个"struct uvc_entity"里面也都有一个"struct v4l2_subdev":

但是,这些subdev没有什么作用,它们的ops函数都是空的:

但本节我们将讨论mipi摄像头驱动,之所以在mipi摄像头驱动章节才引入subdevmedia子系统,正是因为mipi摄像头涉及的内部结构或内部流程很复杂,如下图是v853 mipi摄像头的内部结构:

内部涉及的模块非常多,这些模块相对独立,有必要单独给这些模块编写驱动程序。

这些模块被抽象为subdev,原因有2:

  • 屏蔽硬件操作的细节,有些模块是I2C接口的,有些模块是SPI接口的,它们都被封装为subdev
  • 方便内核使用、APP使用:可以在内核里调用subdev的函数,也可以在用户空间调用subdev的函数。很多厂家不愿意公开ISP的源码,只能在驱动层面提供最简单的subdev,然后通过APP调用subdev的基本读写函数进行复杂的设置。

subdev结构体如下:

里面有各类ops结构体,比如core、tuner、audio、video等等。编写subdev驱动程序时,核心就是实现各类ops结构体的函数。

以上图中第1个模块gc2053为例,它的代码为<font style="color:rgb(51, 51, 51);">"drivers\media\platform\sunxi-vin\modules\sensor\gc2053_mipi.c"</font>,核心结构体如下:

2.2、media子系统的引入

即使我们给每一个子模块都编写了驱动程序,都构造了subdev,但是它们之间的相互关系怎么表示?如下图:

  • 每一个模块都构造了subdev
  • 但是,框图里黑色箭头所表示的关系如何描述?
    • 比如,从"格式解析"模块出来的数据,到底是输出到"ISP0"还是"ISP1"?用户如何选择?
    • 当我们指定"格式解析"模块输出的数据格式后,是否也要同步指定ISP0或ISP1模块的输入数据格式?

这就需要引入media子系统,简化如下:

怎么操作上图的各个对象?使用subdevsubdev里含有各个对象的操作函数。

怎么表示上图的各个对象之间的关系?使用media子系统。在media子系统里:

  • 每个对象都是一个media_entity。
  • media_entity有media_pad,可以认为是端点(不能简单认为是硬件引脚),有source pad(输出数据),sink pad(输入数据)。
  • media_entity之间的连接被称为media_link。
  • media_link仅仅表示两个media_entity之间的连接,要构成一个完整的数据通道,需要多个系列的连接,这被称为pipeline。
  • media子系统的作用就在于管理"拓扑关系",就是各个对象的连接关系。

把V853的拓扑图精简一下:

怎么表示上图的各个对象?使用media子系统media子系统的作用在于管理"拓扑关系",就是各个对象的连接关系。

怎么操作上图的各个对象?使用subdevsubdev里含有各个对象的操作函数。

命令示例:

media-ctl -v -l '"gc2053_mipi":0->"sunxi_mipi.0":0[1]'
v4l2-ctl -D -d /dev/v4l-subdev0

subdev_82">2.3、subdev概览和数据结构

2.3.1、实例

<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">drivers\media\platform\sunxi-vin\modules\sensor\gc2053_mipi.c</font>为例:

sensor_probe/* 分配sensor_info结构体,里面含有v4l2_subdev */struct v4l2_subdev *sd;	struct sensor_info *info;info = kzalloc(sizeof(struct sensor_info), GFP_KERNEL);sd = &info->sd;cci_dev_probe_helper(sd, client, &sensor_ops, &cci_drv[sensor_dev_id++]);v4l2_i2c_subdev_init(sd, client, sensor_ops);v4l2_subdev_init(sd, ops);

2.3.2、数据结构

一个模块对应一个subdev,怎么操作这个模块?subdev里面有ops:

怎么管理多个subdev?必定是放入一个链表里:

2.4、media子系统概览和数据结构

2.4.1、拓扑结构

对于V853精简的拓扑图:

media子系统里,怎么表示上面的各个entity的关系?以最左边2个entity为例,结构体如下:

每个v4l2_subdev结构体都会包含一个media_entity结构体,media_entity结构体会描述这个entity有多少个pad,多少个link,同时包含有media_pad和media_link结构体。其中media_pad结构体的index成员用来描述自己是第几个pad,成员flag表示是source pad还是sink pad。media_link结构体用来表示每两个entity之间的连接关系。而entity之间用media_device结构体来联系。

2.4.2、数据结构

一个media子系统使用"struct media_device"来表示,结构体如下:

media_device里有多个entity,它使用"struct media_entity"来表示:

media_entity里有多个pad,它使用"struct media_pad"来表示:

entity通过pad跟其他entity相连,这被称为link,它使用"media_link"来表示:

两个entity之间的连接,被称为link;多个已经使能的link构成一条完整的数据通道,这被称为pipeline。驱动程序在进行streamon操作时,有些驱动会调用"media_entity_pipeline_start"函数,类似下面的代码:

ret = media_entity_pipeline_start(sensor, camif->m_pipeline);

第1个参数是第1个entity,第2个参数就是pipeline(它在media_entity_pipeline_start函数内部构造)。

media_entity_pipeline_start的目的,是从第1个entity开始,遍历所有的link,把已经使能的link都执行"link_validate"。

media_entity_pipeline_start内部,是按照深度优先来操作entity的,以下图为例:

  • 有两条pipeline:e0->e1->e2->e3,e0->e1->e4->e5
  • 先遍历e0->e1->e2->e3这条pipeline,调用e3、e2的"link_validate"
  • 再遍历e0->e1->e4->e5这条pipeline,调用e5、e4的"link_validate"
  • 接着处理e1,调用它的"link_validate"
  • 最后处理e0,它没有sink pad,无需调用它的"link_validate"

subdev_160">2.4.3、media子系统subdev的关系

内核空间,v4l2_subdev里含有media_entity,两者都很容易找到对方:

从entity找到subdev,使用以下代码:

#define media_entity_to_v4l2_subdev(ent) \container_of(ent, struct v4l2_subdev, entity)

用户空间里,怎么从entity找到对应的/dev/v4l-subdevX?参考<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">v4l-utils-1.20.0\utils\media-ctl\libmediactl.c:</font>

// 1. open /dev/media0
media->fd = open(media->devnode, O_RDWR);// 2.循环调用ioctl(MEDIA_IOC_ENUM_ENTITIES), 得到每一个entity的信息
entity = &media->entities[media->entities_count];
memset(entity, 0, sizeof(*entity));
entity->fd = -1;
entity->info.id = id | MEDIA_ENT_ID_FLAG_NEXT;
entity->media = media;// entity->info类型为: struct media_entity_desc info; 
// 里面含有major, minor
ret = ioctl(media->fd, MEDIA_IOC_ENUM_ENTITIES, &entity->info);// 3. 使用media_get_devname_sysfs函数获得entity对应的设备节点
// 根据主设备号、次设备号在/sys/dev/char目录下找到类似这样的文件: /sys/dev/char/81:2
// 它是一个连接文件,连接到:../../devices/platform/soc/5800800.vind/video4linux/v4l-subdev0
// /dev/v4l-subdev0就是这个entity对应的设备节点
// APP的media_entity.devname[32]里就是"/dev/v4l-subdev0"
static int media_get_devname_sysfs(struct media_entity *entity);

subdev_198">2.5、subdev的注册和使用

2.5.1、内核注册过程

subdev里含有模块的操作函数,谁调用这些函数?

  • 内核调用:subdev完全可以不暴露给用户,在摄像头驱动程序内部"偷偷地"调用subdev的函数,用户感觉不到subdev的存在。
  • APP调用:对于比较复杂的硬件,驱动程序应该"让用户有办法调节各类参数",比如ISP模块几乎都是闭源的,对它的设置只能通过APP进行。这类subdev的函数,应该暴露给用户,用户可以调用它们。

在内核里,subdev的注册过程为分为2步:

int v4l2_device_register_subdev(struct v4l2_device *v4l2_dev,struct v4l2_subdev *sd);// 核心代码
list_add_tail(&sd->list, &v4l2_dev->subdevs);
  • v4l2_device_register_subdev_nodes:遍历v4l2_device链表里各个subdev,如果它想暴露给APP,就把它注册为普通字符设备
int v4l2_device_register_subdev_nodes(struct v4l2_device *v4l2_dev);// 调用过程如下
v4l2_device_register_subdev_nodesstruct video_device *vdev;vdev->fops = &v4l2_subdev_fops;err = __video_register_device(vdev, VFL_TYPE_SUBDEV, -1, 1,sd->owner);name_base = "v4l-subdev";vdev->cdev->ops = &v4l2_fops;ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);

v4l2_device_register_subdev_nodes的注册过程涉及2个结构体:

  • file_operations:

  • v4l2_file_operations:

注册subdev后,内核里结构体如下:

subdev_247">2.5.2、内核态使用subdev

可以直接调用subdev里的操作函数,也可以使用下面的宏:

#define v4l2_subdev_call(sd, o, f, args...)				\(!(sd) ? -ENODEV : (((sd)->ops->o && (sd)->ops->o->f) ?	\(sd)->ops->o->f((sd), ##args) : -ENOIOCTLCMD))

示例:drivers\media\platform\blackfin\bfin_capture.c

subdev的ops示例:

subdev_264">2.5.3、用户态使用subdev

APP对/dev/v4l-subdev*这类的设备进行ioctl操作时,内核里流程如下:

App:   ioctl(fd, cmd, arg)
--------------
kernel: 
v4l2_fops.unlocked_ioctl, 即v4l2_ioctlret = vdev->fops->unlocked_ioctl(filp, cmd, arg);v4l2_subdev_fops.unlocked_ioctl, 即subdev_ioctlvideo_usercopy(file, cmd, arg, subdev_do_ioctl);

APP可以打开设备文件/dev/v4l-subdevX,执行ioctl调用驱动里面的各个ops函数,调用关系如下:

v4l2_subdev_core_opsfont_281">2.5.3.1、v4l2_subdev_core_ops
  • VIDIOC_SUBSCRIBE_EVENT:APP订阅感兴趣的事件,导致core->subscribe_event被调用
  • VIDIOC_UNSUBSCRIBE_EVENT:APP取消兴趣,导致core->unsubscribe_event被调用
  • VIDIOC_DBG_G_REGISTER:调试作用,读寄存器,导致core->g_register被调用
  • VIDIOC_DBG_S_REGISTER:调试作用,写寄存器,导致core->s_register被调用
  • VIDIOC_LOG_STATUS:调试作用,打印调试信息,导致core->log_status被调用
v4l2_subdev_video_opsfont_288">2.5.3.2、v4l2_subdev_video_ops
  • VIDIOC_SUBDEV_G_FRAME_INTERVAL:获得帧间隔,导致video->g_frame_interval被调用
  • VIDIOC_SUBDEV_S_FRAME_INTERVAL:设置帧间隔,导致video->s_frame_interval被调用
  • VIDIOC_SUBDEV_QUERY_DV_TIMINGS:导致video->query_dv_timings被调用
  • VIDIOC_SUBDEV_G_DV_TIMINGS:导致video->g_dv_timings被调用
  • VIDIOC_SUBDEV_S_DV_TIMINGS:导致video->s_dv_timings被调用
v4l2_subdev_pad_opsfont_295">2.5.3.3、v4l2_subdev_pad_ops
  • VIDIOC_SUBDEV_G_FMT:导致pad->get_fmt被调用
  • VIDIOC_SUBDEV_S_FMT:导致pad->set_fmt被调用
  • VIDIOC_SUBDEV_G_CROP/VIDIOC_SUBDEV_G_SELECTION:导致pad->get_selection被调用
  • VIDIOC_SUBDEV_S_CROP/VIDIOC_SUBDEV_S_SELECTION:导致pad->set_selection被调用
  • VIDIOC_SUBDEV_ENUM_MBUS_CODE:导致pad->enum_mbus_code被调用
  • VIDIOC_SUBDEV_ENUM_FRAME_SIZE:导致pad->enum_frame_size被调用
  • VIDIOC_SUBDEV_ENUM_FRAME_INTERVAL:导致pad->enum_frame_interval被调用
  • VIDIOC_G_EDID:导致pad->get_edid被调用
  • VIDIOC_S_EDID:导致pad->set_edid被调用
  • VIDIOC_SUBDEV_DV_TIMINGS_CAP:导致pad->dv_timings_cap被调用
  • VIDIOC_SUBDEV_ENUM_DV_TIMINGS:导致pad->enum_dv_timings被调用
2.5.3.4、驱动内部调用
  • v4l2_subdev_link_validate:导致pad->link_validate被调用
  • v4l2_subdev_alloc_pad_config:导致pad->init_cfg被调用

2.6、media子系统的注册与使用

2.6.1、内核注册过程

总图如下:

media子系统的注册分为2个层次,4个步骤:

2个层次:

  • 描述自己的media_entity:各个subdev里含有media_entity,但是多个media_entity之间的关系由更上层的驱动决定。
  • 描述media_entity之间的联系:更上层的、统筹的驱动。它知道各个subdev即各个media_entity之间的联系:link。

4个步骤:

media子系统的注册分为4个步骤:

  • 描述自己:各个底层驱动构造subdev时,顺便初始里面的media_entity:比如这个media_entity有哪些pad
// 示例
// drivers\media\platform\sunxi-vin\modules\sensor\gc2053_mipi.c: cci_dev_probe_helper
// drivers\media\platform\sunxi-vin\vin-cci\cci_helper.c: cci_media_entity_init_helper
media_entity_pads_init(&sd->entity, SENSOR_PAD_NUM, si->sensor_pads);
  • 注册自己:底层或上层注册subdev时,顺便注册media_entity:把media_entity记录在media_device里
// 示例
// drivers\media\platform\sunxi-vin\vin.c
vin_md_register_entitiesv4l2_device_register_subdevstruct media_entity *entity = &sd->entity;err = media_device_register_entity(v4l2_dev->mdev, entity);
  • 和别人建立联系:subdev之上的驱动程序决定各个media_entity如何连接:比如调用media_create_pad_link创建连接
// 示例
// drivers\media\platform\sunxi-vin\vin.c
ret = media_create_pad_link(source, SCALER_PAD_SOURCE,sink, VIN_SD_PAD_SINK,MEDIA_LNK_FL_ENABLED);
  • 暴露给APP使用:subdev之上的驱动程序注册media_device: media_device里已经汇聚了所有的media_entity
// 示例
// drivers\media\platform\sunxi-vin\vin.c
vin_proberet = media_device_register(&vind->media_dev);

在内核里,media子系统的注册过程为:

media_device_register__media_device_registerstruct media_devnode *devnode;devnode->fops = &media_device_fops;ret = media_devnode_register(mdev, devnode, owner);cdev_init(&devnode->cdev, &media_devnode_fops);ret = cdev_add(&devnode->cdev, MKDEV(MAJOR(media_dev_t), devnode->minor), 1);

上述注册过程涉及2个结构体:

  • file_operations:

  • media_file_operations:

2.6.2、APP使用media子系统

APP使用media子系统时,除了open之外,就只涉及5个ioctl:

  • MEDIA_IOC_DEVICE_INFO
  • MEDIA_IOC_ENUM_ENTITIES
  • MEDIA_IOC_ENUM_LINKS
  • MEDIA_IOC_SETUP_LINK
  • MEDIA_IOC_G_TOPOLOGY

这5个ioctl将导致内核中如下函数被调用(drivers\media\media-device.c):

APP对/dev/media0这类的设备进行ioctl操作时,内核里流程如下:

App:   ioctl
--------------
kernel: 
media_devnode_fops.unlocked_ioctl, 即media_ioctl__media_ioctl(filp, cmd, arg, devnode->fops->ioctl);media_device_ioctlconst struct media_ioctl_info *info;info = &ioctl_info[_IOC_NR(cmd)];ret = info->arg_from_user(karg, arg, cmd);ret = info->fn(dev, karg);ret = info->arg_to_user(arg, karg, cmd);

核心在于ioctl_info:

2.6.2.1、MEDIA_IOC_DEVICE_INFO

这个ioctl被用来获得/dev/mediaX的设备信息,设备信息结构体如下

APP代码如下:

memset(&media->info, 0, sizeof(media->info)); // struct media_device_info info;ret = ioctl(media->fd, MEDIA_IOC_DEVICE_INFO, &media->info);

驱动中对应核心代码如下:

2.6.2.2、MEDIA_IOC_ENUM_ENTITIES

这个ioctl被用来获得指定entity的信息,APP参考代码为:

驱动中对应核心代码如下:

2.6.2.3、MEDIA_IOC_ENUM_LINKS

这个ioctl被用来枚举enity的link,APP参考代码为:

驱动中对应核心代码如下:

2.6.2.4、MEDIA_IOC_SETUP_LINK

这个ioctl被用来设置link,把源pad、目的pad之间的连接激活(active),或者闲置(inactive),APP示例代码为:

APP传入的flags的取值有2种:

  • 0:闲置link
  • 1:激活link

内核里link的flag有如下取值:

如果这个link的flag不是MEDIA_LINK_FL_IMMUTABLE的话,就可以去更改它的bit0状态:激活或闲置。
驱动中对应核心代码如下:

2.6.2.5、MEDIA_IOC_G_TOPOLOGY

这个ioctl被用来获得整体的拓扑图,包括entities、interfaces、pads、links的信息,示例代码如下:

驱动中对应核心代码如下,只要APP提供了对应的buffer,它就可以复制对应的信息,比如entities、interfaces、pads、links的信息:


http://www.ppmy.cn/embedded/167319.html

相关文章

Docker Engine stopped

参考【已解决】win10系统 Docker 提示Docker Engine stopped解决全过程记录-CSDN博客 关键的处理办法&#xff1a;需要wsl --update更新一下&#xff0c;然后卸载docker&#xff0c;重新安装后重启系统恢复正常 重启系统后看到running状态

选择排序:简单高效的选择

大家好&#xff0c;今天我们来聊聊选择排序&#xff08;Selection Sort&#xff09;算法。这是一个非常简单的排序算法&#xff0c;适合用来学习排序的基本思路和操作。选择排序在许多排序算法中以其直观和易于实现的特点著称&#xff0c;虽然它的效率不如其他高效算法&#xf…

【Kafka】Windows下安装Kafka(图文记录详细步骤)

【Kafka】Windows下安装Kafka Kafka简介一、Kafka安装前提安装Kafka之前&#xff0c;需要安装JDK、Zookeeper、Scala。1.1、JDK安装&#xff08;version&#xff1a;1.8&#xff09;1.1.1、JDK官网下载1.1.2、JDK网盘下载1.1.3、JDK安装 1.2、Zookeeper安装1.2.1、Zookeeper官网…

计算机考研之数据结构:斐波那契数列专题(1)

不论是在算法还是在编程语言的教材中&#xff0c;都可能会以斐波那契数列为例&#xff0c;或说明其算法上的特点——主要是递归&#xff0c;或说明如何运用某种编程语言编写相应函数。 本文是一篇关于“斐波那契数列”的专题文章&#xff0c;目的是让学习《数据结构》这门课程…

哈希表入门到精通:从原理到 Python 实现全解析

系列文章目录 01-从零开始掌握Python数据结构&#xff1a;提升代码效率的必备技能&#xff01; 02-算法复杂度全解析&#xff1a;时间与空间复杂度优化秘籍 03-线性数据结构解密&#xff1a;数组的定义、操作与实际应用 04-深入浅出链表&#xff1a;Python实现与应用全面解析 …

排序算法适合的场景

排序算法的选择取决于数据规模、特性、稳定性需求、内存限制等因素。以下为常见排序算法及其适用场景&#xff1a; 1. 简单排序算法&#xff08;O(n)&#xff09; 冒泡排序 场景&#xff1a;数据量极小&#xff08;如 n ≤ 100&#xff09;或几乎有序&#xff1b;教学演示。缺点…

【面试手撕】多线程/并发编程

文章目录 前言三个线程&#xff0c;交替打印A、B、C两个线程1~100交替输出奇数和偶数10个线程&#xff0c;每个线程1w&#xff0c;最终变量到达10w模拟死锁让三个线程怎么串行执行1.使用join方法2.使用CountDownLatch 前言 本文总结面试中常考的手撕多线程问题。 三个线程&am…

排序算法模板——归并,快排【C++】

前言 二者都是分治思想的体现&#xff0c;区别是归并是以整个数组的mid&#xff08;下标的中间值&#xff09;来分&#xff0c;分别将左右两个区间排好序&#xff0c;再合并&#xff1b;而快排是以数组中的一个数来划分&#xff0c;将小于等于这个数的放在该数左边&#xff0c…