Linux多媒体子系统02:V4L2核心框架分析(部分)

news2024/11/18 4:30:20

目录

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的视频输入通路,并没有使用ispdwe

说明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_getv4l2_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,可以在驱动程序的disconnectremove回调函数中调用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字段的维护

首先需要注意的是,minornumindex字段是独立维护的,下面逐一进行说明,此处假设不使能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_getvideo_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_drvdatavideo_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系统调用执行流程数据关系图

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/455448.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

15.Java多线程

目录 1. Java基本介绍 2. JDK下载安装及其环境配置 3. 一个简单的java程序 4. Eclipse基本使用、数据类型、运算符 5. 控制语句(if、switch、for、while、foreach) 6. Java数组 7. Java字符串对象(String|StringBuffer|StringBuilder|StringJoiner…

教你如何免费使用ChatGPT 4?(国内可以直接访问,不用魔法)

目录 一. 内容介绍 二. 功能介绍 三. 优势 四. 版本比较 五. 国内试用方法 内容介绍: ChatGPT 4是由OpenAI开发的最新一代大型语言模型,其采用了GPT-3.5的技术架构,是目前全球最强大的通用AI模型之一。ChatGPT 4拥有极高的语言处理能力和…

SD卡恢复怎么做?内存卡数据恢复,3个方法!

案例:sd卡怎么恢复? 【我的sd卡用了快一年了,里面存储了很多非常重要的文件,但不知道为什么我今天将它插入电脑后,很多文件都无法显示了,大家有什么好的方法可以恢复sd卡吗?感谢回答&#xff0…

【翻译一下官方文档】之uniapp的导航条设置

目录 uni.setNavigationBarTitle(OBJECT) uni.setNavigationBarColor(OBJECT) uni.hideHomeButton(OBJECT) uni.setNavigationBarTitle(OBJECT) 动态设置当前页面的标题。 OBJECT参数说明 参数类型必填说明titleString是页面标题successFunction否接口调用成功的回调函数fai…

安虚拟机详细教程 VMware虚拟机与主机之间不能复制粘贴及拖拽问题

VMware虚拟机中安装Ubuntu18.04(linux发行版)【超详细图文教程】_vmware安装ubuntu18.04__7270的博客-CSDN博客 1. 查看vmware Tools是否安装 打开虚拟机 ,点击上方导航栏 ‘虚拟机’ 查看VMware Tools是否安装,如果未安装&#…

Linux和shell命令第一节课

windows开发 linux服务 区块链, 稳定,安全,可移植性,低资源消耗,开源软件---windows付费 linux就是操作系统,网络服务,移动设备,嵌入式系统,计算器服务器 除个人桌面…

Sentinel:服务限流

文章目录 创建工程测试工程流控熔断热点授权规则系统规则SentinelResource 配置持久化执行流程 创建工程测试工程 1.导入依赖 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><v…

开放原子训练营第三期:RT-Thread 学习有感

介绍 前几天有幸收到C站的训练营学习邀请&#xff0c;了解到这两天即将举行的开放原子 RTT 训练营。博主算是一名嵌入式方向的小白&#xff0c;主要还是在裸机上进行开发&#xff0c;但对嵌入式的操作系统和实时系统很感兴趣。在这次学习训练营中借助一些学习示例&#xff0c;…

【Linux】 1.2基本指令-part 2

文章目录 07. man指令08.cp指令(copy)&#xff08;重要&#xff09;09.mv指令(move)&#xff08;重要&#xff09;10.cat指令- 补充&#xff1a;echo 与 >11.more指令12.less指令&#xff08;重要&#xff09;13.head指令14.tail指令- 补充&#xff1a;管道 07. man指令 ma…

CV不存在了?体验用Segment Anything Meta分割清明上河图

目录 1 Facebook抠图神器2 本地版SAM配置3 Web版SAM体验4 总结 1 Facebook抠图神器 在图像处理与计算机视觉领域&#xff0c;图像分割(image segmentation)是在像素级别将一个完整图像划分为若干具有特定语义区域(region)或对象(object)的过程。每个分割区域是一系列拥有相似特…

IJKPLAYER源码分析-常用API

前言 本文简要介绍IJKPLAYER的几个常用API&#xff0c;以API使用的角度&#xff0c;来审视其内部运作原理。这里以iOS端直播API调用切入。 调用流程 init 创建播放器实例后&#xff0c;会先调用init方法进行初始化&#xff1a; - (IJKFFMediaPlayer *)init {self [super ini…

《深入浅出Embedding》随笔

ChatGPT的核心运行机制或许是Transformer&#xff0c; ChatGPT的核心数据机制或许就是嵌入&#xff08;Embedding&#xff09;了。什么是Embedding呢&#xff1f;了解Embedding可以为我们的软件研发工作带来哪些帮助呢&#xff1f;鉴于此&#xff0c;老码农阅读了《深入浅出Emb…

前端切图仔入门Docker,三分钟上线自己的博客平台

依稀记得2022年趁某平台优惠买了台云服务器&#xff0c;周未准备安装MySQL数据库&#xff0c;两天时间卡在MySQL环境配置上&#xff0c;实在是折磨一气之下把服务器给退了。 要是我早一点学会Docker&#xff0c;我的博客估计已上线一年啦&#xff01;前端切图仔学会Docker&…

AI魔法秀:D-ID助你打造视频虚拟数字人

随着ChatGPT的各种玩法&#xff0c;写文章、做PPT、编程、修bug等&#xff0c;大家都玩的不亦说乎&#xff0c;可以说真的给家人们提升的效率很高&#xff0c;最近个人尝试着制作一个虚拟数字人&#xff0c;也是一个玩法&#xff0c;可以帮助很多中小企业做企业文化宣讲或者是产…

闲谈【Stable-Diffusion WEBUI】的插件:美不美?交给AI打分

文章目录 &#xff08;零&#xff09;前言&#xff08;一&#xff09;咖啡店艺术评价&#xff08;Cafe Aesthetic&#xff09; &#xff08;零&#xff09;前言 本篇主要提到了WEBUI的Cafe Aesthetic插件&#xff0c;这是一个相对独立的插件&#xff0c;单独标签页&#xff0c;…

周杰伦代言的蕉下,3年半广告宣传费超10亿,全靠营销?

五一假期将至&#xff0c;各地即将迎来旅游小热潮&#xff0c;不少游客也开始为自己的出行准备攻略。随着露营、徒步等城市户外运动的兴起&#xff0c;防晒理念“再度升温”&#xff0c;靠卖小黑伞起家的蕉下瞄准年轻世代消费者&#xff0c;又在疯狂收割“防晒焦虑”。 去年4月…

【JavaWeb】JavaScript

1、JavaScript 介绍 Javascript 语言诞生主要是完成页面的数据验证。因此它运行在客户端&#xff0c;需要运行浏览器来解析执行 JavaScript 代码。 JS 是 Netscape 网景公司的产品&#xff0c;最早取名为 LiveScript;为了吸引更多 java 程序员。更名为 JavaScript。 JS 是弱…

排序 - 冒泡排序(Bubble Sort)

文章目录 冒泡排序介绍冒泡排序实现复杂度和稳定性冒泡排序时间复杂度冒泡排序稳定性 代码实现核心&注意结尾 每日一道算法提高脑力&#xff0c;今天是第一天&#xff0c;来个最简单的算法–冒泡排序。 冒泡排序介绍 它是一种较简单的排序算法。它会遍历若干次要排序的数列…

对话庄表伟老师-文字实录

我内心有一套价值观&#xff0c;有一套世界观&#xff0c;它是一个完整的整体&#xff0c;无论我做任何的事情&#xff0c;工作也好、生活也好、学习也好、去做社区也好、或者是结识朋友也好、去聊天也好&#xff0c;背后的价值观在内心都是一整套的&#xff0c;互相之间是不会…

Python学习之简易图片浏览器

俗话说实践是学习最有效的方法。最近在学习python&#xff0c;于是就参考着各类文章&#xff0c;也倒腾了一个简易图片浏览器&#xff0c;效果图如下&#xff1a; 整个浏览器分为左右两侧&#xff0c;左侧是地址栏图片文件列表&#xff1b;右侧则是图片显示区域。 左侧地址栏有…