上一篇我们讲解了如何编写基于V4L2的应用程序编写,本文主要讲解内核中V4L2架构,以及一些最重要的结构体、注册函数。
厂家在实现自己的摄像头控制器驱动时,总体上都遵循这个架构来实现,但是不同厂家、不同型号的SoC,具体的驱动实现仍然会有一些差别。
读者可以通过本文了解各个结构体与对应的摄像头模块、SoC上控制器模块、以及他们之间接口关系,并能够了解这些硬件模块与V4L2架构之间关系。
下一张我们基于瑞芯微rk3568来详细讲解具体V4L2的实现。
一、V4L2架构
V4L2子系统是Linux内核中关于Video(视频)设备的API接口,是V4L(Video for Linux)子系统的升级版本。
V4L(Video for Linux)是Linux内核中关于视频设备的API接口,出现于Linux内核2.1版本,经过修改bug和添加功能,Linux内核2.5版本推出了V4L2(Video for Linux Two)子系统,功能更多且更稳定。
V4L2子系统向上为虚拟文件系统提供了统一的接口,应用程序可通过虚拟文件系统访问Video设备。
V4L2子系统向下给Video设备提供接口,同时管理所有Video设备。
二、V4L2架构包括哪些设备
Video设备又分为主设备和从设备对于Camera来说,
主设备:
Camera Host控制器为主设备,负责图像数据的接收和传输,
从设备:
从设备为Camera Sensor,一般为I2C接口,可通过从设备控制Camera采集图像的行为,如图像的大小、图像的FPS等。
V4L2的主设备号是81,次设备号范围0~255这些次设备号又分为多类设备:
视频设备(次设备号范围0-63)
Radio(收音机)设备(次设备号范围64-127)
Teletext设备(次设备号范围192-223)
VBI设备(次设备号范围224-255)。
V4L2设备对应的设备节点有/dev/videoX、/dev/vbiX、/dev/radioX。本文只讨论视频设备,视频设备对应的设备节点是/dev/videoX,视频设备以高频摄像头或Camera为输入源,Linux内核驱动该类设备,接收相应的视频信息并处理。
V4L2框架的架构如下图所示:
user space:
应用程序主要通过libv4l库来操作摄像头
Applications: 也可以基于字符设备/dev/videoX自己编写应用程序
guvcview:用于调试usb摄像头(还有个软件cheese也可以)
v4l2 utilities: v4l2 的工具集(参考前面第3篇文章)
kernel space:
ensor、ISP、VIPP、CSI、CCI都为从设备
从dphy物理层获取视频数据册通过vb2子模块
CCI :主要是通过GPIO(供电、片选)、I2C(下发配置命令给sensor)实现配置sensorEHCI/OHCI:USB类型摄像头
hardware
SIC Controller:从dphy获取mipi协议帧I2C Controller:与sensor的i2c block通信
GPIO Controller:sensor通常需要供电或者片选
external device
sensror:
像头的接口主要有:USB,DVP.MIPI(CSI)
三、Linux内核中V4L2驱动代码
Linux系统中视频输入设备主要包括以下四个部分:
1.字符设备驱动:V4L2本身就是一个字符设备,具有字符设备所有的特性,暴露接口给用户空间;
2.V4L2驱动核心:主要是构建一个内核中标准视频设备驱动的框架,为视频操作提供统一的接口函数;
3.平台V4L2设备驱动:在V4L2框架下,根据平台自身的特性实现与平台相关的V4L2驱动部分,包括注册video_device和v4l2_device;
4.具体的sensor驱动:主要上电、提供工作时钟、视频图像裁剪、流IO开启等,实现各种设备控制方法供上层调用并注册v4l2_subdev。
V4L2核心源码位于drivers/media/v4l2-core,根据功能可以划分为四类:
由上图可知:
1.字符设备模块:由v4l2-dev.c实现,主要作用申请字符主设备号、注册class和提供video device注册注销等相关函数。
2.V4L2基础框架:由v4l2-device.c、v4l2-subdev.c、v4l2-fh.c、v4l2-ctrls.c等文件构建V4L2基础框架。
3.videobuf管理由videobuf2-core.c、videobuf2-dma-contig.c、videobuf2-dma-sg.c、videobuf2-memops.c、videobuf2-vmalloc.c、v4l2-mem2mem.c等文件实现,完成videobuffer的分配、管理和注销。
4.Ioctl框架:由v4l2-ioctl.c文件实现,构建V4L2 ioctl的框架。
四、结构体详解
V4L2中有几个最重要的几个结构体,v4l2_device、video_device、v4l2_subdev等。他们大致关系如下:
1.v4l2_device主设备
V4L2主设备实例使用struct v4l2_device结构体表示,v4l2_device是V4L2子系统的入口,管理着V4L2子系统的主设备和从设备;
v4l2_device用来描述一个v4l2设备实例,可以包含多个子设备,对应的是例如 I2C、CSI、MIPI 等设备,它们是从属于一个 V4L2 device 之下的;
简单设备可以仅分配这个结构体,但在大多数情况下,都会将这个结构体嵌入到一个更大的结构体中以提供v4l2框架的功能,比如struct isp_device;
需要与媒体框架整合的驱动必须手动设置dev->driver_data,指向包含v4l2_device结构体实例的驱动特定设备结构体。这可以在注册V4L2设备实例前通过dev_set_drvdata()函数完成。
同时必须设置v4l2_device结构体的mdev域,指向适当的初始化并注册过的media_device实例。
注册函数:
使用v4l2_device_register注册v4l2_device结构体.如果v4l2_dev->name为空,则它将被设置为从dev中衍生出的值(为了更加精确,形式为驱动名后跟bus_id)。
如果在调用v4l2_device_register前已经设置好了,则不会被修改。如果dev为NULL,则必须在调用v4l2_device_register前设置v4l2_dev->name。可以基于驱动名和驱动的全局atomic_t类型的实例编号,通过v4l2_device_set_name()设置name。
这样会生成类似ivtv0、ivtv1等名字。若驱动名以数字结尾,则会在编号和驱动名间插入一个破折号,如:cx18-0、cx18-1等。
dev参数通常是一个指向pci_dev、usb_interface或platform_device的指针,很少使其为NULL,除非是一个ISA设备或者当一个设备创建了多个PCI设备,使得v4l2_dev无法与一个特定的父设备关联。
使用v4l2_device_unregister卸载v4l2_device结构体。如果dev->driver_data域指向 v4l2_dev,将会被重置为NULL。主设备注销的同时也会自动注销所有子设备。如果你有一个热插拔设备(如USB设备),则当断开发生时,父设备将无效。
由于v4l2_device有一个指向父设备的指针必须被清除,同时标志父设备已消失,所以必须调用v4l2_device_disconnect函数清理v4l2_device中指向父设备的dev指针。v4l2_device_disconnect并不注销主设备,因此依然要调用v4l2_device_unregister函数注销主设备。
2. video_device
V4L2子系统使用v4l2_device结构体管理设备,设备的具体操作方法根据设备类型决定,
前面说过管理的设备分为很多种,
若是视频设备,则需要注册video_device结构体,并提供相应的操作方法。
对于视频设备Camera而言,Camera控制器可以视为主设备,接在Camera控制器上的摄像头可以视为从设备。
注册函数:
这个函数主要做四件事:
检查设备类型,赋予设备名称
寻找一个空闲的设备位置,寻找合适的主设备号和次设号
初始化字符设备,使用v4l2_device的v4l2_fops初始化video_device的fops,release函数等
注册字符设备,并生成/dev/videoX结点,注册subdev时也会调用这个接口
3. v4l2_subdev从设备
V4L2从设备使用struct v4l2_subdev结构体表示,该结构体用于对子设备进行抽象。
几乎所有的设备都有多个 IC 模块
它们可能是实体的(例如 USB 摄像头里面包含 ISP、sensor 等)
也可能是抽象的(如 USB 设备里面的抽象拓扑结构)
它们在 /dev 目录下面生成了多个设备节点,并且这些 IC 模块还创建了一些非 v4l2 设备:DVB、ALSA、FB、I2C 和输入设备。
通常情况下,这些IC模块通过一个或者多个 I2C 总线连接到主桥驱动上面,同时其它的总线仍然可用,这些 IC 就称为 ‘sub-devices’。
一个V4L2主设备可能对应多个V4L2从设备,所有主设备对应的从设备都挂到v4l2_device结构体的subdevs链表中。
对于视频设备,从设备就是摄像头,通常情况下是I2C设备,主设备可通过I2C总线控制从设备
例如控制摄像头的焦距、闪光灯等,同时使用 MIPI 或者 LVDS 等接口进行图像数据传输。
struct v4l2_subdev中包含的struct v4l2_subdev_ops是一个完备的操作函数集,用于对接各种不同的子设备,比如video、audio、sensor等;
同时还有一个核心的函数集struct v4l2_subdev_core_ops,提供更通用的功能。子设备驱动根据设备特点实现该函数集中的某些函数即可。
使用v4l2_subdev_init初始化v4l2_subdev结构体。然后必须用一个唯一的名字初始化subdev->name,同时初始化模块的owner域。
若从设备是I2C设备,则可使用v4l2_i2c_subdev_init函数进行初始化,该函数内部会调用v4l2_subdev_init,同时设置flags、owner、dev、name等成员。
从设备必须向V4L2子系统注册v4l2_subdev结构体,使用v4l2_device_register_subdev注册,使用v4l2_device_unregister_subdev注销。
V4L2从设备驱动都必须有一个v4l2_subdev结构体。这个结构体可以单独代表一个简单的从设备,也可以嵌入到一个更大的结构体中,与更多设备状态信息保存在一起。通常有一个下级设备结构体(比如:i2c_client)包含了内核创建的设备数据。
建议使用v4l2_set_subdevdata()将这个结构体的指针保存在v4l2_subdev的私有数据域(dev_priv)中。可以更方便的通过v4l2_subdev找到实际的低层总线特定设备数据。
对于常用的i2c_client结构体,i2c_set_clientdata函数可用于保存一个v4l2_subdev指针,i2c_get_clientdata可以获取一个v4l2_subdev指针;对于其他总线可能需要使用其他相关函数。
主设备驱动中也应保存每个子设备的私有数据,比如一个指向特定主设备的各设备私有数据的指针。为此v4l2_subdev结构体提供主设备私有数据域(host_priv),并可通过v4l2_get_subdev_hostdata和 v4l2_set_subdev_hostdata访问。
每个v4l2_subdev都包含子设备驱动需要实现的函数指针(如果对此设备不适用,可为NULL),具体在v4l2_subdev_ops结构体当中。
由于子设备可完成许多不同的工作,而在一个庞大的函数指针结构体中通常仅有少数有用的函数实现其功能肯定不合适。
所以,函数指针根据其实现的功能被分类,每一类都有自己的函数指针结构体,如v4l2_subdev_core_ops、v4l2_subdev_audio_ops、v4l2_subdev_video_ops等等。
顶层函数指针结构体包含了指向各类函数指针结构体的指针,如果子设备驱动不支持该类函数中的任何一个功能,则指向该类结构体的指针为NULL。
使用v4l2_device_register_subdev注册从设备后,就可以调用v4l2_subdev_ops中的方法了。
可以通过v4l2_subdev直接调用,也可以使用内核提供的宏定义v4l2_subdev_call间接调用某一个方法。
若要调用多个从设备的同一个方法,则可使用v4l2_device_call_all宏定义。
如果子设备需要通知它的v4l2_device主设备一个事件,可以调用**v4l2_subdev_notify(sd,notification, arg)**。
这个宏检查是否有一个notify回调被注册,如果没有,返回-ENODEV。否则返回 notify调用结果。notify回调函数由主设备提供。
使用v4l2_subdev的好处在于它是一个通用结构体,且不包含任何底层硬件信息。
所有驱动可以包含多个I2C总线的从设备,但也有从设备是通过GPIO控制。这个区别仅在配置设备时有关系,一旦子设备注册完成,对于v4l2子系统来说就完全透明了。
4. v4l2_fh:
文件访问控制
5. v4l2_ctrl_handler:
控制模块,提供子设备(主要是 video 和 ISP 设备)在用户空间的特效操作接口
6. media_device:
用于运行时数据流的管理,嵌入在 V4L2 device 内部
五、 video_device、v4l2_device和v4l2_subdev的关系举例
下面以我们手机的摄像头来举例:
假定一款CMOS摄像头,有两个接口:一个是摄像头接口(数据),一个是I2C接口(控制命令)
摄像头接口负责传输图像数据,I2C接口负责传输控制信息,所以又可以将CMOS摄像头看作是一个I2C模块
在一款SoC芯片上面,摄像头相关的有摄像头控制器、摄像头接口、I2C总线SOC上可以有多个摄像头控制器,多个摄像头接口,多个I2C总线摄像头控制器负责接收和处理摄像头数据,摄像头接口负责传输图像数据,I2C总线负责传输控制信息
对于手机而言,一般都有两个摄像头:一个前置摄像头,一个后置摄像头
如下图所示:
我们可以选择让控制器去操作哪一个摄像头(可以使用某个gpio供电,通过电平来选择摄像头),这就做到了使用一个摄像头控制器来控制多个摄像头,这就是多路复用
我们回到V4L2来,再来谈v4l2_device和v4l2_subdev:
v4l2_device表示一个v4l2实例,在V4L2驱动中,使用v4l2_device来表示摄像头控制器
使用v4l2_subdev来表示具体的某一个摄像头的I2C控制模块,进而通过其控制摄像头
v4l2_device里有一个v4l2_subdev链表,可以选择v4l2_device去控制哪一个v4l2_subdevsubdev的设计目的是为了多路复用,就是用一个v4l2_device可以服务多个v4l2_subdev
然而某些驱动是没有v4l2_subdev,只有video_device
我们用一张图来总结设备之间关系:
video_device是一个字符设备,video_device内含一个cdev
v4l2_device是一个v4l2实例,嵌入到video_device中
v4l2_device维护者一个链表管理v4l2_subdev,v4l2_subdev表示摄像头的I2C控制模块
主设备可通过v4l2_subdev_call的宏调用从设备提供的方法,反过来从设备可以调用主设备的notify方法通知主设备某些事件发生了。
核心层(core)负责注册字符设备,然后提供video_device对象和相应的注册接口给硬件相关层使用;
硬件相关层需要分配一个video_device并设置它,然后向核心层注册,核心层会为其注册字符设备并且创建设备节点(/dev/videox);
同时硬件相关层还需要分配和设置相应的v4l2_device和v4l2_subdev,其中v4l2_device的一个比较重要的意义就是管理v4l2_subdev,当然有一些驱动并不需要实现v4l2_subdev,此时v4l2_device的意义就不是很大了;
当应用层通过/dev/video来操作设备的时候,首先会来到V4L2的核心层,核心层通过注册进的video_device的回调函数调用相应的操作函数,video_device可以直接操作硬件或者是通过v4l2_subdev来操作硬件。
一口君再把各个结构体与各回调函数之间关系汇总到下面这个图里(rk3568):
主要架构部分Linux内核已经实现了,Camera控制器驱动,厂家一般都会实现,对于一般驱动工程师来说,我们只需要实现子设备驱动即可。
六、videobuf2
从数据流角度来分析,V4L2框架可以分成两个部分看:控制流+数据流:
控制流主要由v4l2_subdev的回调函数实现(一般由摄像头厂商提供),主要用于控制摄像
数据流的部分就是video buffer,驱动部分通常由SoC厂商提供(比如瑞芯微rk3568平台,对应到rkisp_rawrd0_m、rkisp_rawrd2_s子模块)。
V4L2的buffer管理是通过videobuf2来完成的,它充当用户空间和驱动之间的中间层,并提供low-level,模块化的内存管理功能;
回顾一下获取摄像头视频流的主要步骤:
要获取图像信息需要执行VIDIOC_DQBUF、VIDIOC_QBUF命令。
瑞芯微rk3568平台videobuf2相关结构体和ops回调函数关系如下:
其中struct rkisp_device是瑞芯微3568平台用于管理Camera控制器的最重要的结构体
struct rkisp_capture_device 对应拓扑结构中的模块rkisp_rawrd0_m 、rkisp_rawrd2_s 。
该模块是一个video设备,用于获取原始图像信息,所以在struct rkisp_vdev_node vnode中包含了struct vb2_queue buf_queue、struct video_device vdev
struct vb2_queue中的回调函数struct vb2_mem_ops *mem_ops、struct vb2_buf_ops *buf_ops、struct vb2_ops*ops就是videobuf2驱动。
videobuf2驱动部分相关结构体如下:
上图大体包含了videobuf2的框架;
vb2_queue:核心的数据结构,用于描述buffer的队列,其中struct vb2_buffer *bufs[]是存放buffer节点的数组,该数组中的成员代表了vb2 buffer,并将在queued_list和done_list两个队列中进行流转;
struct vb2_buf_ops:buffer的操作函数集,由驱动来实现,并由框架通过call_bufop宏来对特定的函数进行调用;
struct vb2_mem_ops:内存buffer分配函数接口,buffer类型分为三种:1)虚拟地址和物理地址都分散,可以通过dma-sg来完成;2)物理地址分散,虚拟地址连续,可以通过vmalloc分配;3)物理地址连续,可以通过dma-contig来完成;三种类型也vb2框架中都有实现,框架可以通过call_memop来进行调用;
struct vb2_ops:vb2队列操作函数集,由驱动来实现对应的接口,并在框架中通过call_vb_qop宏被调用;
调用流程:
下面是VIDIOC_DQBUF命令执行的 log【在函数vb2_core_dqbuf入口调用stack_dump()】:
VIDIOC_QBUF命令执行的log:
七、v4l2拓扑结构
关于如何使用设备树节点描述拓扑结构,后续文章会详细讲解。
文中各种mipi技术文档,后台回复关键字:mipi
后面还会继续更新几篇Camera文章,
建议大家订阅本专题!
也可以后台留言,加一口君好友yikoupeng,
拉你进高质量技术交流群。
领取专属 10元无门槛券
私享最新 技术干货