摄像头驱动程序_V4L2驱动分析

2023年 1月 29日 102点热度 0人点赞 0条评论

V4L2 框架:Video For Linux 2, 即 V4L 的第二个版本, 第一个版本由于太老了, 已经从 Linux 中被淘汰了, 目前看到的都是第二个版本.

回顾第二期的驱动:

  • 字符驱动程序
  • 块设备的驱动程序
  • 网卡驱动程序

对于字符设备驱动程序, APP 只会通过标准的接口 open, read, write 来访问设备, 写应用程序的不应该去看硬件手册, 操作寄存器等来访问设备, 这些都是驱动程序来完成的.

驱动程序提供 drv_open, drv_read, drv_write.

怎么写这个驱动呢?

  1. 构造 file_operations, 里面有属性 .open = drv_open, .read = drv_read
  2. 辛辛苦苦写出来的操作需要告诉内核,register_chrdev, 注册驱动的时候肯定需要参数, 将其放到 chrdev[] 将 file_operations 放到这个数组中, index 就是主设备号, 如果给 0 的话就是系统分配
  3. 谁来调用这个 register_chrdev, 在驱动装载的时候, 内核会调用一个入口函数, 在这个入口函数中会调用 register_chrdev.
  4. 有入口自然有出口, 当不想用这个驱动的时候, 自然会来出口函数中卸载, 这个时候调用的就是 unregister_chrdev.

register_chrdev 太老了, 现在都不推荐使用这个, 现在推荐使用 cdev:

  1. 分配 cdev
  2. 设置 cdev
  3. 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 使用一套同意的代码.

问题来了: 字符驱动设备如何写分层驱动?

  1. 分配某个结构体
  2. 设置这个结构体
  3. 注册这个结构体
  4. 硬件相关的操作

对于我们的 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 看下内核的输出信息.

file

进入 linux-3.4.2/drivers 的目录搜索下: grep "Found UVC" * -nR, 在 Uvc_driver.c

分析 v4l2 的实现细节有两种方法

分析 v4l2 的实现细节有两种方法:

  • 看文档:linux 提供的 v4l2-framework.txt, 但是写的不是很好
  • 看代码:vivi.c

虚拟视频驱动分析 vivi.c

  1. 分配一个 video_device 结构体
  2. 设置这个结构体
  3. 注册: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 的驱动?

  1. 分配/设置/注册一个 v4l2_device: v4l2_device_register(关键函数), 得到一个结构体 v4l2_device(辅助作用, 提供引用计数, 自旋锁等)
  2. 分配一个 video_device 结构体: 用的函数是 video_device_alloc,
  3. 设置
    • 设置 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

这个框架并没有脱离字符设备驱动的框架, 只是里面有些技巧性的东西.

本文来自:https://blog.duhbb.com

本文链接地址:摄像头驱动程序_V4L2驱动分析,英雄不问来路,转载请注明出处,谢谢。

有话想说:那就赶紧去给我留言吧。

rainbow

这个人很懒,什么都没留下

文章评论