可以看到v4l2_device中有一个v4l2_subdev的链表,v4l2_device的主要目的时用来管理v4l2_subdev
v4l2_subdev
struct v4l2_subdev {
struct list_head list;
struct v4l2_device * v4l2_dev;
const struct v4l2_subdev_ops * ops;
} ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
v4l2_subdev中有一个v4l2_subdev_ops,实现了一系列的操作,供v4l2_device调用
struct v4l2_subdev_ops {
const struct v4l2_subdev_core_ops * core;
const struct v4l2_subdev_tuner_ops * tuner;
const struct v4l2_subdev_audio_ops * audio;
const struct v4l2_subdev_video_ops * video;
const struct v4l2_subdev_vbi_ops * vbi;
const struct v4l2_subdev_ir_ops * ir;
const struct v4l2_subdev_sensor_ops * sensor;
} ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
struct v4l2_subdev_core_ops {
. . .
int ( * s_config) ( struct v4l2_subdev * sd, int irq, void * platform_data) ;
int ( * init) ( struct v4l2_subdev * sd, u32 val) ;
int ( * s_gpio) ( struct v4l2_subdev * sd, u32 val) ;
int ( * g_ctrl) ( struct v4l2_subdev * sd, struct v4l2_control * ctrl) ;
int ( * s_ctrl) ( struct v4l2_subdev * sd, struct v4l2_control * ctrl) ;
int ( * s_std) ( struct v4l2_subdev * sd, v4l2_std_id norm) ;
long ( * ioctl) ( struct v4l2_subdev * sd, unsigned int cmd, void * arg) ;
. . .
} ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
struct v4l2_subdev_video_ops {
. . .
int ( * enum_fmt) ( struct v4l2_subdev * sd, struct v4l2_fmtdesc * fmtdesc) ;
int ( * g_fmt) ( struct v4l2_subdev * sd, struct v4l2_format * fmt) ;
int ( * try_fmt) ( struct v4l2_subdev * sd, struct v4l2_format * fmt) ;
int ( * s_fmt) ( struct v4l2_subdev * sd, struct v4l2_format * fmt) ;
int ( * cropcap) ( struct v4l2_subdev * sd, struct v4l2_cropcap * cc) ;
int ( * g_crop) ( struct v4l2_subdev * sd, struct v4l2_crop * crop) ;
int ( * s_crop) ( struct v4l2_subdev * sd, struct v4l2_crop * crop) ;
int ( * g_parm) ( struct v4l2_subdev * sd, struct v4l2_streamparm * param) ;
int ( * s_parm) ( struct v4l2_subdev * sd, struct v4l2_streamparm * param) ;
. . .
} ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
2.2 V4L2提供的注册接口
video_device
注册
int video_register_device ( struct video_device * vdev, int type, int nr) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
注销
void video_unregister_device ( struct video_device * vdev) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
v4l2_device
注册
int v4l2_device_register ( struct device * dev, struct v4l2_device * v4l2_dev) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
注销
void v4l2_device_unregister ( struct v4l2_device * v4l2_dev) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
v4l2_subdev
注册
int v4l2_device_register_subdev ( struct v4l2_device * v4l2_dev,
struct v4l2_subdev * sd) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
注销
void v4l2_device_unregister_subdev ( struct v4l2_subdev * sd) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
三、源码剖析
3.1 V4L2驱动模板
此示例中并没有设计到v4l2_subdev,这部分将会在后面分析具体的驱动中出现
#include <...>
static struct video_device* video_dev;
static struct v4l2_device v4l2_dev;
static const struct v4l2_file_operations video_dev_fops = {
. owner = THIS_MODULE,
. release = vdev_close,
. read = vdev_read,
. poll = vdev_poll,
. ioctl = video_ioctl2,
. mmap = vdev_mmap,
} ;
static const struct v4l2_ioctl_ops video_dev_ioctl_ops = {
. vidioc_querycap = vidioc_querycap,
. vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
. vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap,
. vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap,
. vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
. vidioc_reqbufs = vidioc_reqbufs,
. vidioc_querybuf = vidioc_querybuf,
. vidioc_qbuf = vidioc_qbuf,
. vidioc_dqbuf = vidioc_dqbuf,
. vidioc_enum_input = vidioc_enum_input,
. vidioc_g_input = vidioc_g_input,
. vidioc_s_input = vidioc_s_input,
. vidioc_streamon = vidioc_streamon,
. vidioc_streamoff = vidioc_streamoff,
} ;
static int __init video_init ( void )
{
video_dev = video_device_alloc ( ) ;
video_dev-> fops = & video_dev_fops;
video_dev-> ioctl_ops = & video_dev_ioctl_ops;
video_dev-> release = video_device_release;
video_dev-> tvnorms = V4L2_STD_525_60;
video_dev-> current_norm = V4L2_STD_NTSC_M;
v4l2_device_register ( video_dev-> dev, & v4l2_dev) ;
video_dev-> v4l2_dev = & video_dev;
video_register_device ( video_dev, VFL_TYPE_GRABBER, - 1 ) ;
return 0 ;
}
static void __exit video_exit ( void )
{
video_unregister_device ( video_dev) ;
v4l2_device_unregister ( & v4l2_dev) ;
video_device_release ( video_dev) ;
}
module_init ( video_init) ;
module_exit ( video_exit) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
如果你熟悉v4l2应用编程的话,你应该知道v4l2有许多ioctl操作,上面的模板就实现了很多ioctl操作
3.2 V4L2源码剖析
下面我们来分析分析源码
在上面的video_init
中,我们分配并设置了video_dev
,注册了v4l2_device
(v4l2_device_register),然后向v4l2核心层注册video_device
(video_register_device)
我们先来看v4l2_device_register
int v4l2_device_register ( struct device * dev, struct v4l2_device * v4l2_dev)
{
INIT_LIST_HEAD ( & v4l2_dev-> subdevs) ;
spin_lock_init ( & v4l2_dev-> lock) ;
dev_set_drvdata ( dev, v4l2_dev) ;
. . .
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
从源码中可以看出v4l2_device_register
并没有做什么事,只是初始化链表,自旋锁,还有设置数据,这函数并不是我们的重点
下面来仔细分析video_register_device
int video_register_device ( struct video_device * vdev, int type, int nr)
{
vdev-> cdev = cdev_alloc ( ) ;
vdev-> cdev-> ops = & v4l2_fops;
cdev_add ( vdev-> cdev, MKDEV ( VIDEO_MAJOR, vdev-> minor) , 1 ) ;
dev_set_name ( & vdev-> dev, "%s%d" , name_base, vdev-> num) ;
device_register ( & vdev-> dev) ;
video_device[ vdev-> minor] = vdev;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
可以看到这个函数会为video_device分配一个cdev,然后设置fops,向内核注册字符设备,再者生成设备节点
然后设置video_device全局数组,video_device一个全局数组
static struct video_device * video_device[ VIDEO_NUM_DEVICES] ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
保存着注册的video_device
接下来看一下其中设置的fops(v4l2_fops)
static const struct file_operations v4l2_fops = {
. owner = THIS_MODULE,
. read = v4l2_read,
. write = v4l2_write,
. open = v4l2_open,
. get_unmapped_area = v4l2_get_unmapped_area,
. mmap = v4l2_mmap,
. ioctl = v4l2_ioctl,
. release = v4l2_release,
. poll = v4l2_poll,
. llseek = no_llseek,
} ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
这个是video_device中的字符设备对应的fops,应用层发生系统调用会率先调用到这里,我们来好好看一看这些调用
v4l2_open
static int v4l2_open ( struct inode * inode, struct file * filp)
{
struct video_device * vdev;
vdev = video_devdata ( filp) ;
if ( vdev-> fops-> open)
ret = vdev-> fops-> open ( filp) ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
从这个函数可以看到,发生系统调用首先来到v4l2核心层的字符设备,然后再回调到对应的video_device,video_device在前面已经实现了v4l2_file_operations
和v4l2_ioctl_ops
一系列回调
v4l2_ioctl
V4L2的应用编程会有非常多的ioctl,会先调用到此处
static int v4l2_ioctl ( struct inode * inode, struct file * filp,
unsigned int cmd, unsigned long arg)
{
struct video_device * vdev = video_devdata ( filp) ;
return vdev-> fops-> ioctl ( filp, cmd, arg) ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
下面来看一看video_device怎么实现ioctl
static const struct v4l2_file_operations video_dev_fops = {
. owner = THIS_MODULE,
. release = vdev_close,
. read = vdev_read,
. poll = vdev_poll,
. ioctl = video_ioctl2,
. mmap = vdev_mmap,
} ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
从上面驱动程序的编写,我们可以知道video_device对应ioctl就是video_ioctl2,这个函数是内核提供的,我们看一看这个函数的内容
long video_ioctl2 ( struct file * file,
unsigned int cmd, unsigned long arg)
{
__video_do_ioctl ( file, cmd, parg) ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
static long __video_do_ioctl ( struct file * file,
unsigned int cmd, void * arg)
{
struct video_device * vfd = video_devdata ( file) ;
const struct v4l2_ioctl_ops * ops = vfd-> ioctl_ops;
switch ( cmd) {
case VIDIOC_QUERYCAP:
ops-> vidioc_querycap ( file, fh, cap) ;
case VIDIOC_ENUM_FMT:
ops-> vidioc_enum_fmt_vid_cap ( file, fh, f) ;
. . .
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 可以看出,最终会调用到video_device实现的v4l2_ioctl_ops
static const struct v4l2_ioctl_ops video_dev_ioctl_ops = {
. vidioc_querycap = vidioc_querycap,
. vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
. vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap,
. vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap,
. vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
. vidioc_reqbufs = vidioc_reqbufs,
. vidioc_querybuf = vidioc_querybuf,
. vidioc_qbuf = vidioc_qbuf,
. vidioc_dqbuf = vidioc_dqbuf,
. vidioc_enum_input = vidioc_enum_input,
. vidioc_g_input = vidioc_g_input,
. vidioc_s_input = vidioc_s_input,
. vidioc_streamon = vidioc_streamon,
. vidioc_streamoff = vidioc_streamoff,
} ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 所以系统调用最先都会调用到字符设备的fops,然后经过v4l2核心层最终调用到video_device这里
关于v4l2的驱动框架就分析到这里,关于更加详细的实现v4l2驱动,将在后续文章中通过实例讲解
data-report-view="{"mod":"1585297308_001","spm":"1001.2101.3001.6548","dest":"http://iyenn.com/rec/2146286.html","extend1":"pc","ab":"new"}">>
id="article_content" class="article_content clearfix">
id="content_views" class="markdown_views prism-atom-one-light">
深入学习Linux摄像头系列
深入学习Linux摄像头(一)v4l2应用编程
深入学习Linux摄像头(二)v4l2驱动框架
深入学习Linux摄像头(三)虚拟摄像头驱动分析
深入学习Linux摄像头(四)三星平台fimc驱动详解
深入学习Linux 摄像头(二)v4l2驱动框架
class="toc">
文章目录
一、V4L2 框架
1.1 相关对象
v4l2驱动框架主要的对象有video_device
、v4l2_device
、v4l2_subdev
、videobuf
video_device
一个字符设备,为用户空间提供设备节点(/dev/videox),提供系统调用的相关操作(open、ioctl…)
v4l2_device
嵌入到video_device中,表示一个v4l2设备的实例
v4l2_subdev
依附在v4l2_device之下,并表示一个v4l2设备的子设备,一个v4l2_devide下可以有多个sub_device
videobuf
v4l2驱动的缓存管理
下面有必要对v4l2_device和v4l2_subdev来进行说明
subdev的设计目的是为了多路复用,就是用一个v4l2_device可以服务多个v4l2_subdev
下面以我们手机的摄像头来举例
CMOS摄像头
对于一款CMOS摄像头来说,有两个接口,一个是摄像头接口
,一个是I2C接口
摄像头接口负责传输图像数据,I2C接口负责传输控制信息,所以又可以将CMOS摄像头看作是一个I2C模块
如下图所示
芯片片上资源
在一款芯片上面,摄像头相关的有摄像头控制器
、摄像头接口
、I2C总线
SOC上可以有多个摄像头控制器,多个摄像头接口,多个I2C总线
摄像头控制器负责接收和处理摄像头数据,摄像头接口负责传输图像数据,I2C总线负责传输控制信息
如下图所示
对于手机而言,一般都有两个摄像头,一个前置摄像头,一个后置摄像头,其接发下图所示
我们可以选择让控制器去操作哪一个摄像头,这就做到了使用一个摄像头控制器来控制多个摄像头,这就是多路复用
上面说要使用一个摄像头控制器去操作多个摄像头,这是我们的目的,那么在软件中是怎么实现的呢?
我们回到V4L2来,再来谈v4l2_device
和v4l2_subdev
上面我们介绍到v4l2_device
表示一个v4l2实例
在V4L2驱动中,使用v4l2_device
来表示摄像头控制器
使用v4l2_subdev
来表示具体的某一个摄像头的I2C控制模块
,进而通过其控制摄像头
v4l2_device
里有一个v4l2_subdev
链表,可以选择v4l2_device
去控制哪一个v4l2_subdev
相信到此,你对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控制模块
1.2 V4L2 框架
在理清楚V4L2中的主要对象后,我们来介绍V4L2的框架
在介绍V4L2驱动框架前,我们先回顾一下简单的字符设备 的编写
分配一个字符设备(cdev) 设置一个fops 注册字符设备
复杂的字符设备
对于复杂的字符设备,内核都是采用分层的方法,一般分驱动核心层 还有硬件相关层
核心层 会帮你完成字符设备的分配,fops的设置,注册字符设备,并向硬件相关层提供一个相应的对象和注册接口
硬件相关层 则需要分配相应的对象,设置对象和对象的fops,并注册到核心层中
当应用层发生系统调用,会先来到核心层,核心层再通过回调函数调用到硬件相关层的驱动
对于V4L2的驱动框架也是如此,可分为V4L2驱动核心层 和硬件相关层
下面先用一张图来总结大致V4L2的驱动框架
从图中可以看出V4L2分为核心层还有硬件相关层
核心层负责注册字符设备,然后提供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
来操作硬件
二、V4L2的数据结构
介绍完V4L2的驱动框架后,来看一看内核中各对象的数据结构
2.1 V4L2主要对象的数据结构
video_device
struct video_device
{
struct cdev * cdev;
struct v4l2_device * v4l2_dev;
const struct v4l2_file_operations * fops;
const struct v4l2_ioctl_ops * ioctl_ops;
} ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
可以看到video_device中含有一个cdev还有v4l2_device,此外还有fops和ioctl_ops,从应用层进行系统调用会经过v4l2的核心层回调到这里
其中v4l2_file_operations
和v4l2_ioctl_ops
如下
struct v4l2_file_operations {
struct module * owner;
ssize_t ( * read) ( struct file * , char __user * , size_t, loff_t * ) ;
ssize_t ( * write) ( struct file * , const char __user * , size_t, loff_t * ) ;
unsigned int ( * poll) ( struct file * , struct poll_table_struct * ) ;
long ( * ioctl) ( struct file * , unsigned int , unsigned long ) ;
long ( * unlocked_ioctl) ( struct file * , unsigned int , unsigned long ) ;
unsigned long ( * get_unmapped_area) ( struct file * , unsigned long ,
unsigned long , unsigned long , unsigned long ) ;
int ( * mmap) ( struct file * , struct vm_area_struct * ) ;
int ( * open) ( struct file * ) ;
int ( * release) ( struct file * ) ;
} ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
熟悉v4l2应用编程的应该都知道v4l2有很多ioctl操作,具体实现都在这里
struct v4l2_ioctl_ops {
int ( * vidioc_querycap) ( struct file * file, void * fh, struct v4l2_capability * cap) ;
int ( * vidioc_reqbufs) ( struct file * file, void * fh, struct v4l2_requestbuffers * b) ;
int ( * vidioc_querybuf) ( struct file * file, void * fh, struct v4l2_buffer * b) ;
int ( * vidioc_qbuf) ( struct file * file, void * fh, struct v4l2_buffer * b) ;
int ( * vidioc_dqbuf) ( struct file * file, void * fh, struct v4l2_buffer * b) ;
int ( * vidioc_streamon) ( struct file * file, void * fh, enum v4l2_buf_type i) ;
int ( * vidioc_streamoff) ( struct file * file, void * fh, enum v4l2_buf_type i) ;
. . .
} ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
v4l2_device
struct v4l2_device {
struct list_head subdevs;
. . .
} ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
可以看到v4l2_device中有一个v4l2_subdev的链表,v4l2_device的主要目的时用来管理v4l2_subdev
v4l2_subdev
struct v4l2_subdev {
struct list_head list;
struct v4l2_device * v4l2_dev;
const struct v4l2_subdev_ops * ops;
} ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
v4l2_subdev中有一个v4l2_subdev_ops,实现了一系列的操作,供v4l2_device调用
struct v4l2_subdev_ops {
const struct v4l2_subdev_core_ops * core;
const struct v4l2_subdev_tuner_ops * tuner;
const struct v4l2_subdev_audio_ops * audio;
const struct v4l2_subdev_video_ops * video;
const struct v4l2_subdev_vbi_ops * vbi;
const struct v4l2_subdev_ir_ops * ir;
const struct v4l2_subdev_sensor_ops * sensor;
} ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
struct v4l2_subdev_core_ops {
. . .
int ( * s_config) ( struct v4l2_subdev * sd, int irq, void * platform_data) ;
int ( * init) ( struct v4l2_subdev * sd, u32 val) ;
int ( * s_gpio) ( struct v4l2_subdev * sd, u32 val) ;
int ( * g_ctrl) ( struct v4l2_subdev * sd, struct v4l2_control * ctrl) ;
int ( * s_ctrl) ( struct v4l2_subdev * sd, struct v4l2_control * ctrl) ;
int ( * s_std) ( struct v4l2_subdev * sd, v4l2_std_id norm) ;
long ( * ioctl) ( struct v4l2_subdev * sd, unsigned int cmd, void * arg) ;
. . .
} ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
struct v4l2_subdev_video_ops {
. . .
int ( * enum_fmt) ( struct v4l2_subdev * sd, struct v4l2_fmtdesc * fmtdesc) ;
int ( * g_fmt) ( struct v4l2_subdev * sd, struct v4l2_format * fmt) ;
int ( * try_fmt) ( struct v4l2_subdev * sd, struct v4l2_format * fmt) ;
int ( * s_fmt) ( struct v4l2_subdev * sd, struct v4l2_format * fmt) ;
int ( * cropcap) ( struct v4l2_subdev * sd, struct v4l2_cropcap * cc) ;
int ( * g_crop) ( struct v4l2_subdev * sd, struct v4l2_crop * crop) ;
int ( * s_crop) ( struct v4l2_subdev * sd, struct v4l2_crop * crop) ;
int ( * g_parm) ( struct v4l2_subdev * sd, struct v4l2_streamparm * param) ;
int ( * s_parm) ( struct v4l2_subdev * sd, struct v4l2_streamparm * param) ;
. . .
} ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
2.2 V4L2提供的注册接口
video_device
注册
int video_register_device ( struct video_device * vdev, int type, int nr) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
注销
void video_unregister_device ( struct video_device * vdev) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
v4l2_device
注册
int v4l2_device_register ( struct device * dev, struct v4l2_device * v4l2_dev) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
注销
void v4l2_device_unregister ( struct v4l2_device * v4l2_dev) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
v4l2_subdev
注册
int v4l2_device_register_subdev ( struct v4l2_device * v4l2_dev,
struct v4l2_subdev * sd) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
注销
void v4l2_device_unregister_subdev ( struct v4l2_subdev * sd) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
三、源码剖析
3.1 V4L2驱动模板
此示例中并没有设计到v4l2_subdev,这部分将会在后面分析具体的驱动中出现
#include <...>
static struct video_device* video_dev;
static struct v4l2_device v4l2_dev;
static const struct v4l2_file_operations video_dev_fops = {
. owner = THIS_MODULE,
. release = vdev_close,
. read = vdev_read,
. poll = vdev_poll,
. ioctl = video_ioctl2,
. mmap = vdev_mmap,
} ;
static const struct v4l2_ioctl_ops video_dev_ioctl_ops = {
. vidioc_querycap = vidioc_querycap,
. vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
. vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap,
. vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap,
. vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
. vidioc_reqbufs = vidioc_reqbufs,
. vidioc_querybuf = vidioc_querybuf,
. vidioc_qbuf = vidioc_qbuf,
. vidioc_dqbuf = vidioc_dqbuf,
. vidioc_enum_input = vidioc_enum_input,
. vidioc_g_input = vidioc_g_input,
. vidioc_s_input = vidioc_s_input,
. vidioc_streamon = vidioc_streamon,
. vidioc_streamoff = vidioc_streamoff,
} ;
static int __init video_init ( void )
{
video_dev = video_device_alloc ( ) ;
video_dev-> fops = & video_dev_fops;
video_dev-> ioctl_ops = & video_dev_ioctl_ops;
video_dev-> release = video_device_release;
video_dev-> tvnorms = V4L2_STD_525_60;
video_dev-> current_norm = V4L2_STD_NTSC_M;
v4l2_device_register ( video_dev-> dev, & v4l2_dev) ;
video_dev-> v4l2_dev = & video_dev;
video_register_device ( video_dev, VFL_TYPE_GRABBER, - 1 ) ;
return 0 ;
}
static void __exit video_exit ( void )
{
video_unregister_device ( video_dev) ;
v4l2_device_unregister ( & v4l2_dev) ;
video_device_release ( video_dev) ;
}
module_init ( video_init) ;
module_exit ( video_exit) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
如果你熟悉v4l2应用编程的话,你应该知道v4l2有许多ioctl操作,上面的模板就实现了很多ioctl操作
3.2 V4L2源码剖析
下面我们来分析分析源码
在上面的video_init
中,我们分配并设置了video_dev
,注册了v4l2_device
(v4l2_device_register),然后向v4l2核心层注册video_device
(video_register_device)
我们先来看v4l2_device_register
int v4l2_device_register ( struct device * dev, struct v4l2_device * v4l2_dev)
{
INIT_LIST_HEAD ( & v4l2_dev-> subdevs) ;
spin_lock_init ( & v4l2_dev-> lock) ;
dev_set_drvdata ( dev, v4l2_dev) ;
. . .
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
从源码中可以看出v4l2_device_register
并没有做什么事,只是初始化链表,自旋锁,还有设置数据,这函数并不是我们的重点
下面来仔细分析video_register_device
int video_register_device ( struct video_device * vdev, int type, int nr)
{
vdev-> cdev = cdev_alloc ( ) ;
vdev-> cdev-> ops = & v4l2_fops;
cdev_add ( vdev-> cdev, MKDEV ( VIDEO_MAJOR, vdev-> minor) , 1 ) ;
dev_set_name ( & vdev-> dev, "%s%d" , name_base, vdev-> num) ;
device_register ( & vdev-> dev) ;
video_device[ vdev-> minor] = vdev;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
可以看到这个函数会为video_device分配一个cdev,然后设置fops,向内核注册字符设备,再者生成设备节点
然后设置video_device全局数组,video_device一个全局数组
static struct video_device * video_device[ VIDEO_NUM_DEVICES] ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
保存着注册的video_device
接下来看一下其中设置的fops(v4l2_fops)
static const struct file_operations v4l2_fops = {
. owner = THIS_MODULE,
. read = v4l2_read,
. write = v4l2_write,
. open = v4l2_open,
. get_unmapped_area = v4l2_get_unmapped_area,
. mmap = v4l2_mmap,
. ioctl = v4l2_ioctl,
. release = v4l2_release,
. poll = v4l2_poll,
. llseek = no_llseek,
} ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
这个是video_device中的字符设备对应的fops,应用层发生系统调用会率先调用到这里,我们来好好看一看这些调用
v4l2_open
static int v4l2_open ( struct inode * inode, struct file * filp)
{
struct video_device * vdev;
vdev = video_devdata ( filp) ;
if ( vdev-> fops-> open)
ret = vdev-> fops-> open ( filp) ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
从这个函数可以看到,发生系统调用首先来到v4l2核心层的字符设备,然后再回调到对应的video_device,video_device在前面已经实现了v4l2_file_operations
和v4l2_ioctl_ops
一系列回调
v4l2_ioctl
V4L2的应用编程会有非常多的ioctl,会先调用到此处
static int v4l2_ioctl ( struct inode * inode, struct file * filp,
unsigned int cmd, unsigned long arg)
{
struct video_device * vdev = video_devdata ( filp) ;
return vdev-> fops-> ioctl ( filp, cmd, arg) ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
下面来看一看video_device怎么实现ioctl
static const struct v4l2_file_operations video_dev_fops = {
. owner = THIS_MODULE,
. release = vdev_close,
. read = vdev_read,
. poll = vdev_poll,
. ioctl = video_ioctl2,
. mmap = vdev_mmap,
} ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
从上面驱动程序的编写,我们可以知道video_device对应ioctl就是video_ioctl2,这个函数是内核提供的,我们看一看这个函数的内容
long video_ioctl2 ( struct file * file,
unsigned int cmd, unsigned long arg)
{
__video_do_ioctl ( file, cmd, parg) ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
static long __video_do_ioctl ( struct file * file,
unsigned int cmd, void * arg)
{
struct video_device * vfd = video_devdata ( file) ;
const struct v4l2_ioctl_ops * ops = vfd-> ioctl_ops;
switch ( cmd) {
case VIDIOC_QUERYCAP:
ops-> vidioc_querycap ( file, fh, cap) ;
case VIDIOC_ENUM_FMT:
ops-> vidioc_enum_fmt_vid_cap ( file, fh, f) ;
. . .
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 可以看出,最终会调用到video_device实现的v4l2_ioctl_ops
static const struct v4l2_ioctl_ops video_dev_ioctl_ops = {
. vidioc_querycap = vidioc_querycap,
. vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
. vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap,
. vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap,
. vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
. vidioc_reqbufs = vidioc_reqbufs,
. vidioc_querybuf = vidioc_querybuf,
. vidioc_qbuf = vidioc_qbuf,
. vidioc_dqbuf = vidioc_dqbuf,
. vidioc_enum_input = vidioc_enum_input,
. vidioc_g_input = vidioc_g_input,
. vidioc_s_input = vidioc_s_input,
. vidioc_streamon = vidioc_streamon,
. vidioc_streamoff = vidioc_streamoff,
} ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 所以系统调用最先都会调用到字符设备的fops,然后经过v4l2核心层最终调用到video_device这里
关于v4l2的驱动框架就分析到这里,关于更加详细的实现v4l2驱动,将在后续文章中通过实例讲解
data-report-view="{"mod":"1585297308_001","spm":"1001.2101.3001.6548","dest":"http://iyenn.com/rec/2146286.html","extend1":"pc","ab":"new"}">>
id="article_content" class="article_content clearfix">
id="content_views" class="markdown_views prism-atom-one-light">
深入学习Linux摄像头系列
深入学习Linux摄像头(一)v4l2应用编程
深入学习Linux摄像头(二)v4l2驱动框架
深入学习Linux摄像头(三)虚拟摄像头驱动分析
深入学习Linux摄像头(四)三星平台fimc驱动详解
深入学习Linux 摄像头(二)v4l2驱动框架
class="toc">
文章目录
一、V4L2 框架
1.1 相关对象
v4l2驱动框架主要的对象有video_device
、v4l2_device
、v4l2_subdev
、videobuf
video_device
一个字符设备,为用户空间提供设备节点(/dev/videox),提供系统调用的相关操作(open、ioctl…)
v4l2_device
嵌入到video_device中,表示一个v4l2设备的实例
v4l2_subdev
依附在v4l2_device之下,并表示一个v4l2设备的子设备,一个v4l2_devide下可以有多个sub_device
videobuf
v4l2驱动的缓存管理
下面有必要对v4l2_device和v4l2_subdev来进行说明
subdev的设计目的是为了多路复用,就是用一个v4l2_device可以服务多个v4l2_subdev
下面以我们手机的摄像头来举例
CMOS摄像头
对于一款CMOS摄像头来说,有两个接口,一个是摄像头接口
,一个是I2C接口
摄像头接口负责传输图像数据,I2C接口负责传输控制信息,所以又可以将CMOS摄像头看作是一个I2C模块
如下图所示
芯片片上资源
在一款芯片上面,摄像头相关的有摄像头控制器
、摄像头接口
、I2C总线
SOC上可以有多个摄像头控制器,多个摄像头接口,多个I2C总线
摄像头控制器负责接收和处理摄像头数据,摄像头接口负责传输图像数据,I2C总线负责传输控制信息
如下图所示
对于手机而言,一般都有两个摄像头,一个前置摄像头,一个后置摄像头,其接发下图所示
我们可以选择让控制器去操作哪一个摄像头,这就做到了使用一个摄像头控制器来控制多个摄像头,这就是多路复用
上面说要使用一个摄像头控制器去操作多个摄像头,这是我们的目的,那么在软件中是怎么实现的呢?
我们回到V4L2来,再来谈v4l2_device
和v4l2_subdev
上面我们介绍到v4l2_device
表示一个v4l2实例
在V4L2驱动中,使用v4l2_device
来表示摄像头控制器
使用v4l2_subdev
来表示具体的某一个摄像头的I2C控制模块
,进而通过其控制摄像头
v4l2_device
里有一个v4l2_subdev
链表,可以选择v4l2_device
去控制哪一个v4l2_subdev
相信到此,你对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控制模块
1.2 V4L2 框架
在理清楚V4L2中的主要对象后,我们来介绍V4L2的框架
在介绍V4L2驱动框架前,我们先回顾一下简单的字符设备 的编写
分配一个字符设备(cdev) 设置一个fops 注册字符设备
复杂的字符设备
对于复杂的字符设备,内核都是采用分层的方法,一般分驱动核心层 还有硬件相关层
核心层 会帮你完成字符设备的分配,fops的设置,注册字符设备,并向硬件相关层提供一个相应的对象和注册接口
硬件相关层 则需要分配相应的对象,设置对象和对象的fops,并注册到核心层中
当应用层发生系统调用,会先来到核心层,核心层再通过回调函数调用到硬件相关层的驱动
对于V4L2的驱动框架也是如此,可分为V4L2驱动核心层 和硬件相关层
下面先用一张图来总结大致V4L2的驱动框架
从图中可以看出V4L2分为核心层还有硬件相关层
核心层负责注册字符设备,然后提供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
来操作硬件
二、V4L2的数据结构
介绍完V4L2的驱动框架后,来看一看内核中各对象的数据结构
2.1 V4L2主要对象的数据结构
video_device
struct video_device
{
struct cdev * cdev;
struct v4l2_device * v4l2_dev;
const struct v4l2_file_operations * fops;
const struct v4l2_ioctl_ops * ioctl_ops;
} ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
可以看到video_device中含有一个cdev还有v4l2_device,此外还有fops和ioctl_ops,从应用层进行系统调用会经过v4l2的核心层回调到这里
其中v4l2_file_operations
和v4l2_ioctl_ops
如下
struct v4l2_file_operations {
struct module * owner;
ssize_t ( * read) ( struct file * , char __user * , size_t, loff_t * ) ;
ssize_t ( * write) ( struct file * , const char __user * , size_t, loff_t * ) ;
unsigned int ( * poll) ( struct file * , struct poll_table_struct * ) ;
long ( * ioctl) ( struct file * , unsigned int , unsigned long ) ;
long ( * unlocked_ioctl) ( struct file * , unsigned int , unsigned long ) ;
unsigned long ( * get_unmapped_area) ( struct file * , unsigned long ,
unsigned long , unsigned long , unsigned long ) ;
int ( * mmap) ( struct file * , struct vm_area_struct * ) ;
int ( * open) ( struct file * ) ;
int ( * release) ( struct file * ) ;
} ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
熟悉v4l2应用编程的应该都知道v4l2有很多ioctl操作,具体实现都在这里
struct v4l2_ioctl_ops {
int ( * vidioc_querycap) ( struct file * file, void * fh, struct v4l2_capability * cap) ;
int ( * vidioc_reqbufs) ( struct file * file, void * fh, struct v4l2_requestbuffers * b) ;
int ( * vidioc_querybuf) ( struct file * file, void * fh, struct v4l2_buffer * b) ;
int ( * vidioc_qbuf) ( struct file * file, void * fh, struct v4l2_buffer * b) ;
int ( * vidioc_dqbuf) ( struct file * file, void * fh, struct v4l2_buffer * b) ;
int ( * vidioc_streamon) ( struct file * file, void * fh, enum v4l2_buf_type i) ;
int ( * vidioc_streamoff) ( struct file * file, void * fh, enum v4l2_buf_type i) ;
. . .
} ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
v4l2_device
struct v4l2_device {
struct list_head subdevs;
. . .
} ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
可以看到v4l2_device中有一个v4l2_subdev的链表,v4l2_device的主要目的时用来管理v4l2_subdev
v4l2_subdev
struct v4l2_subdev {
struct list_head list;
struct v4l2_device * v4l2_dev;
const struct v4l2_subdev_ops * ops;
} ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
v4l2_subdev中有一个v4l2_subdev_ops,实现了一系列的操作,供v4l2_device调用
struct v4l2_subdev_ops {
const struct v4l2_subdev_core_ops * core;
const struct v4l2_subdev_tuner_ops * tuner;
const struct v4l2_subdev_audio_ops * audio;
const struct v4l2_subdev_video_ops * video;
const struct v4l2_subdev_vbi_ops * vbi;
const struct v4l2_subdev_ir_ops * ir;
const struct v4l2_subdev_sensor_ops * sensor;
} ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
struct v4l2_subdev_core_ops {
. . .
int ( * s_config) ( struct v4l2_subdev * sd, int irq, void * platform_data) ;
int ( * init) ( struct v4l2_subdev * sd, u32 val) ;
int ( * s_gpio) ( struct v4l2_subdev * sd, u32 val) ;
int ( * g_ctrl) ( struct v4l2_subdev * sd, struct v4l2_control * ctrl) ;
int ( * s_ctrl) ( struct v4l2_subdev * sd, struct v4l2_control * ctrl) ;
int ( * s_std) ( struct v4l2_subdev * sd, v4l2_std_id norm) ;
long ( * ioctl) ( struct v4l2_subdev * sd, unsigned int cmd, void * arg) ;
. . .
} ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
struct v4l2_subdev_video_ops {
. . .
int ( * enum_fmt) ( struct v4l2_subdev * sd, struct v4l2_fmtdesc * fmtdesc) ;
int ( * g_fmt) ( struct v4l2_subdev * sd, struct v4l2_format * fmt) ;
int ( * try_fmt) ( struct v4l2_subdev * sd, struct v4l2_format * fmt) ;
int ( * s_fmt) ( struct v4l2_subdev * sd, struct v4l2_format * fmt) ;
int ( * cropcap) ( struct v4l2_subdev * sd, struct v4l2_cropcap * cc) ;
int ( * g_crop) ( struct v4l2_subdev * sd, struct v4l2_crop * crop) ;
int ( * s_crop) ( struct v4l2_subdev * sd, struct v4l2_crop * crop) ;
int ( * g_parm) ( struct v4l2_subdev * sd, struct v4l2_streamparm * param) ;
int ( * s_parm) ( struct v4l2_subdev * sd, struct v4l2_streamparm * param) ;
. . .
} ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
2.2 V4L2提供的注册接口
video_device
注册
int video_register_device ( struct video_device * vdev, int type, int nr) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
注销
void video_unregister_device ( struct video_device * vdev) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
v4l2_device
注册
int v4l2_device_register ( struct device * dev, struct v4l2_device * v4l2_dev) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
注销
void v4l2_device_unregister ( struct v4l2_device * v4l2_dev) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
v4l2_subdev
注册
int v4l2_device_register_subdev ( struct v4l2_device * v4l2_dev,
struct v4l2_subdev * sd) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
注销
void v4l2_device_unregister_subdev ( struct v4l2_subdev * sd) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
三、源码剖析
3.1 V4L2驱动模板
此示例中并没有设计到v4l2_subdev,这部分将会在后面分析具体的驱动中出现
#include <...>
static struct video_device* video_dev;
static struct v4l2_device v4l2_dev;
static const struct v4l2_file_operations video_dev_fops = {
. owner = THIS_MODULE,
. release = vdev_close,
. read = vdev_read,
. poll = vdev_poll,
. ioctl = video_ioctl2,
. mmap = vdev_mmap,
} ;
static const struct v4l2_ioctl_ops video_dev_ioctl_ops = {
. vidioc_querycap = vidioc_querycap,
. vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
. vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap,
. vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap,
. vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
. vidioc_reqbufs = vidioc_reqbufs,
. vidioc_querybuf = vidioc_querybuf,
. vidioc_qbuf = vidioc_qbuf,
. vidioc_dqbuf = vidioc_dqbuf,
. vidioc_enum_input = vidioc_enum_input,
. vidioc_g_input = vidioc_g_input,
. vidioc_s_input = vidioc_s_input,
. vidioc_streamon = vidioc_streamon,
. vidioc_streamoff = vidioc_streamoff,
} ;
static int __init video_init ( void )
{
video_dev = video_device_alloc ( ) ;
video_dev-> fops = & video_dev_fops;
video_dev-> ioctl_ops = & video_dev_ioctl_ops;
video_dev-> release = video_device_release;
video_dev-> tvnorms = V4L2_STD_525_60;
video_dev-> current_norm = V4L2_STD_NTSC_M;
v4l2_device_register ( video_dev-> dev, & v4l2_dev) ;
video_dev-> v4l2_dev = & video_dev;
video_register_device ( video_dev, VFL_TYPE_GRABBER, - 1 ) ;
return 0 ;
}
static void __exit video_exit ( void )
{
video_unregister_device ( video_dev) ;
v4l2_device_unregister ( & v4l2_dev) ;
video_device_release ( video_dev) ;
}
module_init ( video_init) ;
module_exit ( video_exit) ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
如果你熟悉v4l2应用编程的话,你应该知道v4l2有许多ioctl操作,上面的模板就实现了很多ioctl操作
3.2 V4L2源码剖析
下面我们来分析分析源码
在上面的video_init
中,我们分配并设置了video_dev
,注册了v4l2_device
(v4l2_device_register),然后向v4l2核心层注册video_device
(video_register_device)
我们先来看v4l2_device_register
int v4l2_device_register ( struct device * dev, struct v4l2_device * v4l2_dev)
{
INIT_LIST_HEAD ( & v4l2_dev-> subdevs) ;
spin_lock_init ( & v4l2_dev-> lock) ;
dev_set_drvdata ( dev, v4l2_dev) ;
. . .
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
从源码中可以看出v4l2_device_register
并没有做什么事,只是初始化链表,自旋锁,还有设置数据,这函数并不是我们的重点
下面来仔细分析video_register_device
int video_register_device ( struct video_device * vdev, int type, int nr)
{
vdev-> cdev = cdev_alloc ( ) ;
vdev-> cdev-> ops = & v4l2_fops;
cdev_add ( vdev-> cdev, MKDEV ( VIDEO_MAJOR, vdev-> minor) , 1 ) ;
dev_set_name ( & vdev-> dev, "%s%d" , name_base, vdev-> num) ;
device_register ( & vdev-> dev) ;
video_device[ vdev-> minor] = vdev;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
可以看到这个函数会为video_device分配一个cdev,然后设置fops,向内核注册字符设备,再者生成设备节点
然后设置video_device全局数组,video_device一个全局数组
static struct video_device * video_device[ VIDEO_NUM_DEVICES] ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
保存着注册的video_device
接下来看一下其中设置的fops(v4l2_fops)
static const struct file_operations v4l2_fops = {
. owner = THIS_MODULE,
. read = v4l2_read,
. write = v4l2_write,
. open = v4l2_open,
. get_unmapped_area = v4l2_get_unmapped_area,
. mmap = v4l2_mmap,
. ioctl = v4l2_ioctl,
. release = v4l2_release,
. poll = v4l2_poll,
. llseek = no_llseek,
} ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
这个是video_device中的字符设备对应的fops,应用层发生系统调用会率先调用到这里,我们来好好看一看这些调用
v4l2_open
static int v4l2_open ( struct inode * inode, struct file * filp)
{
struct video_device * vdev;
vdev = video_devdata ( filp) ;
if ( vdev-> fops-> open)
ret = vdev-> fops-> open ( filp) ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
从这个函数可以看到,发生系统调用首先来到v4l2核心层的字符设备,然后再回调到对应的video_device,video_device在前面已经实现了v4l2_file_operations
和v4l2_ioctl_ops
一系列回调
v4l2_ioctl
V4L2的应用编程会有非常多的ioctl,会先调用到此处
static int v4l2_ioctl ( struct inode * inode, struct file * filp,
unsigned int cmd, unsigned long arg)
{
struct video_device * vdev = video_devdata ( filp) ;
return vdev-> fops-> ioctl ( filp, cmd, arg) ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
下面来看一看video_device怎么实现ioctl
static const struct v4l2_file_operations video_dev_fops = {
. owner = THIS_MODULE,
. release = vdev_close,
. read = vdev_read,
. poll = vdev_poll,
. ioctl = video_ioctl2,
. mmap = vdev_mmap,
} ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
从上面驱动程序的编写,我们可以知道video_device对应ioctl就是video_ioctl2,这个函数是内核提供的,我们看一看这个函数的内容
long video_ioctl2 ( struct file * file,
unsigned int cmd, unsigned long arg)
{
__video_do_ioctl ( file, cmd, parg) ;
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
static long __video_do_ioctl ( struct file * file,
unsigned int cmd, void * arg)
{
struct video_device * vfd = video_devdata ( file) ;
const struct v4l2_ioctl_ops * ops = vfd-> ioctl_ops;
switch ( cmd) {
case VIDIOC_QUERYCAP:
ops-> vidioc_querycap ( file, fh, cap) ;
case VIDIOC_ENUM_FMT:
ops-> vidioc_enum_fmt_vid_cap ( file, fh, f) ;
. . .
}
}
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 可以看出,最终会调用到video_device实现的v4l2_ioctl_ops
static const struct v4l2_ioctl_ops video_dev_ioctl_ops = {
. vidioc_querycap = vidioc_querycap,
. vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
. vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap,
. vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap,
. vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
. vidioc_reqbufs = vidioc_reqbufs,
. vidioc_querybuf = vidioc_querybuf,
. vidioc_qbuf = vidioc_qbuf,
. vidioc_dqbuf = vidioc_dqbuf,
. vidioc_enum_input = vidioc_enum_input,
. vidioc_g_input = vidioc_g_input,
. vidioc_s_input = vidioc_s_input,
. vidioc_streamon = vidioc_streamon,
. vidioc_streamoff = vidioc_streamoff,
} ;
class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
class="hide-preCode-box">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 所以系统调用最先都会调用到字符设备的fops,然后经过v4l2核心层最终调用到video_device这里
关于v4l2的驱动框架就分析到这里,关于更加详细的实现v4l2驱动,将在后续文章中通过实例讲解
data-report-view="{"mod":"1585297308_001","spm":"1001.2101.3001.6548","dest":"http://iyenn.com/rec/2146286.html","extend1":"pc","ab":"new"}">>
评论记录:
回复评论: