前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >V4L2 实例分析 —— vivi.c 源码详解(深度好文)

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

作者头像
用户6280468
发布2024-05-17 14:15:57
1510
发布2024-05-17 14:15:57
举报
文章被收录于专栏:txp玩Linuxtxp玩Linux

一、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 font\n");
        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 driver\n", 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-%03d", 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 %s\n",
         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: %d\n",
         __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 number\n");
        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 minor\n");
        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 failed\n", __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 failed\n", __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 %s\n", __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 failed\n",
             __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 函数。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2024-05-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 txp玩Linux 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、V4L2 驱动核心:
  • 二、虚拟视频驱动程序 vivi.c 源码分析
    • 2.1、分析一个程序从它的 init 入口函数开始分析:
      • 2.2、vivi_create_instance(i) 函数:
      • 三、分析总结:
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档