目录
1 V4L2框架结构概述
1.1 imx8视频输入通路硬件结构
1.2 V4L2设备节点观察
1.3 dts配置观察
1.4 probe函数观察
1.4.1 函数功能简介
1.4.2 各模块probe函数分析
1.5 V4L2框架结构图示
2 设备管理机制
2.1 v4l2_device结构体相关
2.1.1 v4l2_device结构体
2.1.2 v4l2_device结构体相关操作
2.2 video_device结构体相关
2.2.1 video_device结构体
2.2.2 video_device结构体相关操作
2.3 核心数据结构关系
3 V4L2设备节点文件操作
3.1 通用文件操作函数集v4l2_fops
3.1.1 概述
3.1.2 v4l2_fops回调函数分析
3.2 V4L2 ioctl系统调用执行流程
3.2.1 确定驱动程序支持的ioctl操作
3.2.2 video_ioctl2函数分析
1 V4L2框架结构概述
1.1 imx8视频输入通路硬件结构
软件框架是对硬件结构的映射与描述,所以在说明V4L2框架结构之前先说明一下硬件结构,此处以imx8视频输入通路为例(下图中红框部分)
1. MIPI-CSI2(Camera Serial Interface V2)
① MIPI-CSI是由MIPI联盟下的Camera工作组指定的接口标准,用于规范Camera和SoC的连接
② imx8支持2个4-lane的MIPI-CS2接口用于连接Camera
③ MIPI-CSI2的输出根据配置可传输给下一级的ISI或ISP模块处理
说明:在当前使用的imx8开发板中,从第2组MIPI-CSI2中引出2-lane的接口连接OV5645 camera,对camera的控制则是通过I2C接口实现
2. ISI(Image Sensing Interface,图像传感器接口)
① ISI是一个简单的camera接口,可以对camera输入的图像进行简单处理
② ISI支持的功能包括下采样(down scaling)、颜色空间转换(color space conversion)、去隔行(de-interlacing)、裁剪(cropping)和旋转(rotation)
③ 经过ISI处理的图像将被存储在软件指定的内存中
说明:去隔行是指进行隔行扫描(interlaced)到逐行扫描(progressive)的转换
3. ISP(Image Signal Processor,图像信号处理器)
① ISP可以对camera输入的图像进行更为复杂的处理(e.g. 3A处理和降噪)
② 经过ISP处理的图像将被存储在软件指定的内存中
4. Dewarp Engine(广角鱼眼畸变矫正引擎)
① Dewarp Engine用于矫正广角鱼眼镜头产生的畸变
② Dewarp Engine从内存中获取待矫正的图像,之后再将矫正后的图像输出到内存
1.2 V4L2设备节点观察
在了解了imx8视频输入通路的硬件结构之后,我们再从V4L2设备节点的角度对其进行观察
1. 在当前实验环境中,生成的V4L2设备节点如下图所示
2. 在/sys/class/video4linux目录下查看每个V4L2设备节点的name字段,与imx8视频输入通路相关的设备节点如下图红框中所示
1.3 dts配置观察
1. arch/arm64/boot/dts/freescale/imx8mp.dtsi
可见在当前实验环境中,与视频通路相关的物理IP被组织在逻辑设备cameradev之中
2. arch/arm64/boot/dts/freescale/OK8MP-C.dts
可见在当前实验环境中,OV5645 sensor连接在mipi_csi_1
1.4 probe函数观察
1.4.1 函数功能简介
观察上述模块的probe函数,是为了理清V4L2设备节点是何时以及如何被创建的,为此先简单介绍相关函数的功能
函数 | 功能 |
v4l2_device_register | 注册v4l2_device结构体 硬件设备可能包含多个子设备,v4l2_device结构体就是所有这些设备的根节点,负责管理所有子设备 |
v4l2_subdev_init | 初始化v4l2_subdev结构体,设置子设备操作函数集 |
v4l2_device_register_subdev | 注册子设备,将v4l2_subdev结构体注册到v4l2_device结构体中 |
v4l2_async_register_subdev | 异步注册子设备,将v4l2_subdev结构体注册到V4L2异步框架(async framework)中。在完成匹配后,异步框架会调用v4l2_device_register_subdev函数将异步注册的v4l2_subdev结构体注册到匹配的v4l2_device结构体中 |
video_register_device | 注册video_device结构体,一般用于创建/dev/video[x]设备节点 |
v4l2_device_register_subdev_nodes | 根据配置,为v4l2_device结构体管理的所有v4l2_subdev结构体注册video_device结构体,创建/dev/v4l-subdev[x]设备节点 |
说明:创建/dev/video[x]和/dev/v4l-subdev[x]设备节点的目的,就是为了让应用程序可以在用户态使用这些设备提供的功能
1.4.2 各模块probe函数分析
1.4.2.1 isi父设备
文件:drivers/staging/media/imx/imx8-isi-core.c
mxc_isi_probe函数中会分配并填充mxc_isi_dev结构体
1.4.2.2 isi子设备
文件:drivers/staging/media/imx/imx8-isi-cap.c
isi_cap_probe函数中会分配并填充mxc_isi_cap_dev结构体,并且初始化mxc_isi_cap_dev.sd子设备。需要注意的是,初始化过程中并没有设置V4L2_SUBDEV_FL_HAS_DEVNODE标志,那么在v4l2_device_register_subdev_nodes函数中将不会为其创建/dev/v4l-subdev[x]设备节点
说明1:在为mxc_isi_cap_dev.sd子设备设置的internal_ops->registered回调函数中,会创建isi子设备所属的/dev/video[x]设备节点,该节点就是操作MIPI Camera进行图像采集时使用的设备节点,详情可参考Linux多媒体子系统01:从用户空间使用V4L2子系统 chapter 1.3
说明2:v4l2_subdev结构体中internal_ops->registered回调函数的调用时机是在子设备v4l2_subdev结构体被注册时(在isi_cap_probe函数中只是调用v4l2_subdev_init函数初始化了mxc_isi_cap_dev.sd子设备)
1.4.2.3 mipi-csi设备
文件:drivers/staging/media/imx/imx8-mipi-csi2-sam.c
mipi_csis_probe函数中会初始化csi_state.sd子设备,并且设置了V4L2_SUBDEV_FL_HAS_DEVNODE标志
1.4.2.4 ov5645 sensor
文件:drivers/media/i2c/ov5645.c
ov5645_probe函数中会初始化ov5645.sd子设备,并且设置了V4L2_SUBDEV_FL_HAS_DEVNODE标志,同时还将该子设备注册到异步框架中
1.4.2.5 cameradev逻辑设备
文件:drivers/staging/media/imx/imx8-media-dev.c
1. 经过上面的步骤,与isi子设备和mipi-csi子设备相关的v4l2_subdev结构体已经被初始化但是尚未注册,mxc_md_probe函数会进行汇总。该函数会先注册v4l2_device结构体,之后调用v4l2_device_register_subdev函数将isi子设备和mipi-csi子设备注册到v4l2_device结构体中
说明:在register_isi_entity函数调用v4l2_device_register_subdev函数的过程中,就会导致isi_cap_probe函数注册的internal_ops->registered回调函数被调用,从而为isi子设备创建/dev/video[x]设备节点
2. 在完成v4l2_subdev结构体的注册后,就需要为各子设备创建/dev/v4l_subdev[x]设备节点,该操作是在subdev_notifier_complete函数中完成。subdev_notifier_complete函数被注册为subdev_notifier的回调函数,将在异步注册的ov5645 sensor子设备匹配成功时调用
说明:根据上述分析,如果不连接ov5645 sensor就会导致相应的子设备无法匹配,那么subdev_notifier_complete函数就不会被调用到,这就会导致整个视频输入通路的设备节点都不会被注册。经过验证,确实如此
1.4.2.6 isp设备
文件:extra/vvcam/v4l2/isp_driver_of.c
isp_hw_probe函数中会初始化isp_device.sd子设备,并且设置了V4L2_SUBDEV_FL_HAS_DEVNODE标志,同时还将该子设备注册到异步框架中
1.4.2.7 dwe设备
文件:extra/vvcam/v4l2/dwe_driver_of.c
dwe_hw_probe函数中会初始化dwe_device.sd子设备,并且设置了V4L2_SUBDEV_FL_HAS_DEVNODE标志,同时还将该子设备注册到异步框架中
1.4.2.8 vvcam逻辑设备
文件:extra/vvcam/v4l2/video/video.c
vvcam逻辑设备的作用与cameradev逻辑设备类似,用于汇总实现isp设备和dwe设备对应的/dev/video[x]和/dev/v4l-subdev[x]设备节点的创建
说明1:isp和dwe设备对应的v4l2_subdev结构体均采用异步方式注册,因此也是在匹配之后创建/dev/v4l-subdev[x]设备节点
说明2:上述分析的probe函数在实验环境中的启动顺序如下
说明3:在当前实验环境中,视频输入通路中如下组件的驱动程序是以内核模块的方式加载的
1.5 V4L2框架结构图示
根据上述分析,描述imx8视频输入通路的V4L2框架结构如下图所示,
说明1:出于简化目的,图中仅画了一路mipi-csi通路
说明2:可见在当前实验环境中,默认使用的是sensor --> mipi-csi --> isi --> ddr的视频输入通路,并没有使用isp和dwe
说明3:创建/dev/video[x]设备节点与创建/dev/v4l-subdev[x]设备节点的区别
① 在实验环境的默认视频输入通路中,sensor / mipi-csi / isi均有相应的v4l2_subdev结构体,但是sensor和mipi-csi创建的是/dev/v4l-subdev[x]设备节点,isi创建的是/dev/video[x]设备节点
② 无论是创建/dev/v4l-subdev[x]设备节点还是/dev/video[x]设备节点,目的都是让应用程序可以在用户态直接使用设备提供的功能
- 从提供的ioctl功能来看,/dev/video[x]设备节点提供的功能更加完备,/dev/v4l-subdev[x]设备节点支持的ioctl操作为/dev/video[x]设备的子集(可参考subdev_do_ioctl函数)以及一组子设备专用操作
- 从持有的资源来看,/dev/video[x]设备节点一般都持有缓冲区队列(也就是vb2_queue结构体),可以进行缓冲区操作
③ 缓冲区的核心是缓冲区内存,因此最终操作缓冲区内存的设备需要创建/dev/video[x]设备节点,这也是视频通路中isi设备需要创建/dev/video[x]设备节点的原因
说明4:关于bridge driver的概念
在Linux内核的V4L2文档中,会提到一个bridge driver的概念。在内核文档中并没有给出bridge driver的清晰定义,但是他具备如下特征,
① bridge driver会创建/dev/video[x]设备节点,而其他驱动可以创建/dev/v4l-subdev[x]设备节点
② 通过bridge driver可以间接操作v4l2_subdev子设备
③ bridge driver会将总线上的数据传送到内存中
因此可以将bridge driver理解为V4L2设备驱动中处于顶层的,用于进行汇总和统领的驱动,当前实验环境中的cameradev驱动就是bridge driver
2 设备管理机制
2.1 v4l2_device结构体相关
2.1.1 v4l2_device结构体
视频设备正变得越来越复杂,在此类设备中通常包含多个需要相互协作的硬件IP,这会导致复杂的V4L2驱动程序。如上文所示,如果将imx8默认视频输入通路视为一个V4L2驱动程序,其中就包含了sensor、mipi-csi和isi等多个子设备。v4l2_device结构体就是这些子设备的根节点,负责管理所有子设备
说明1:关于dev字段
① v4l2_device结构体中并不包含device结构体实例,只是包含了指向device结构体的指针(即dev字段)。由于不包含device结构体实例,v4l2_device结构体在Linux设备驱动模型中不会存在实体,而是依赖dev字段所指向的device结构体存在
② 在很多材料中,包括Linux内核的V4L2文档中,将dev字段指向的device结构体称作v4l2_device结构体的父设备(parent device)。但是根据上述分析,v4l2_device结构体本身不包含device结构体,在Linux设备驱动模型中自己并不是一个设备,所以个人觉得将dev字段指向的device结构体称作父设备不是非常合理
③ 在本文中,将dev字段指向的device结构体称作与v4l2_device结构体关联的device结构体
说明2:关于notify字段
notify字段指向的回调函数通过v4l2_subdev_notify_event函数调用,供v4l2_subdev子设备向v4l2_device父设备通知有事件发生
说明3:本文不包含对V4L2子设备的详细说明
2.1.2 v4l2_device结构体相关操作
2.1.2.1 注册v4l2_device结构体
V4L2驱动程序可以通过v4l2_device_register函数完成v4l2_device结构体的注册,
说明1:关于与v4l2_device结构体关联的device结构体
与v4l2_device结构体关联的device结构体通常是总线相关设备数据结构中的device结构体,e.g. platform_device、usb_device和pci_dev。在imx8视频输入通路的V4L2驱动程序中,就是将cameradev逻辑设备驱动所属platform_device中的device结构体设置为与v4l2_device结构体关联的device结构体
说明2:关于v4l2_device结构体中name字段的设置
① 如果V4L2驱动程序调用v4l2_device_register函数时传递的dev参数为NULL,则必须在调用该函数之前设置name字段
② 如果V4L2驱动程序调用v4l2_device_register函数时传递了有效的dev参数,则只有在驱动程序没有设置name字段的情况下,v4l2_device_register函数才会根据驱动程序名和设备名构造默认的V4L2设备名
2.1.2.2 注销v4l2_device结构体
V4L2驱动程序可以通过v4l2_device_unregister函数完成v4l2_device结构体的注销,
说明1:一般在platfrom驱动程序的probe函数中注册v4l2_device结构体,在remove函数中注销v4l2_device结构体
说明2:关于v4l2_device_disconnect函数
① v4l2_device_disconnect函数的作用是将V4L2设备(由一个v4l2_device结构体实例表示)的状态设置为断开连接
② 如果v4l2_device结构体关联的device设备支持热插拔(hot-pluggable),e.g. v4l2_device结构体关联的device结构体属于一个USB设备,那么当该设备断开连接时,需要标识v4l2_device结构体关联的device已经无效
2.1.2.3 维护v4l2_device结构体引用计数
可以通过v4l2_device_get和v4l2_device_put函数维护v4l2_device结构体的引用计数,需要注意的是,因为v4l2_device结构体中并不包含device结构体实例,所以是通过内部包含的kref结构体维护引用计数
说明1:v4l2_device_unregister函数不减少v4l2_device结构体的引用计数
需要注意的是,v4l2_device_unregister函数在注销v4l2_device结构体时并不会减少v4l2_device结构体的引用计数。考虑到v4l2_device_register函数中设置的v4l2_device结构体引用计数初始值为1,可以在驱动程序的disconnect或remove回调函数中调用v4l2_device_put函数减少引用计数,否则引用计数不会达到0
说明2:v4l2_device.release回调函数设置实例
文件:drivers/media/radio/dsbr100.c
① dsbr100.c驱动程序对应的设备是一个USB设备,在相应的probe回调函数中会注册v4l2_device结构体并创建/dev/radio[x]设备节点,在此过程中会设置v4l2_device.release回调函数
② 由于USB设备支持热插拔,当设备断开连接时会调用相应的disconnect回调函数。此处的disconnect回调函数的实现非常典型,其中v4l2_device_put操作会将v4l2_device结构体的引用计数减少到0
③ 当v4l2_device结构体引用计数减少到0时,会调用v4l2_device.release回调函数,也就是usb_dsbr100_release函数。该函数会注销v4l2_device结构体,并释放之前在probe函数中动态分配的内存
2.1.2.4 设置v4l2_device结构体name字段
可以通过v4l2_device_set_name函数设置v4l2_device结构体的name字段,
说明:v4l2_device_set_name函数调用实例
文件:drivers/media/radio/radio-si476x.c
此处通过ATOMIC_INIT定义初值为0的实例计数器(instance counter)并传递给v4l2_device_set_name函数,是该函数的典型用法
2.2 video_device结构体相关
2.2.1 video_device结构体
video_device结构体的主要目的是支持/dev/video[x]和/dev/v4l-subdev[x]设备节点,
说明1:video_device结构体在Linux设备驱动模型中的存在
① video_device结构体中包含了device结构体实例,因此在Linux设备驱动模型中会存在实体
② video_device结构体中的device结构体属于video_class类,该类在videodev_init函数中注册(可见在该函数中还预先注册了所有V4L2字符设备的设备号)
文件:drivers/media/v4l2-core/v4l2-dev.c
③ 与注册的video_class类名对应,video_device结构体会在/sys/class/video4linux目录下生成相应节点
说明2:关于vfl_type字段
① vfl_type字段表示V4L2设备节点类型,使用vfl_devnode_type枚举值设置,各枚举值含义如下,
本文中仅涉及VFL_TYPE_GRABBER和VFL_TYPE_SUBDEV类型
② 不同的V4L2设备节点类型会使用不同的设备节点名称
③ vfl_type字段设置实例
vfl_type字段值一般在注册video_device结构体时通过参数传递,imx8视频输入通路的V4L2驱动中设置的vfl_type字段如下,
说明3:关于vfl_dir字段
① vfl_dir字段表示V4L2设备节点方向,使用vfl_devnode_direction枚举值设置,各枚举值含义如下,
② vfl_dir字段设置实例
vfl_dir字段一般在注册video_device结构体之前设置,在imx8视频输入通路的V4L2驱动中,在将video_device结构体清零后没有再设置该字段,因此使用的字段值为0,也就是VFL_DIR_RX
说明4:关于device_caps字段
① device_caps字段由驱动程序根据设备能力进行设置,字段值由一系列标志位构成
② device_caps字段设置实例
除了V4L2子设备(对应VFL_TYPE_SUBDEV设备节点类型),其他设备类型必须在注册video_device之前设置device_caps字段,imx8视频输入通路的V4L2驱动中设置的device_caps字段如下,
V4L2_CAP_STREAMING | 设备支持流式IO |
V4L2_CAP_VIDEO_CAPTURE_MPLANE | 设备支持multi-planar格式图像获取 |
说明5:name字段设置实例
name字段一般在注册video_device结构体之前设置,imx8视频输入通路的V4L2驱动中设置的name字段如下,
如上文所述,name字段会体现在/sys/class/video4linux/[V4L2设备节点名]/name文件节点,
说明6:关于flags字段
flags字段使用v4l2_video_device_flags枚举值设置,各枚举值含义如下,
说明7:关于dev_debug字段
① dev_debug字段体现在/sys/class/video4linux/[V4L2设备节点名]/dev_debug文件节点
② dev_debug字段对应V4L2框架定义的如下sysfs属性,
文件:drivers/media/v4l2-core/v4l2-dev.c
③ dev_debug字段的掩码取值如下,
说明8:关于queue字段
① video_device结构体中的queue字段只是一个vb2_queue结构体类型指针,用于指向与当前V4L2设备节点关联的缓冲区队列。如果使用该字段,需要驱动程序自己准备vb2_queue结构体,imx8视频输入通路的V4L2驱动中设置的queue字段如下,
② 由于video_device结构体中只有一个queue字段,因此如果使用该字段,则意味着video_device结构体持有且只持有一个缓冲区队列,而且该队列是与video_device结构体绑定的
如果不满足上述条件,则不需要使用queue字段,示例场景如下,
- 对于V4L2子设备,不需要持有缓冲区队列
- 如果驱动程序以打开V4L2设备节点的file结构体为单位分配缓冲区队列,则video_device结构体中的queue字段不能满足要求
- 如果设备需要持有多个缓冲区队列(e.g. codec的V4L2驱动需要持有输入和输出两个缓冲区队列),则video_device结构体中的queue字段也无法满足需求
说明9:关于lock字段
① video_device结构体中的lock字段也只是一个mutex结构体类型指针,V4L2框架会使用lock字段指向的互斥锁来序列化对video_device结构体的访问。如果使用该字段,需要驱动程序自己准备mutex结构体,imx8视频输入通路的V4L2驱动中设置的lock字段如下,
② 所有V4L2的ioctl操作都可以通过lock字段指向的互斥锁进行序列化,
③ 由上述分析可见,V4L2框架的ioctl操作有两级级锁机制,
- 所有ioctl操作都可以通过video_device层级lock进行序列化
- 如果注册了vb2_queue层级lock,则所有queueing ioctl操作会使用vb2_queue层级lock进行序列化
设置2级锁机制的目的,是为了提高ioctl操作的并行效率,使得queueing ioctl操作和非queueing ioctl操作可以并行处理。但是一般驱动程序会将video_device层级lock和vb2_queue层级lock指向相同的互斥锁,imx8视频输入通路的V4L2驱动就是按该策略进行的设置
④ 所谓queueing ioctl操作,就是由INFO_FL_QUEUE宏标记的操作,
根据v4l2_ioctls数组,共有REQBUFS / QUERYBUF / QBUF / EXPBUF / DQBUF / STREAMON / STREAMOFF / CREATE_BUFS / PREPARE_BUF这9个命令输入queueing ioctl操作,显然他们都会对缓冲区队列(也就是vb2_queue结构体)进行操作
⑤ 驱动程序也可以两级锁机制都不使用,即video_device.lock和vb2_queue.lock字段均置为NULL,此时完全由驱动程序进行互斥操作(但是个人认为没有必要)
说明10:minor / num / index字段辨析
本节先给出结论,上述各字段设置与生效的方式详见下文对注册video_device结构体的函数分析
① minor字段表示V4L2字符设备的子设备号
② num字段表示V4L2设备节点名称编号,即/dev/video[x]或/dev/v4l-subdev[x]中的x
③ index字段表示V4L2设备节点序号,即同属于一个v4l2_device父设备之下的video_device结构体序号。以imx8视频输入通路为例,mipi-csi2 / ov5645 / isi设备均属于cameradev逻辑设备注册的v4l2_device父设备,他们对应的video_device结构体的index字段从0开始递增
2.2.2 video_device结构体相关操作
2.2.2.1 分配video_device结构体
V4L2驱动程序可以通过video_device_alloc函数动态分配video_device结构体,
说明1:驱动程序可以使用video_device_alloc函数动态分配video_device结构体,或者以静态方式嵌入到更大的动态分配的结构体中(大多数情况下是设备状态结构体,imx8视频输入通路的V4L2驱动程序就使用了这种方式)
说明2:video_device_alloc函数调用实例
文件:extra/vvcam/v4l2/video/video.c
2.2.2.2 释放video_device结构体
V4L2驱动程序可以通过video_device_release函数释放动态分配的video_device结构体,
说明1:video_device_release函数使用实例
文件:extra/vvcam/v4l2/video/video.c
video_device_release函数的调用与video_device_alloc函数的调用是匹配的,video_device_release函数一般注册为video_device.release字段的回调函数,当video_device结构体中的device结构体实例的引用计数减少到0时会调用video_device.release回调函数,从而释放之前动态分配的video_device结构体
说明2:video_device_release_empty函数
如果驱动程序中使用的video_device结构体是以静态方式嵌入的,则不需要通过video_device_release函数释放内存。但是由于V4L2框架要求注册video_device结构体时必须要设置release字段,所以需要将release字段的回调函数设置为一个空函数,此时就可以使用V4L2框架提供的video_device_release_empty函数
2.2.2.3 注册video_device结构体
V4L2驱动程序可以通过video_register_device函数注册video_device结构体,
说明1:__video_register_device函数分析
① video_register_device函数最终通过__video_register_device函数实现video_device结构体的注册,该函数还被v4l2_device_register_subdev_nodes函数调用,注册V4L2子设备类型的video_device结构体
② __video_register_device函数流程分析如下,
③ __video_register_device函数应该只被V4L2核心框架调用,驱动程序不应直接使用
④ __video_register_device函数假定传递给他的video_device结构体除了设置好的字段其他字段均被清零,结构体中没有无效数据。需要特别注意的是,__video_register_device函数并没有检查要注册的video_device结构体是否已经被注册过,因此驱动程序需要负责对video_register_device函数调用的合理性
⑤ 如果注册video_device结构体时指定的V4L2设备文件名编号已经被占用,是否打印警告信息由传递的warn_if_nr_in_use参数确定。如果不希望打印警告信息,可以调用video_register_device_no_warn函数
说明2:如果video_register_device函数调用失败,video_device结构体中的release回调函数不会被调用到。因此当video_register_device函数调用失败时,需要驱动程序自己释放相关资源,比如调用video_device_release函数释放之前动态分配的video_device结构体
说明3:通过video_is_registered函数可以判断指定的video_device结构体是否已经被注册
说明4:minor / num / index字段的维护
首先需要注意的是,minor、num和index字段是独立维护的,下面逐一进行说明,此处假设不使能CONFIG_VIDEO_FIXED_MINOR_RANGES功能
① minor字段通过video_devices全局数组维护,注册的video_device结构体在数组中的下标就是该V4L2设备的子设备号。注册完成后,通过V4L2设备的子设备号就可以从video_devices数组中索引到相应的video_device结构体
② num字段通过devnode_nums全局位图维护,其中每个V4L2设备类型有自己的位图,因此不同V4L2设备类型的设备节点名称编号都是从0开始
③ index字段通过used局部位图维护,所获得的V4L2设备节点序号局部于具有相同v4l2_device父设备的video_device结构体
说明5:CONFIG_VIDEO_FIXED_MINOR_RANGES机制简介
CONFIG_VIDEO_FIXED_MINOR_RANGES是V4L2框架中的一个老式机制,他按照V4L2设备节点类型对子设备号区域进行静态划分,目前已不再使用
说明6:video_device父设备的指定
video_device结构体的dev_parent字段最终用于设置video_device结构体中device结构体实例的parent字段,一般情况下使用v4l2_device父设备所关联的device结构体。只有当v4l2_device父设备没有设置关联的device结构体时,才需要设置video_device结构体的dev_parent字段
2.2.2.4 注销video_device结构体
V4L2驱动程序可以通过video_unregister_device函数注销video_device结构体,
说明1:根据video_unregister_device函数注释,将清除V4L2_FL_REGISTERED标志的操作至于videodec_lock互斥锁保护之下,是为了实现与v4l2_open函数的互斥
说明2:video_device结构体资源的release流程
① video_unregister_device函数只是清除了V4L2_FL_REGISTERED标志并注销了video_device结构体中的device结构体实例,但是还有大量的资源没有释放(例如在__video_register_device函数中注册的字符设备,标记的位图),这些主要依靠v4l2_device_release函数完成
② 在__video_register_device函数中,v4l2_device_release函数被注册为video_device结构体中device结构体实例的release回调函数,该函数将在device结构体实例的引用计数为0时被调用
__video_register_device函数中调用的device_register函数会增加device结构体实例的引用计数,video_unregister_device函数中调用的device_unregister函数会减少device结构体实例的引用计数
③ v4l2_device_release函数流程分析如下,
④ 需要注意的是,在__video_register_device函数中会调用v4l2_device_get函数增加v4l2_device父设备的引用计数,此处增加的引用计数应该适时予以减少,才能维持引用计数的平衡
说明3:V4L2驱动程序release流程良好实现实例
考虑到v4l2_device_register函数中将v4l2_device结构体的引用计数初始化为1,且v4l2_device_unregister函数不会减少v4l2_device结构体的引用计数。良好的V4L2驱动程序实现如下,
文件:drivers/media/platform/vicodec/vicodec-core.c
① 在platform_driver.probe回调函数中注册v4l2_device结构体时,设置v4l2_device.release回调函数
② 驱动程序在调用video_register_device函数注册video_device结构体时,除了会增加自身device结构体实例的引用计数,还会增加其v4l2_device父设备的引用计数
③ 驱动程序在调用video_unregister_device函数注销video_device结构体时,
- 会减少自身device结构体实例的引用计数,从而导致v4l2_device_release函数被调用,其中会调用到video_device.release回调函数
- 由于注册了v4l2_device.release回调函数,v4l2_device_release函数也会减少v4l2_device父设备的引用计数。由于v4l2_device_register函数中将v4l2_device结构体的引用计数初始化为1,平衡的video_register_device和video_unregister_device函数调用只能将v4l2_device结构体的引用计数维护到1
④ 在platform_driver.remove回调函数中调用v4l2_device_put函数,将v4l2_device结构体的引用计数减少到0,从而导致v4l2_device.release回调函数被调用
⑤ 从上述分析可见,如果没有注册v4l2_device.release回调函数,则需要在注册的video_device.release回调函数中调用v4l2_device_put函数,以维护v4l2_device结构体引用计数的平衡
2.2.2.5 维护video_device结构体引用计数
如上文所述,video_device结构体的引用计数通过其包含的device结构体实例实现。可以通过video_get和video_put函数维护video_device结构体的引用计数
说明1:由于video_get和video_put函数定义在V4L2框架源文件中,并且使用static关键字修饰,所以这2个函数的链接属性被局限在v4l2-dev.c文件中,驱动程序无法直接使用
说明2:video_get和video_put函数调用实例
这2个函数由v4l2_open和v4l2_release函数调用,他们属于V4L2设备节点文件通用操作函数集,
由此可见,如果仍有打开的V4L2设备节点没有关闭,即使调用video_unregister_device函数注销video_device结构体,video_device结构体的引用计数也不会为0,也就不会导致v4l2_device_release函数被调用
2.2.2.6 其他辅助函数
1. 通过video_get_drvdata和video_set_drvdata函数可以获取与设置video_device结构体中device结构体实例的driver_data
2. 通过video_devdata函数可以通过打开文件的子设备号索引到对应的video_device结构体
3. 通过video_drvdata函数可以通过打开文件获取到对应的video_device结构体中device结构体实例的driver_data
说明:video_set_drvdata / video_drvdata / video_get_drvdata / video_devdata函数调用实例
文件:drivers/staging/media/imx/imx8-isi-cap.c
① 在初始化video_device结构体时,通过video_set_drvdata函数,将驱动程序中包含video_device结构体的更大的结构体的指针存储在video_device.dev.driver_data中
② 在后续的V4L2设备文件节点操作中,通过video_drvdata函数获取之前存储的结构体指针,在此过程中会间接调用video_devdata和video_get_drvdata函数
4. 通过video_device_node_name函数可以获取video_device结构体中device结构体实例的name
说明1:video_device_node_name函数打印的name在__video_register_device函数中注册,可见该name就是V4L2设备节点文件名
说明2:video_device_node_name一般在打印调试信息时使用,
2.3 核心数据结构关系
V4L2框架设备管理机制中核心数据结构之间的关系如下图所示,
3 V4L2设备节点文件操作
3.1 通用文件操作函数集v4l2_fops
3.1.1 概述
1. 如上文所述,注册video_device结构体时会生成字符设备节点,在注册相应的字符设备时会指定通用文件操作函数集v4l2_fops
2. 通用文件操作函数集中的回调函数会调用V4L2驱动程序注册在v4l2_file_operations操作函数集中的回调函数
说明1:关于字符设备与file_operations操作函数集的关系,可参考Linux设备驱动基础03:Linux字符设备驱动
说明2:v4l2_file_operations操作函数集实例
文件:drivers/staging/media/imx/imx8-isi-cap.c
imx8视频输入通路定义的v4l2_file_operations操作函数集如下,
3.1.2 v4l2_fops回调函数分析
下面逐一分析v4l2_fops操作函数集中的回调函数,至于被调用的v4l2_file_operations操作函数集,将以imx8视频输入通路为例在chapter 7进行说明
3.1.2.1 open函数
可见如果V4L2驱动程序不注册v4l2_file_operations.open回调函数,进行open操作会返回成功
3.1.2.2 release函数
可见如果V4L2驱动程序不注册v4l2_file_operations.release回调函数,进行release操作会返回成功
3.1.2.3 read函数
可见如果V4L2驱动程序不注册v4l2_file_operations.release回调函数,进行read操作会返回-ENODEV
3.1.2.4 write函数
可见如果V4L2驱动程序不注册v4l2_file_operations.write回调函数,进行write操作会返回-EINVAL
3.1.2.5 llseek函数
V4L2设备不支持llseek操作
3.1.2.6 unlocked_ioctl函数
可见如果V4L2驱动程序不注册v4l2_file_operations.unlocked_ioctl回调函数,进行ioctl操作会返回-ENOTTY
3.1.2.7 mmap函数
可见如果V4L2驱动程序不注册v4l2_file_operations.mmap回调函数,进行mmap操作会返回-ENODEV
3.1.2.8 get_unmapped_area函数
在当前使能MMU的场景中,get_unmapped_area回调函数设置为NULL
3.1.2.9 poll函数
可见如果V4L2驱动程序不注册v4l2_file_operations.poll回调函数,进行poll操作会返回DEFAULT_POLLMASK
3.2 V4L2 ioctl系统调用执行流程
根据Linux多媒体子系统01:从用户空间使用V4L2子系统,V4L2应用编程的主体内容就是执行一系列ioctl系统调用,所以本节对V4L2 ioctl系统调用的执行流程进行说明
3.2.1 确定驱动程序支持的ioctl操作
1. V4L2驱动程序需要为支持的ioctl操作在v4l2_ioctl_ops操作函数集中注册回调函数,v4l2_ioctl_ops结构体包含120多个条目,描述了每个可能的V4L2 ioctl操作
2. video_device结构体中的valid_ioctls位图用于标记当前设备支持的ioctl操作
说明:关于私有V4L2 ioctl操作
① 根据Linux设备驱动基础03:Linux字符设备驱动 chapter 3.6,Linux内核推荐的控制命令构成方式中,nr序号字段占8位,即有效值为0 ~ 255
② 在V4L2框架中,将序号192 ~ 255预留给驱动程序定义私有的ioctl操作,供驱动程序实现V4L2框架外特有的控制命令。需要注意的是,V4L2框架目前尚未用完序号0 ~ 191,这也为V4L2框架的扩展留下了空间
③ V4L2驱动定义私有V4L2 ioctl操作实例
文件:include/uapi/linux/omap3isp.h
omap3isp驱动程序定义了如下私有ioctl操作,并且在这些私有ioctl操作中使用了自定义数据结构。需要注意的是,私有ioctl操作需要在内核态和用户态同步定义。关于私有ioctl操作如何处理,详见下文分析
3. 在调用video_register_device函数注册video_device结构体之前,可以调用v4l2_disable_ioctl函数禁用指定的ioctl操作
说明1:V4L2框架通过提供禁用指定ioctl操作的功能,使得驱动程序可以只提供一个v4l2_ioctl_ops结构体就可以支持多个相关设备。对于某个设备不支持的ioctl操作,只需要在注册video_device结构体之前禁用即可
说明2:v4l2_disable_ioctl函数调用实例
文件:drivers/media/platform/vicodec/vicodec-core.c
vicodec驱动程序会注册stateful-encoder / stateful-decoder / stateless-decoder共3个video_device结构体,但是他们共用一个v4l2_ioctl_ops结构体。对于encoder或decoder不支持的ioctl操作,在注册video_device结构体之前调用v4l2_disable_ioctl函数予以禁用
说明3:v4l2_disable_ioctl函数禁用ioctl操作语义
① v4l2_disable_ioctl函数根据要禁用的ioclt操作序号,将valid_ioctls位图中的相应比特位置1
② 但是valid_ioctls位图最终是标记当前设备支持的ioctl操作,其中的语义转换由determine_valid_ioctls函数完成,详见下文分析
4. 如果驱动程序为video_device结构体设置了v4l2_ioctl_ops操作函数集,__video_register_device函数将会根据用户的设置判断驱动程序支持的ioctl操作,并将结果记录在video_device.valid_ioctls字段
从最终设置video_device.valid_ioctls位图的方法,可以看出v4l2_disable_ioctl函数禁用ioctl的操作语义,以及必须在注册video_device结构体之前进行禁用的原因
5. 后续通过is_valid_ioctl宏可以判断video_device是否支持指定的ioctl操作
3.2.2 video_ioctl2函数分析
3.2.2.1 概述
1. 如果驱动程序要使用v4l2_ioctl_ops操作函数集,则必须将v4l2_file_operations.unlocked_ioctl回调函数设置为video_ioctl2
说明:一般的V4L2驱动程序都会使用v4l2_ioctl_ops操作函数集,否则无法支持规范的ioctl操作
2. video_ioctl2函数的核心是video_usercopy和__video_do_ioctl函数,其中,
① video_usercopy函数提供处理ioctl控制命令的框架,并处理控制命令参数与操作结果在用户态和内核态之间的拷贝
② __video_do_ioclt函数则是处理ioctl控制命令的核心,该函数会将控制命令的处理传递给驱动程序注册的相应回调函数
3.2.2.2 v4l2_ioctls数组
video_usercopy和__video_do_ioctl函数中均会使用到v4l2_ioctls数组,因此先对其进行说明
1. v4l2_ioctls是一个v4l2_ioctl_info结构体类型数组,包含了当前V4L2框架中支持的所有ioctl控制命令的相关信息
2. v4l2_ioctl_info结构体类型如下,关于各标志位的具体作用,详见下文分析
3. v4l2_ioclts数组中通过IOCTL_INFO宏设置v4l2_ioctl_info结构体
说明:IOCTL_INFO宏设置实例
我们选择v4l2_ioctls数组中的4个实例,说明IOCTL_INFO宏的使用方式,
① VIDIOC_QUERYCAP控制命令
- 没有设置标志
② VIDIOC_REQBUFS控制命令
- 设置INFO_FL_PRIO标志,在执行前需要检查优先级
- 设置INFO_FL_QUEUE标志,属于queueing ioctl
③ VIDIOC_ENUM_FMT控制命令
- 设置INFO_FL_CLEAR标志,在执行前需要将用户态传入的v4l2_fmtdesc结构体从type字段之后的区域清零
④ VIDIOC_S_CTRL控制命令
- 设置INFO_FL_PRIO标志,在执行前需要检查优先级
- 设置INFO_FL_CTRL标志,该指令可以通过control handler处理
3.2.2.3 video_usercopy函数分析
说明1:关于kvmalloc函数
video_usercopy函数中通过kvmalloc函数动态分配内存,该函数首先尝试通过kmalloc函数分配物理连续内存;当失败时,再尝试通过vamlloc函数分配物理不连续内存,以此确保内存分配尽可能成功
说明2:ioctl控制命令参数指定区域清零逻辑分析
① 如上文所述,对于需要video_usercopy函数将用户态传递的参数指定区域清零的ioctl控制命令,在通过IOCTL_INFO宏设置v4l2_ioctl_info结构体时会使用INFO_FL_CLEAR宏设置flags字段
② 对于这类需要将用户态传递的参数指定区域清零的场景,一定是需要将参数从用户态拷贝到内核态的(也就是ioclt控制命令的方向一定包含_IOC_WRITE)。video_usercopy函数在从用户态拷贝参数时,只会拷贝不需要清零的部分(当然,内核态准备的buffer是足够存储完整参数的)
③ 对参数指定区域的清零操作直接对存储参数的内核态buffer进行,因为无需将用户态传递的参数全部拷贝到内核态之后再对指定区域清零,所以性能更好
说明3:check_array_args函数分析
从check_array_args函数的实现与调用可见,V4L2 ioctl控制命令的参数结构体中最多只有一个数组型参数
说明4:video_usercopy函数中只使用mbuf指针类型变量指向动态分配的内存,因此不可能既分配ioctl控制命令参数内存,又分配其中的数组型参数内存
可见这里有一个隐含的条件,就是在当前环境中,包含数组型参数的ioctl控制命令参数结构体本身不会超过128B
3.2.2.4 __video_do_ioctl函数分析
关于ioctl操作中的两级锁机制在chapter 2.2.1中已有说明,此处不再赘述
说明1:私有V4L2 ioctl操作的处理
① 如上文所述,驱动程序可以定义私有V4L2 ioctl操作(ioctl操作命令序号192 ~ 255),这些自定义ioctl操作肯定不在v4l2_ioctls数组中,此时需要驱动程序注册v4l2_ioctl_ops.vidioc_default回调函数进行处理
② v4l2_ioctl_ops.vidioc_default回调函数实例
文件:drivers/media/usb/uvc/uvc_v4l2.c
说明2:V4L2 ioctl系统调用执行流程数据关系图