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函数,是该函数的典型用法