V4L2 实例分析 —— vivi.c 源码详解(深度好文)

2024-05-17 14:15:57 浏览数 (2)

一、V4L2 驱动核心:

V4L2 驱动源码在 drivers/media/video 目录下,主要核心代码有:

  • v4l2-dev.c:Linux 版本 2 视频捕捉接口,主要结构体 video_device 的注册;
  • v4l2-common.c:在 Linux 操作系统体系采用低级别的操作一套设备 struct tures/vectors 的通用视频设备接口;
  • v4l2-device.c:V4L2 的设备支持,注册 v4l2_device;
  • v4l2-ioctl.c:处理 V4L2 的 ioctl 命令的一个通用的框架;
  • v4l2-subdev.c:v4l2 子设备;
  • v4l2-mem2mem.c:内存到内存为 Linux 和 videobuf 视频设备的框架,设备的辅助函数,使用其源和目的 videobuf 缓冲区。

直接来看驱动源码的话,还是对驱动的框架没有一个感性的认识,尤其这个 V4L2 框架非常复杂,我们先从内核源码中提供的虚拟视频驱动程序 vivi.c 来分析,内核版本 3.4.2。

二、虚拟视频驱动程序 vivi.c 源码分析

2.1、分析一个程序从它的 init 入口函数开始分析:

代码语言:javascript复制
static int __init vivi_init(void)
{
    const struct font_desc *font = find_font("VGA8x16");
    int ret = 0, i;
 
    if (font == NULL) {
        printk(KERN_ERR "vivi: could not find fontn");
        return -ENODEV;
    }
    font8x16 = font->data;
 
    if (n_devs <= 0)
        n_devs = 1;
 
    for (i = 0; i < n_devs; i  ) {
        ret = vivi_create_instance(i);
        if (ret) {
            /* If some instantiations succeeded, keep driver */
            if (i)
                ret = 0;
            break;
        }
    }
 
    if (ret < 0) {
        printk(KERN_ERR "vivi: error %d while loading drivern", ret);
        return ret;
    }
 
    printk(KERN_INFO "Video Technology Magazine Virtual Video "
            "Capture Board ver %s successfully loaded.n",
            VIVI_VERSION);
 
    /* n_devs will reflect the actual number of allocated devices */
    n_devs = i;
 
    return ret;
}
 
static void __exit vivi_exit(void)
{
    vivi_release();
}
 
module_init(vivi_init);
module_exit(vivi_exit);

其中 n_devs 的定义在前面,如下所示:

代码语言:javascript复制
static unsigned n_devs = 1;
module_param(n_devs, uint, 0644);
MODULE_PARAM_DESC(n_devs, "numbers of video devices to create");

写的很清楚了,n_devs 表示想要创建的 video devices 个数。

注:一般用户态传递参数是通过 main 函数,第一个参数表示 args 个数,第二个参数表示具体的参数。在 kernel 态 ,无法通过这样的方式传递参数,一般使用 module_param 的方式,步骤如下:

  • 使用 module_param 指定模块的参数;
  • 加载 driver 时给模块传递参数;

去掉其他的判断语句,发现重要的函数只有一个 vivi_create_instance(i) 函数,我们下面就来分析这个函数。

2.2、vivi_create_instance(i) 函数:

代码语言:javascript复制
static int __init vivi_create_instance(int inst)
{
    struct vivi_dev *dev;
    struct video_device *vfd;
    struct v4l2_ctrl_handler *hdl;
    struct vb2_queue *q;
    int ret;
 
    dev = kzalloc(sizeof(*dev), GFP_KERNEL);
    if (!dev)
        return -ENOMEM;
 
    snprintf(dev->v4l2_dev.name, sizeof(dev->v4l2_dev.name),
            "%s-d", VIVI_MODULE_NAME, inst);
    ret = v4l2_device_register(NULL, &dev->v4l2_dev);
    if (ret)
        goto free_dev;
 
    dev->fmt = &formats[0];
    dev->width = 640;
    dev->height = 480;
    hdl = &dev->ctrl_handler;
    v4l2_ctrl_handler_init(hdl, 11);
    dev->volume = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
            V4L2_CID_AUDIO_VOLUME, 0, 255, 1, 200);
    dev->brightness = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
            V4L2_CID_BRIGHTNESS, 0, 255, 1, 127);
    dev->contrast = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
            V4L2_CID_CONTRAST, 0, 255, 1, 16);
    dev->saturation = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
            V4L2_CID_SATURATION, 0, 255, 1, 127);
    dev->hue = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
            V4L2_CID_HUE, -128, 127, 1, 0);
    dev->autogain = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
            V4L2_CID_AUTOGAIN, 0, 1, 1, 1);
    dev->gain = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,
            V4L2_CID_GAIN, 0, 255, 1, 100);
    dev->button = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_button, NULL);
    dev->int32 = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_int32, NULL);
    dev->int64 = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_int64, NULL);
    dev->boolean = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_boolean, NULL);
    dev->menu = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_menu, NULL);
    dev->string = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_string, NULL);
    dev->bitmask = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_bitmask, NULL);
    if (hdl->error) {
        ret = hdl->error;
        goto unreg_dev;
    }
    v4l2_ctrl_auto_cluster(2, &dev->autogain, 0, true);
    dev->v4l2_dev.ctrl_handler = hdl;
 
    /* initialize locks */
    spin_lock_init(&dev->slock);
 
    /* initialize queue */
    q = &dev->vb_vidq;
    memset(q, 0, sizeof(dev->vb_vidq));
    q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;
    q->drv_priv = dev;
    q->buf_struct_size = sizeof(struct vivi_buffer);
    q->ops = &vivi_video_qops;
    q->mem_ops = &vb2_vmalloc_memops;
 
    vb2_queue_init(q);
 
    mutex_init(&dev->mutex);
 
    /* init video dma queues */
    INIT_LIST_HEAD(&dev->vidq.active);
    init_waitqueue_head(&dev->vidq.wq);
 
    ret = -ENOMEM;
    vfd = video_device_alloc();
    if (!vfd)
        goto unreg_dev;
 
    *vfd = vivi_template;
    vfd->debug = debug;
    vfd->v4l2_dev = &dev->v4l2_dev;
    set_bit(V4L2_FL_USE_FH_PRIO, &vfd->flags);
 
    /*
     * Provide a mutex to v4l2 core. It will be used to protect
     * all fops and v4l2 ioctls.
     */
    vfd->lock = &dev->mutex;
 
    ret = video_register_device(vfd, VFL_TYPE_GRABBER, video_nr);
    if (ret < 0)
        goto rel_vdev;
 
    video_set_drvdata(vfd, dev);
 
    /* Now that everything is fine, let's add it to device list */
    list_add_tail(&dev->vivi_devlist, &vivi_devlist);
 
    if (video_nr != -1)
        video_nr  ;
 
    dev->vfd = vfd;
    v4l2_info(&dev->v4l2_dev, "V4L2 device registered as %sn",
         video_device_node_name(vfd));
    return 0;
 
rel_vdev:
    video_device_release(vfd);
unreg_dev:
    v4l2_ctrl_handler_free(hdl);
    v4l2_device_unregister(&dev->v4l2_dev);
free_dev:
    kfree(dev);
    return ret;
}

1)、函数首先为 struct vivi_dev *dev 分配内存,然后将 dev->v4l2_dev.name 的名字设置为 “vivi-i” 的形式,然后调用 v4l2_device_register 这个函数来注册 dev->v4l2_dev 这个结构体,结构体 v4l2_device 如下所示,看它的名字就叫 V4L2 设备,它肯定就是 V4L2 设备的核心结构体:

代码语言:javascript复制
struct v4l2_device {
    /* dev->driver_data points to this struct.
       Note: dev might be NULL if there is no parent device
        as is the case with e.g. ISA devices.*/
#if defined(CONFIG_MEDIA_CONTROLLER) 
    struct media_device *mdev;
#endif
    
    /* used to keep track of the registered subdevs */
    struct list_head subdevs;
 
    /* lock this struct, can be used by the drivers as well if this 
       struct is embedded into a larger struct.*/  
    spinlock_t lock;
    
    /* unique device name, by default the driver name   bus ID */
    char name[V4L2_DEVICE_NAME_SIZE];
    
    /* notify callback called by some sub-devices. */
    void (*notify)(struct v4l2_subdev *sd, unsigned int notification, void *arg);
 
    /* The control handler. May be NULL. */
    struct v4l2_ctrl_handler *ctrl_handler;
 
    /* Device's priority state */
    struct v4l2_prio_state prio;
    
    /* BKL replacement mutex.Temporary solution only. */
    struct kref ref;
    
    /* Release function that is called when the ref count goes to 0. */
    void (*release)(struct v4l2_devcie *v4l2_dev); 
};

可以看到这个结构体里面包含一个 device 父设备成员,一个 subdevs 子设备链表头,一个自旋锁,一个 notify 函数指针,v4l2_ctrl_handler 控制句柄,prio 优先级,ref 引用计数,还有一个 release 函数指针。暂时先不对这个结构体进行具体的分析,在以后分析 V4L2 框架的时候再分析。

2)、它通过 v4l2_devie_register(NULL, &dev->v4l2_dev) 函数来完成对结构体的注册,可以看出在 “vivi.c” 中,它的父设备为 NULL,v4l2_device_register 函数在 v4l2-device.c 中:

代码语言:javascript复制
int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)
{
    if (v4l2_dev == NULL)
        return -EINVAL;
 
    INIT_LIST_HEAD(&v4l2_dev->subdevs);
    spin_lock_init(&v4l2_dev->lock);
    mutex_init(&v4l2_dev->ioctl_lock);
    v4l2_prio_init(&v4l2_dev->prio);
    kref_init(&v4l2_dev->ref);
    get_device(dev);
    v4l2_dev->dev = dev;
    if (dev == NULL) {
        /* If dev == NULL, then name must be filled in by the caller */
        WARN_ON(!v4l2_dev->name[0]);
        return 0;
    }
 
    /* Set name to driver name   device name if it is empty. */
    if (!v4l2_dev->name[0])
        snprintf(v4l2_dev->name, sizeof(v4l2_dev->name), "%s %s",
            dev->driver->name, dev_name(dev));
    if (!dev_get_drvdata(dev))
        dev_set_drvdata(dev, v4l2_dev);
    return 0;
}
EXPORT_SYMBOL_GPL(v4l2_device_register);

这个函数完成了子设备链表的初始化,自旋锁,互斥量,优先级,引用计数的初始化,其他并没做太多工作。

3)、继续回到 vivi_create_instance(i) 函数中进行分析,下一句话:dev->fmt = &formats[0];

通过向前搜索发现,vivi.c 维持着一个 formats 数组,它表示 vivi.c 支持的数据格式。关于视频的格式我们在 V4L2 框架中介绍,通过这行代码,我们知道了 vivi.c 所支持的格式为:V4L2_PIX_FMT_YUYV。

代码语言:javascript复制
struct vivi_fmt {
    char *name;
    u32 fourcc; /* v4l2 format id */
    int depth;
};
 
static struct vivi_fmt formats[] = {
    {
        .name = "4:2:2, packed, YUYV",
        .fourcc = V4L2_PIX_FMT_YUYV,
        .depth = 16,
    },
    {
        .name = "4:2:2, packed, UYVY",
        .fourcc = V4L2_PIX_FMT_UYVY,
        .depth = 16,
    },
    {
        .name = "RGB565 (LE)",
        .fourcc = V4L2_PIX_FMT_RGB565, /* gggbbbbb rrrrrggg */
        .depth = 16,
    },
    {
        .name = "RGB565 (BE)",
        .fourcc = V4L2_PIX_FMT_RGB565X, /* rrrrrggg gggbbbbb */
        .depth = 16,
    },
    {
        .name = "RGB555 (LE)",
        .fourcc = V4L2_PIX_FMT_RGB555, /* gggbbbbb arrrrrgg */
        .depth = 16,
    },
    {
        .name = "RGB555 (BE)",
        .fourcc = V4L2_PIX_FMT_RGB555X, /* arrrrrgg gggbbbbb */
        .depth = 16,
    },
};

4)、继续在 vivi_create_instance(i) 函数中分析:

代码语言:javascript复制
hdl = &dev->ctrl_handler;
v4l2_ctrl_handler_init(hdl, 11);

v4l2_ctrl_handler 结构体在 v4l2-ctrls.h 中定义,如下所示:

代码语言:javascript复制
struct v4l2_ctrl_handler {
    struct mutex lock;
    struct list_head ctrls;
    struct list_head ctrl_refs;
    struct v4l2_ctrl_ref *cached;
    struct v4l2_ctrl_ref ** buckets;
    u16 nr_of_buckets;
    int error;
};

v4l2_ctrl_handler 是用于保存子设备控制方法集的结构体,对于视频设备这些 ctrls 包括设置亮度、饱和度、对比度和清晰度等,用链表的方式来保存 ctrls,可以通过 v4l2_ctrl_new_std 函数向链表添加 ctrls。在下面的代码中用到了这个函数。

代码语言:javascript复制
/* Initialize the handler */
int v4l2_ctrl_handler_init(struct v4l2_ctrl_handler *hdl, unsigned nr_of_controls_hint)
{
    mutex_init(&hdl->lock);
    INIT_LIST_HEAD(&hdl->ctrls);
    INIT_LIST_HEAD(&hdl->ctrl_refs);
    hdl->nr_of_buckets = 1   nr_of_controls_hint / 8;
    hdl->buckets = kcalloc(hdl->nr_of_buckets, sizeof(hdl->buckets[0]), GFP_KERNEL);
    hdl->error = hdl->buckets ? 0 : -ENOMEM;
    return hdl->error;
}

通过 nr_of_control_hint 变量的大小,计算出 nr_of_buckets,计算出 nr_of_buckets,并为 buckets 申请空间,并将申请结果保存在 error 变量中。

5)、继续在 vivi_create_instance(i) 函数中分析,继续设置 dev 结构体中的其他一些参数,对 volume,brightness,contrast,saturation 等参数设置的时候,调用了 v4l2_ctrl_new_std 这个函数,以及对 button,int32,menu,bitmask 等参数设置,调用了 v4l2_ctrl_new_custom 这个函数,一看就知道这两个函数是 V4L2 框架所提供的接口函数。

代码语言:javascript复制
struct v4l2_ctrl *v4l2_ctrl_new_std(struct v4l2_ctrl_handler *hdl, const struct v4l2_ctrl_ops *ops, u32 id, s32 min, s32 max, u32 step, s32 def);
  • hdl 是初始化好的 v4l2_ctrl_handler 结构体;
  • ops 是 v4l2_ctrl_ops 结构体,包含 ctrls 的具体实现;
  • id 是通过 IOCTL 的 arg 参数传过来的指令,定义在 v4l2-controls.h 文件;
  • min、max 用来定义某操作对象的范围。如:v4l2_ctrl_new_std(hdl, ops, V4L2_CID_BRIGHTNESS, -208, 127, 1, 0);

用户空间可以通过 ioctl 的 VIDIOC_S_CTRL 指令调用到 v4l2_ctrl_handler, id 透过 arg 参数传递。

通过几个含糊是来完成对视频中 亮度,饱和度等的设置。

6)、然后是缓冲区队列的操作,设置 vb2_queue 队列 q 的一些参数,最主要的是下面两个参数:

代码语言:javascript复制
q->ops = &vivi_qops;
q->mem_ops = &vb2_vmalloc_memops;

可以看到:q->ops = &vivi_video_qops 中 vivi_video_qops 是需要 vivi.c 实现的一个操作函数集,它在 vivi.c 中定义如下:

代码语言:javascript复制
static struct vb2_ops vivi_video_qops = {
    .queue_setup = queue_setup,
    .buf_init = buffer_init,
    .buf_prepare = buffer_prepare,
    .buf_finish = buffer_finish,
    .buf_cleanup = buffer_cleanup,
    .buf_queue = buffer_queue,
    .start_streaming = start_streaming,
    .stop_streaming = stop_streaming,
    .wait_prepare = vivi_unlock,
    .wait_finish = vivi_lock,
};

这几个函数是需要我们写驱动程序的时候自己实现的函数。

其中 vb2_ops 结构体在 videobuf2-core.h 中定义,如下所示:

代码语言:javascript复制
struct vb2_ops {
    int (*queue_setup)(struct vb2_queue *q, const struct v4l2_format *fmt,
             unsigned int *num_buffers, unsigned int *num_planes,
             unsigned int sizes[], void *alloc_ctxs[]);
 
    void (*wait_prepare)(struct vb2_queue *q);
    void (*wait_finish)(struct vb2_queue *q);
 
    int (*buf_init)(struct vb2_buffer *vb);
    int (*buf_prepare)(struct vb2_buffer *vb);
    int (*buf_finish)(struct vb2_buffer *vb);
    void (*buf_cleanup)(struct vb2_buffer *vb);
 
    int (*start_streaming)(struct vb2_queue *q, unsigned int count);
    int (*stop_streaming)(struct vb2_queue *q);
 
    void (*buf_queue)(struct vb2_buffer *vb);
};

对于 vb2_vmalloc_memops 结构体,它在 videobuf2-vmalloc.c 中定义,如下所示:

代码语言:javascript复制
const struct vb2_mem_ops vb2_vmalloc_memops = {
    .alloc        = vb2_vmalloc_alloc,
    .put        = vb2_vmalloc_put,
    .get_userptr    = vb2_vmalloc_get_userptr,
    .put_userptr    = vb2_vmalloc_put_userptr,
    .vaddr        = vb2_vmalloc_vaddr,
    .mmap        = vb2_vmalloc_mmap,
    .num_users    = vb2_vmalloc_num_users,
};
EXPORT_SYMBOL_GPL(vb2_vmalloc_memops);

看它的名字是 vb2 开头的,这几个函数应该都是系统为我们提供好的函数,通过查看源码发现,它确实存在于 videobuf2-vmalloc.c 中。

然后调用 vb2_queue_init(q) 函数来初始化它,vb2_queue_init(q) 函数如下所示:

代码语言:javascript复制
/**
 * vb2_queue_init() - initialize a videobuf2 queue
 * @q:        videobuf2 queue; this structure should be allocated in driver
 *
 * The vb2_queue structure should be allocated by the driver. The driver is
 * responsible of clearing it's content and setting initial values for some
 * required entries before calling this function.
 * q->ops, q->mem_ops, q->type and q->io_modes are mandatory. Please refer
 * to the struct vb2_queue description in include/media/videobuf2-core.h
 * for more information.
 */
int vb2_queue_init(struct vb2_queue *q)
{
    BUG_ON(!q);
    BUG_ON(!q->ops);
    BUG_ON(!q->mem_ops);
    BUG_ON(!q->type);
    BUG_ON(!q->io_modes);
 
    BUG_ON(!q->ops->queue_setup);
    BUG_ON(!q->ops->buf_queue);
 
    INIT_LIST_HEAD(&q->queued_list);
    INIT_LIST_HEAD(&q->done_list);
    spin_lock_init(&q->done_lock);
    init_waitqueue_head(&q->done_wq);
 
    if (q->buf_struct_size == 0)
        q->buf_struct_size = sizeof(struct vb2_buffer);
 
    return 0;
}
EXPORT_SYMBOL_GPL(vb2_queue_init);

它只是完成了一些检查判断的语句,进行了一些链表,自旋锁等的初始化。

7)、/* init video dma queues */

INIT_LIST_HEAD(&dev->vidq.active);

init_waitqueue_head(&dev->vidq.wq);

8)、下面是对 video_device 的操作,它算是这个函数中核心的操作:

代码语言:javascript复制
struct video_device *vfd;
vfd = video_device_alloc();
*vfd = vivi_template;
ret = video_register_device(vfd, VFL_TYPE_GRABBER, video_nr);
video_set_drvdata(vfd, dev);

8.1)、先来看这个 video_device 结构体,它在 v4l2-dev.h 中定义,显示如下:

代码语言:javascript复制
struct video_device {
    /* device ops */
    const struct v4l2_file_operations *fops;
 
    /* sysfs */
    struct device dev;    /* v4l device */
    struct cdev *cdev;    /* character device */
    
    /* Set either parent or v4l2_dev if your driver uses v4l2_device */
    struct device *parent;    /* device parent */
    struct v4l2_device *v4l2_dev;    /* v4l2_device parent */
    
    /* Control handler associated with this device node. May be NULL */
    struct v4l2_ctrl_handler *ctrl_handler;
 
    /* Priority state. If NULL, then v4l2_dev->prio will be used */
    struct v4l2_prio_state *prio;
 
    /* device info */
    char name[32];
    int vfl_type;
 
    /* 'minor' is set to -1 if the registration failed */
    int minor;
    u16 num;
    
    /* use bitops to set/clear/test flags */
    unsigned long flags;
 
    /* attribute to differentiate multiple indices on one physical device */
    int index;
    
    /* V4L2 file handles */
    spinlock_t fh_lock;    /* Lock for all v4l2_fhs */
    struct list_head fh_list;    /* List of struct v4l2_fh */
    int debug;    /* Activates debug level */
    
    /* Video standed vars */
    v4l2_std_id tvnorms;    /* Supproted tv norms */
    v4l2_std_id current_norm;    /* Current tv norm */
    
    /* callbacks */
    void (*release)(struct video_device *vdev);
    
    /* ioctl callbacks */
    const struct v4l2_ioctl_ops *ioctl_ops;
    
    /* serialization lock */
    struct mutex *lock;
};

根据注释应该能大致了解各个成员的意义。后面有这个函数的一些初始化和注册函数,里面肯定有对这个结构体成员的设置初始化等,所以我们在后面再具体分析这些成员。

8.2)、下面来看 video_device_alloc 函数。它在 v4l2-dev.c 中定义:

代码语言:javascript复制
struct video_device *video_device_alloc(void)
{
    return kzalloc(sizeof(struct video_device), GFP_KERNEL);
}
EXPORT_SYMBOL(video_device_alloc);

只是分配了一段内存,然后将它置为 0,并没有对 video_device 结构体里面的成员进行设置。

8.3)、然后 vivi.c 中下一句是 *vfd = vivi_template 在 vivi.c 搜索发现,它在前面定义:

代码语言:javascript复制
static struct video_device vivi_template = {
    .name = "vivi",
    .fops = &vivi_fops,
    .ioctl_ops = &vivi_ioctl_ops,
    .release = video_device_release,
      
    .tvnorms = V4L2_STD_525_60,
    .current_norm = V4L2_STD_NTSC_M,
};

对比 video_device 结构体中的成员,可以确定就是在这进行赋值的。它只是对其中某些成员进行了赋值。

8.3.1)、video_device 结构体中首先是 .fops = &vivi_fops,在 vivi.c 中搜索如下所示:

代码语言:javascript复制
static const struct v4l2_file_operations vivi_fops = {
    .owner = THIS_MODULE,
    .open = v4l2_fh_open,
    .release = vivi_close,
    .read = vivi_read,
    .poll = vivi_poll,
    .unlocked_ioctl = video_ioctl2, /* V4L2 ioctl handler */
    .mmap = vivi_mmap,
};

先来看这几个函数的名字,其中 open 函数和 unlocked_ioctl 函数的名字与其他的不同,直觉告诉我,他俩是系统提供的,其他的函数名字都是 vivi 开头的,应该是这个文件里面实现的函数,我们在 vivi.c 中搜索就可以找到,但是我们暂时先不具体分析这几个函数。

8.3.2)、video_device 结构体中第二个是 .ioctl_ops = &vivi_ioctl_ops ,看名字也是在 vivi.c 中定义的:

代码语言:javascript复制
static const struct v4l2_ioctl_ops vivi_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_s_std = vidioc_s_std,
    .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,
    .vidioc_log_status = v4l2_ctrl_log_status,
    .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
    .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
};

可以看到,这是一大堆的 ioctl 调用,大致可分为以下几类:

  • 查询性能 query capability 的:vidioc_querycap;
  • 对 format 的一些操作:vidioc_enum_fmt_vid_cap,vidioc_g_fmt_vid_cap,vidioc_try_fmt_vid_cap,vidioc_s_fmt_vid_cap;
  • 对缓冲区的一些操作:vidioc_reqbufs,vidioc_querybuf,vidioc_qbuf,vidioc_dqbuf;
  • 对标准 standard 的操作:vidioc_s_std;
  • 对输入 input 的操作:vidioc_enum_input,vidioc_g_input,vidiocs_s_input;
  • 对流 stream 的操作:vidioc_streamon,vidioc_streamoff;

以上几个 ioctl 调用都是需要我们自己实现的,后面 3 个 ioctl 的名字是 v4l2 开头的,应该是系统里面实现好的函数,搜索可以发现在 v4l2-ctrls.c 和 v4l2-event.c 中定义。

8.3.3)、video_device 结构体中第三个是 .release = video_device_release,它在 v4l2-dev.c 中定义,如下所示:

代码语言:javascript复制
void video_device_release(struct video_device *vdev)
{
    kfree(vdev);
}
EXPORT_SYMBOL(video_device_release);

8.3.4)、video_device 结构体中第四,五个是:

代码语言:javascript复制
.tvnorms = V4L2_STD_525_60,

.current_norm = V4L2_STD_NTSC_M,

通过看 video_device 结构体中的注释:

代码语言:javascript复制
/* Video standard vars */
v4l2_std_id tvnorms;    /* Supported tv norms */
v4l2_std_id current_norm;    /* Current tvnorm */

是指支持的 TV 制式以及当前的 TV 制式。

8.3.5)、分析完了 vivi_template 中的成员,也即 video_device 结构体中的成员,还是有点迷惑的,在 video_device 结构体中,有一个 struct v4l2_file_operation 成员,这个成员又包含一个 unlocked_ioctl,同时 video_device 结构体中还有一个 struct v4l2_ioctl_ops 成员,怎么有两个 ioctl 成员函数啊?先大致分析一些,struct v4l2_file_operations vivi_fops 中 .unlocked_ioctl = video_ioctl2,这个 video_ioctl2 在 v4l2-ioctl.c 中:

代码语言:javascript复制
long video_ioctl2(struct file *file, unsigned int cmd, unsigned long arg)
{
    return video_usercopy(file, cmd, arg, __video_do_ioctl);
}
EXPORT_SYMBOL(video_ioctl2);

它又调用了 __video_do_ioctl 函数,也在 v4l2-ioctl.c 中,这个 __video_do_ioctl 函数是一个大的 switch,case 语句,根据不同的 case,调用不同的函数,以 VIDIOC_QUERYCAP 为例:

代码语言:javascript复制
struct video_device *vfd = video_devdata(file);
const struct v4l2_ioctl_ops *ops = vfd->ioctl_ops;
case VIDIOC_QUERYCAP:
    ret = ops->vidioc_querycap(file, fh, cap);

用在 vivi.c 这个例子就是:vfd 这个结构体就是 vivi_template,Ops 就是 vivi_template 中的 ioctl_ops 成员,也就是 vivi_ioctl_ops,对于 VIDIOC_QUERCAP 宏,真正调用的是 vivi_ioctl_ops 中的 .vidiioc_querycap 成员,也即我们在 vivi.c 中自己实现的 static int vidioc_querycap(struct file *file, void *priv, struct v4l2_capability *cap) 函数。有点绕,我已晕@_@!~~ 咱们在后面再具体分析。

8.4)、继续在 vivi_create_instance 中分析:

代码语言:javascript复制
vfd->debug = debug;
vfd->v4l2_dev = &dev->v4l2_dev;

注意这个语句,从这里可以看出在注册 video_device 之前必须先注册了 v4l2_device.

代码语言:javascript复制
set_bit(V4L2_FL_USE_FH_PRIO, &vfd->flags);
vfd->lock = &dev->mutex;

进行了一些设置,然后就是大 boss 了:

代码语言:javascript复制
ret = video_register_device(vfd, VFL_TYPE_GRABBER,  video_nr);

它在 v4l2-dev.h 中定义,如下所示:

代码语言:javascript复制
static inline int __must_check video_register_device_no_warm(struct video_device *vdev, int type, int nr)
{
    return __video_register_device(vdev, type, nr, 0, vdev->fops->owner);
}

__video_register_device 在 v4l2-dev.c 中定义:(就直接在代码中注释了)

代码语言:javascript复制
/**
 *    __video_register_device - register video4linux devices
 *    @vdev: video device structure we want to register
 *    @type: type of device to register
 *    @nr: which device node number (0 == /dev/video0, 1 == /dev/video1, ...
 * -1 == first free)
 *    @warn_if_nr_in_use: warn if the desired device node number
 *     was already in use and another number was chosen instead.
 *    @owner: module that owns the video device node
 *
 *    The registration code assigns minor numbers and device node numbers
 *    based on the requested type and registers the new device node with
 *    the kernel.
 *
 *    This function assumes that struct video_device was zeroed when it
 *    was allocated and does not contain any stale date.
 *
 *    An error is returned if no free minor or device node number could be
 *    found, or if the registration of the device node failed.
 *
 *    Zero is returned on success.
 *
 *    Valid types are
 *
 *    %VFL_TYPE_GRABBER - A frame grabber
 *
 *    %VFL_TYPE_VBI - Vertical blank data (undecoded)
 *
 *    %VFL_TYPE_RADIO - A radio card
 *
 *    %VFL_TYPE_SUBDEV - A subdevice
 */
int __video_register_device(struct video_device *vdev, int type, int nr,
        int warn_if_nr_in_use, struct module *owner)
{
    int i = 0;
    int ret;
    int minor_offset = 0;
    int minor_cnt = VIDEO_NUM_DEVICES;
    const char *name_base;
 
    /* A minor value of -1 marks this video device as never
     having been registered */
    vdev->minor = -1;
 
    /* the release callback MUST be present */
    if (WARN_ON(!vdev->release))
        return -EINVAL;
/* 如果没有提供这个release函数的话,就直接返回错误,它就在vivi_template中提供了。 */
    /* v4l2_fh support */
    spin_lock_init(&vdev->fh_lock);
    INIT_LIST_HEAD(&vdev->fh_list);
 
    /* Part 1: check device type */
    switch (type) {
    case VFL_TYPE_GRABBER:
        name_base = "video";
        break;
    case VFL_TYPE_VBI:
        name_base = "vbi";
        break;
    case VFL_TYPE_RADIO:
        name_base = "radio";
        break;
    case VFL_TYPE_SUBDEV:
        name_base = "v4l-subdev";
        break;
    default:
        printk(KERN_ERR "%s called with unknown type: %dn",
         __func__, type);
        return -EINVAL;
    }
/* 根据传进来的type参数,确定设备在/dev目录下看到的名字 */
    vdev->vfl_type = type;
    vdev->cdev = NULL;
    if (vdev->v4l2_dev) {
        if (vdev->v4l2_dev->dev)
            vdev->parent = vdev->v4l2_dev->dev;
        if (vdev->ctrl_handler == NULL)
            vdev->ctrl_handler = vdev->v4l2_dev->ctrl_handler;
        /* If the prio state pointer is NULL, then use the v4l2_device
         prio state. */
        if (vdev->prio == NULL)
            vdev->prio = &vdev->v4l2_dev->prio;
    }
/* 进行vdev中父设备和ctrl处理函数的初始化。*/
    /* Part 2: find a free minor, device node number and device index. */
#ifdef CONFIG_VIDEO_FIXED_MINOR_RANGES
    /* Keep the ranges for the first four types for historical
     * reasons.
     * Newer devices (not yet in place) should use the range
     * of 128-191 and just pick the first free minor there
     * (new style). */
    switch (type) {
    case VFL_TYPE_GRABBER:
        minor_offset = 0;
        minor_cnt = 64;
        break;
    case VFL_TYPE_RADIO:
        minor_offset = 64;
        minor_cnt = 64;
        break;
    case VFL_TYPE_VBI:
        minor_offset = 224;
        minor_cnt = 32;
        break;
    default:
        minor_offset = 128;
        minor_cnt = 64;
        break;
    }
#endif
 
    /* Pick a device node number */
    mutex_lock(&videodev_lock);
    nr = devnode_find(vdev, nr == -1 ? 0 : nr, minor_cnt);
    if (nr == minor_cnt)
        nr = devnode_find(vdev, 0, minor_cnt);
    if (nr == minor_cnt) {
        printk(KERN_ERR "could not get a free device node numbern");
        mutex_unlock(&videodev_lock);
        return -ENFILE;
    }
#ifdef CONFIG_VIDEO_FIXED_MINOR_RANGES
    /* 1-on-1 mapping of device node number to minor number */
    i = nr;
#else
    /* The device node number and minor numbers are independent, so
     we just find the first free minor number. */
    for (i = 0; i < VIDEO_NUM_DEVICES; i  )
        if (video_device[i] == NULL)
            break;
    if (i == VIDEO_NUM_DEVICES) {
        mutex_unlock(&videodev_lock);
        printk(KERN_ERR "could not get a free minorn");
        return -ENFILE;
    }
#endif
    vdev->minor = i   minor_offset;
    vdev->num = nr;
    devnode_set(vdev);
 
    /* Should not happen since we thought this minor was free */
    WARN_ON(video_device[vdev->minor] != NULL);
    vdev->index = get_index(vdev);
    mutex_unlock(&videodev_lock);
/* 上面的part2就是确定设备的次设备号 */
    /* Part 3: Initialize the character device */
    vdev->cdev = cdev_alloc();
    if (vdev->cdev == NULL) {
        ret = -ENOMEM;
        goto cleanup;
    }
/* 在这进行设备的注册,用cdev_alloc函数,从这我们就可以看出来,它是一个普通的字符设备驱动,然后设置它的一些参数。怎么就是字符设备驱动了???这个在后面的v4l2框架中再说。 */
    vdev->cdev->ops = &v4l2_fops;
/* cdev结构体里面的ops指向了v4l2_fops这个结构体,这个v4l2_fops结构体也是在v4l2-dev.c这个文件中。又一个file_operations操作函数集,在vivi.c中有一个v4l2_file_operations vivi_fops,他俩又是什么关系呢? */
    vdev->cdev->owner = owner;
    ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
    if (ret < 0) {
        printk(KERN_ERR "%s: cdev_add failedn", __func__);
        kfree(vdev->cdev);
        vdev->cdev = NULL;
        goto cleanup;
    }
 
 
    /* Part 4: register the device with sysfs */
    vdev->dev.class = &video_class;
    vdev->dev.devt = MKDEV(VIDEO_MAJOR, vdev->minor);
    if (vdev->parent)
        vdev->dev.parent = vdev->parent;
    dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num);
    ret = device_register(&vdev->dev);
    if (ret < 0) {
        printk(KERN_ERR "%s: device_register failedn", __func__);
        goto cleanup;
    }
    /* Register the release callback that will be called when the last
     reference to the device goes away. */
    vdev->dev.release = v4l2_device_release;
 
    if (nr != -1 && nr != vdev->num && warn_if_nr_in_use)
        printk(KERN_WARNING "%s: requested %s%d, got %sn", __func__,
            name_base, nr, video_device_node_name(vdev));
 
    /* Increase v4l2_device refcount */
    if (vdev->v4l2_dev)
        v4l2_device_get(vdev->v4l2_dev);
/* 在sysfs中创建类,在类下创建设备结点 */
 
#if defined(CONFIG_MEDIA_CONTROLLER)
    /* Part 5: Register the entity. */
    if (vdev->v4l2_dev && vdev->v4l2_dev->mdev &&
     vdev->vfl_type != VFL_TYPE_SUBDEV) {
        vdev->entity.type = MEDIA_ENT_T_DEVNODE_V4L;
        vdev->entity.name = vdev->name;
        vdev->entity.info.v4l.major = VIDEO_MAJOR;
        vdev->entity.info.v4l.minor = vdev->minor;
        ret = media_device_register_entity(vdev->v4l2_dev->mdev,
            &vdev->entity);
        if (ret < 0)
            printk(KERN_WARNING
             "%s: media_device_register_entity failedn",
             __func__);
    }
#endif
/* 创建实体entity,这一步并不是必须的,需要配置了CONFIG_MEDIA_CONTROLLER选项后才会执行这一步,在这一步里面有一个media_entity实体结构体,在后面再分析它。 */
    /* Part 6: Activate this minor. The char device can now be used. */
    set_bit(V4L2_FL_REGISTERED, &vdev->flags);
/* 设置标志位 */
    mutex_lock(&videodev_lock);
    video_device[vdev->minor] = vdev;
/* 将设置好的video_device结构体vdev按照次设备号保存到video_device数组中。这个数组是在前面static struct video_device *video_device[VIDEO_NUM_DEVICES];定义的。 */
    mutex_unlock(&videodev_lock);
 
    return 0;
 
cleanup:
    mutex_lock(&videodev_lock);
    if (vdev->cdev)
        cdev_del(vdev->cdev);
    devnode_clear(vdev);
    mutex_unlock(&videodev_lock);
    /* Mark this video device as never having been registered. */
    vdev->minor = -1;
    return ret;
}
EXPORT_SYMBOL(__video_register_device);

8.5)、注册完 video_device 结构体后继续 vivi_create_instance 中执行:

代码语言:javascript复制
/* 将 vivi_dev dev 添加到 video_device vfd 中,为什么要这样做呢?是为了以后字符设备驱动接口的使用 */
video_set_drvdata(vfd, dev);
 
/* 添加到 device list 链表中 */
list_add_tail(&dev->vivi_devlist, &vivi_devlist);
 
/* 用于计数 */
if(video_nr != -1)
    video_nr   ;
 
/* 关联 video_device 和 vivi_dev */
dev->vfd = vfd;

三、分析总结:

到这里我们就分析完了 vivi_init 和 vivi_create_instance 函数,vivi.c 中剩下的代码,基本就是以下 3 个结构体的具体实现代码我们暂时先不分析。

代码语言:javascript复制
static struct video_device vivi_template = {
    .name        = "vivi",
    .fops = &vivi_fops,
    .ioctl_ops     = &vivi_ioctl_ops,
    .release    = video_device_release,
    .tvnorms = V4L2_STD_525_60,
    .current_norm = V4L2_STD_NTSC_M,
};
 
static const struct v4l2_ioctl_ops vivi_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_s_std = vidioc_s_std,
    .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,
    .vidioc_log_status = v4l2_ctrl_log_status,
    .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
    .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
};
 
static const struct v4l2_file_operations vivi_fops = {
    .owner        = THIS_MODULE,
    .open = v4l2_fh_open,
    .release = vivi_close,
    .read = vivi_read,
    .poll        = vivi_poll,
    .unlocked_ioctl = video_ioctl2, /* V4L2 ioctl handler */
    .mmap = vivi_mmap,
};

我们首先分析这个 vivi.c 的目的是为了先大致看一些 v4l2 驱动的代码,留一些疑问,以后分析 v4l2 的代码框架及一些概念的时候,还会以这个 vivi.c 为例子来说明。等到分析完大致的框架之后,我们再来继续仔细分析 vivi.c 中那些具体代码的实现。

实例说明:

以 read 为例,应用程序在调用 read 的时候,对应到驱动 file_operations v4l2_fops 中的 v4l2_read 函数,在函数里面通过 ret = vdev->fops->read(filp, buf, sz, off) 最后调用到我们在 vivi.c 中申请注册的 video_device vivi_template 结构体里面的 fops->read 函数,即 vivi_read 函数。即 V4L2 框架只是提供了一个中转站的效果。再看 vivi_read 函数里面:return vb2_read(&dev->vb_vidq, data, count, ppos, file->f_flags & O_NONBLOCK);它又调用了 videobuf2-core.c 中的 vb2_read 函数。确实说明了 v4l2 框架的中转作用。

这样相似的函数有read,write,poll,mmap,release 等,比较特别的是 ioctl 函数,在后面分析它。它们都是应用程序调用,通过 V4L2 框架中转到对应的驱动程序中,然后驱动程序根据不同的调用,选择调用 videobuf 或 ioctl 中的函数。

对于 const struct v4l2_ioctl_ops *ioctl_ops,在 vivi.c 中就是 static const struct v4l2_ioctl_ops vivi_ioctl_ops;这个 ioctl 更麻烦,首先作为字符设备驱动,当应用程序调用 ioctl 的时候,就调用到了 file_operations v4l2_fops 中的 .unlocked_ioctl = v4l2_ioctl,这个 v4l2_ ioctl 同样通过 ret = vdev->fops->ioctl(filp, cmd, arg) 就调用到了 vivi.c 中申请注册的 struct video_device vivi_template 结构体里面的 fops->unlocked_ioctl 函数,即 v4l2_file_operatios vivi_fops 里面的 video_ioctl2 函数,这个 video_ioctl2 函数又调用 __video_do_ioctl 函数(以上两个函数都在 v4l2-ioctl.c 中),根据不同的 cmd 宏,以 VIDIOC_QUERYCAP 为例:

通过 ret = ops->vidioc_querycap(file, fh, cap);其中 struct video_device *vfd = video_devdata(file);const struct v4l2_ioctl_ops *ops = vfd->ioctl_ops;可以看出,__video_do_ioctl 函数又调用了 struct video_device vivi_template 结构体李曼的 .ioctl_ops = &vivi_ioctl_ops,然后根据宏的名字来选择 struct v4l2_ioctl_ios vivi_ioctl_ops 中对应的函数,即 vidioc_querycap 函数。

0 人点赞