V4L2 框架:Video For Linux 2, 即 V4L 的第二个版本, 第一个版本由于太老了, 已经从 Linux 中被淘汰了, 目前看到的都是第二个版本.
回顾第二期的驱动:
- 字符驱动程序
- 块设备的驱动程序
- 网卡驱动程序
对于字符设备驱动程序, APP 只会通过标准的接口 open
, read
, write
来访问设备, 写应用程序的不应该去看硬件手册, 操作寄存器等来访问设备, 这些都是驱动程序来完成的.
驱动程序提供 drv_open
, drv_read
, drv_write
.
怎么写这个驱动呢?
- 构造 file_operations, 里面有属性
.open = drv_open
,.read = drv_read
- 辛辛苦苦写出来的操作需要告诉内核,
register_chrdev
, 注册驱动的时候肯定需要参数, 将其放到chrdev[]
将 file_operations 放到这个数组中, index 就是主设备号, 如果给 0 的话就是系统分配 - 谁来调用这个 register_chrdev, 在驱动装载的时候, 内核会调用一个入口函数, 在这个入口函数中会调用 register_chrdev.
- 有入口自然有出口, 当不想用这个驱动的时候, 自然会来出口函数中卸载, 这个时候调用的就是 unregister_chrdev.
register_chrdev 太老了, 现在都不推荐使用这个, 现在推荐使用 cdev:
- 分配 cdev
- 设置 cdev
- cdev_add
即用上面的三个步骤代替 register_chrdev, 这种方法也是可以的.
以上就是基本的字符设备的框架, 对于复杂的字符设备驱动程序, 就引入了分层的概念.
LCD 中的驱动程序就是使用了分层的概念, 它的驱动程序分为两层:
上一层:fbmem.c:
- file_operations 结构体
- 第二注册这个结构体
- 第三出口入口
这个 fbmem.c 是内核已经帮我们提供了, 那么我们要做的是什么呢? 我们要做的就是硬件相关的这一层.
- 分配一个 fb_info 结构体
- 设置 fb_info 结构体
- 注册
- 硬件相关的操作
在 LCD 的应用中, 应用程序 (APP) 调用 open, read,write 操作 LCD 时, 首先会调用 fbmem.c 中的 file_operations 中的 open read write 方法, 这个时候就会根据我们提供的 fb_info 中的操作或者属性来操作硬件了, 这种分层的好处是我们只需要专注于硬件的这一层就行了, 其他通用的东西内核已经帮我们做好了. 并且这种分层除了减少我们的工作量之外, 还可以使应用程序的 open read write 使用一套同意的代码.
问题来了: 字符驱动设备如何写分层驱动?
- 分配某个结构体
- 设置这个结构体
- 注册这个结构体
- 硬件相关的操作
对于我们的 LCD 驱动来说, 它是 struct fb_info
, 对我们的视频是什么结构体呢?
首先来猜一下摄像头的驱动代码是否也是分为两层呢?
V4L2 框架至少也会分为两层:
- 核心层
- file_operations 结构体
- 注册
- 分配 cdev 结构体, 设置这个结构体, 然后注册这个结构体, cdev_add
- v4l2-dev.c
- 硬件相关
- uvc_driver.c 或者其他, 肯定会向上注册一个结构体, 而这个结构体中会有一个 probe 函数
- v4l2_device_register 函数
- video_device_alloc
- uvc_register_device(这个重要)
- 分配一个 video_device 结构体
- 设置这个结构体
- 注册:video_register_device
如何找到摄像头对应的驱动: 打开 Linux 虚拟机, 然后插入摄像头设备, 通过 dmesg
看下内核的输出信息.
进入 linux-3.4.2/drivers 的目录搜索下: grep "Found UVC" * -nR
, 在 Uvc_driver.c
下
分析 v4l2 的实现细节有两种方法
分析 v4l2 的实现细节有两种方法:
- 看文档:linux 提供的 v4l2-framework.txt, 但是写的不是很好
- 看代码:vivi.c
虚拟视频驱动分析 vivi.c
- 分配一个 video_device 结构体
- 设置这个结构体
- 注册:video_register_device
vivi_init
vivi_create_instance
v4l2_device_register(不是主要的, 只是用于初始化一些东西, 比如自旋锁, 引用计数)
video_device[vdev->minor] = vdev; 以次设备号为下标, 将 vdev 存进来了
video_device_alloc
// 设置, 然后看下 video_device_alloc 分配完之后设置了哪些东西?
1.
.fops = &vivi_fops
.ioctl_ops = &vivi_ioctl_ops
.release = video_device_release
2.
vfd->v4l2_dev = &dev->v4l2_dev;
3. 设置 ctrl 用于 APP 的 ioctl, 这些东西非常多
video_register_device(video_device, type: 类型, number), 根据类型得到不同的名字
__video_register_device
vdev->cdev = cdev_alloc();
vdev->cdev->ops = &v4l2_fops;
cdev_add
video_device[vdev->minor] = vdev;
if (vdev->ctrl_handler == NULL)
vdev->ctrl_handler = vdev->v4l2_dev->ctrl_handler;
有了这些东西就可以分析它的读写过程了
分析 vivi.c 的 open, read,write,ioctl 的过程
open 过程
app: open("/dev/video0", ...)
------------------------------------------------
drv: v4l2_fops 的 open 函数, 看看它做了啥 (v4l2_open 函数, 就是这个了)
v4l2_open
vdev = video_devdata(filp); // 根据次设备号从数组中得到 video_device, 刚好和中的存入对应 v4l2_device_register
ret = vdev->fops->open(filp);
vivi_ioctl_ops.open
v4l2_fh_open
read 过程
app: read
------------------------------------------------
drv: v4l2_read 函数
struct video_device *vdev = video_devdata(filp);
vdev->fops->read
ret = vdev->fops->read(filp, buf, sz, off);
write 函数
不看了, 差不多
ioctl 函数
app: ioctl
----------------------------------
drv: vdev->fops->ioctl
v4l2_fops 中有一个 unlocked_ioctl = v4l2_ioctl, 以前的 ioctl 已经被移除了
先根据次设备号得到 video_device 结构体, 如果提供了 unlocked_ioctl 就调用这个
video_ioctl2
video_usercopy(file, cmd,arg,__video_do_ioctl): 将用户空间的参数复制过来
__video_do_ioctl
struct video_device *vfd = video_devdata(file); 根据次设备号得到 video_device 结构体
根据 app 传入的 cmd 获得, 设置某些属性, 这些属性在哪里呢? 谁来提供这些属性呢?
画图的方式写下来
app: open, read, write
v4l2_fops: open, read, write, ioctl, unlocked_ioctl
vfd.fops: open, read, write
怎么写 v4l2 的驱动?
- 分配/设置/注册一个 v4l2_device: v4l2_device_register(关键函数), 得到一个结构体 v4l2_device(辅助作用, 提供引用计数, 自旋锁等)
- 分配一个 video_device 结构体: 用的函数是 video_device_alloc,
- 设置
- 设置 video_device 结构体, 称之为 vfd, vfd->v4l2_dev 将其指向第一步得到的结构体
- vfd:
- .fops = ...
- .ioctl_operation = &vivi_ioctl_ops
- 设置一些 ctrl 属性
APP 可以通过 ioctl 来设置亮度等信息, 驱动程序里面, 谁来接收/存储/设置 (到硬件)/提供这些信息?
- 属性:v4l2_ctrl 这个结构体, 每一项对应一个.
- 管理:v4l2_ctrl_handler
- v412_ctrl_handler_init, 初始化一个 ctrl_handler
- v4l2_ctrl_new_std: 标准属性
- v4l2_ctrl_new_custom: 创建用户自定义
- 跟 vdev 关联:v4l2_dev.ctrl_handler = hdl, video_dev->v4l2_dev = v4l2_dev = ctrl_handler 里面有各个属性
- 如何使用? 通过 ioctl 引用的
创建 v4l2_ctrl, 并且放入这个链表
v4l2_ctrl_handler 的使用过程
app ->
...
-> __video_do_ioctl
struct video_device *vfd = video_devdata(file);
vfh vfd->ctrl_handler 在哪里设置的?
v4l2_queryctrl(vfd->ctrl_handler, p);
find_ref 根据 id 找到 v4l2_ctrl
// 根据 ID 在 ctrl_hanlder 里找到 v4l2_ctrl
这个框架并没有脱离字符设备驱动的框架, 只是里面有些技巧性的东西.
本文链接地址:摄像头驱动程序_V4L2驱动分析,英雄不问来路,转载请注明出处,谢谢。
有话想说:那就赶紧去给我留言吧。
文章评论