嵌入式Linux驱动笔记(十七)------详解V4L2框架(UVC驱动)

来源:互联网 发布:linux禁止ip访问网站 编辑:程序博客网 时间:2024/06/05 15:04

你好!这里是风筝的博客,

欢迎和我一起交流。


Video for Linux 2,简称V4l2,是Linux内核中关于视频设备的内核驱动框架,为上层的访问底层的视频设备提供了统一的接口。凡是内核中的子系统都有抽象底层硬件的差异,为上层提供统一的接口和提取出公共代码避免代码冗余等好处。

首先来看看所有的v4l2驱动都必须要有的几个组成部分:
– 用来描述每一个v4l2设备实例状态的结构(struct v4l2_device)。
– 用来初始化和控制子设备的方法(struct v4l2_subdev)。
– 要能创建设备节点(/dev/videoX、/dev/vbiX 和 /dev/radioX)并且能够对该节点所持有的数据进行跟踪(structvideo_device)。
– 为每一个被打开的节点维护一个文件句柄(structv4l2_fh)。
– 视频缓冲区的处理(videobuf或者videobuf2 framework)。

用一个比较粗糙的图来表现他们之间的关系,大致为:
设备实例(v4l2_device)
|__子设备实例(v4l2_subdev)
|__视频设备节点(video_device)
|__文件访问控制(v4l2_fh)
|__视频缓冲的处理(videobuf/videobuf2)

许多驱动需要与子设备通信。这些设备可以完成各种任务,但通常他们负责 音视频复用和编解码。如网络摄像头的子设备通常是传感器和摄像头控制器。
这些一般为 I2C 接口设备,但并不一定都是。为了给驱动提供调用子设备的 统一接口,v4l2_subdev 结构体(v4l2-subdev.h)产生了。
在别的文章看到的图,觉得还不错,贴一下:
风筝
可以看出:
每个子设备驱动都必须有一个 v4l2_subdev 结构体(实际的硬件设备都被抽象为v4l2_subdev),代表一个简单的子设备,也可以嵌入到一个更大的结构体中,与更多设备状态 信息保存在一起。
v4l2_device在v4l2框架中充当所有v4l2_subdev的父设备,管理着注册在其下的子设备。
因为子设备千差万别,所以v4l2-device又向上层提供一个标准的接口。所以可以认为v4l2-device就是一个中间层。

在说v4l2之前,先说下uvc吧:
USB video class(又称为USB video device class or UVC)就是USB device class视频产品在不需要安装任何的驱动程序下即插即用,包括摄像头、数字摄影机、模拟视频转换器、电视卡及静态视频相机。

V4L2就是用来管理UVC设备的并且能够提供视频相关的一些API

我们以Linux kernel 4.8.17为例,分析下实现过程:
drivers\media\usb\uvc\uvc_driver.c文件:

struct uvc_driver uvc_driver = {    .driver = {        .name       = "uvcvideo",        .probe      = uvc_probe,//支持的video设备插入就会进入        .disconnect = uvc_disconnect,        .suspend    = uvc_suspend,        .resume     = uvc_resume,        .reset_resume   = uvc_reset_resume,        .id_table   = uvc_ids,        .supports_autosuspend = 1,    },};

当特定的usb设备被插入时,就会触发probe函数:

static int uvc_probe(struct usb_interface *intf,             const struct usb_device_id *id){    struct usb_device *udev = interface_to_usbdev(intf);    struct uvc_device *dev;    int ret;    /*省略部分内容*/    if ((dev = kzalloc(sizeof *dev, GFP_KERNEL)) == NULL)//【1】        return -ENOMEM;    /*省略部分内容*/    dev->udev = usb_get_dev(udev);//【2】    dev->intf = usb_get_intf(intf);    dev->intfnum = intf->cur_altsetting->desc.bInterfaceNumber;    dev->quirks = (uvc_quirks_param == -1)            ? id->driver_info : uvc_quirks_param;    /*省略部分内容*/    /* Parse the Video Class control descriptor. */    if (uvc_parse_control(dev) < 0) {//【3】        uvc_trace(UVC_TRACE_PROBE, "Unable to parse UVC "            "descriptors.\n");        goto error;    }    /*省略部分内容*/    if (v4l2_device_register(&intf->dev, &dev->vdev) < 0)//【4】        goto error;    /* Initialize controls. */    if (uvc_ctrl_init_device(dev) < 0)//【5】        goto error;    /* Scan the device for video chains. */    if (uvc_scan_device(dev) < 0)        goto error;    /* Register video device nodes. */    if (uvc_register_chains(dev) < 0)//【6】        goto error;    /*省略部分内容*/    /* Initialize the interrupt URB. */    if ((ret = uvc_status_init(dev)) < 0) {//【7】uvc状态的处理由中断端点来控制处理    /*省略部分内容*/    return 0;error:    uvc_unregister_video(dev);    return -ENODEV;}

函数太长了,省略了部分内容,但是可以看出,主要的就是做几件事情:
【1】分配一个dev
【2】给dev设置各种参数,如dev->udevudev
【3】调用uvc_parse_control函数分析设备的控制描述符
【4】调用v4l2_device_register函数初始化v4l2_dev
【5】调用uvc_ctrl_init_device函数初始化uvc控制设备
【6】调用uvc_register_chains函数注册所有通道
【7】调用uvc_status_init函数初始化uvc状态

我们来一个个分析下:
【3】:调用uvc_parse_control函数
看下调用关系:

uvc_parse_control(dev)    uvc_parse_standard_control(dev, buffer, buflen)        uvc_parse_streaming(dev, intf)      

跟踪下uvc_parse_streaming函数:

static int uvc_parse_streaming(struct uvc_device *dev,    struct usb_interface *intf){    /*以下大部分内容省略,只显示重要的*/    struct uvc_streaming *streaming = NULL;    struct uvc_format *format;    struct uvc_frame *frame;    streaming = kzalloc(sizeof *streaming, GFP_KERNEL);    size = nformats * sizeof *format + nframes * sizeof *frame         + nintervals * sizeof *interval;    format = kzalloc(size, GFP_KERNEL);//申请format数组存放格式    streaming->format = format;//设置格式    streaming->nformats = nformats;//最多支持nformats种格式    ret = uvc_parse_format(dev, streaming, format,                &interval, buffer, buflen);//分析格式    list_add_tail(&streaming->list, &dev->streams);    return 0;}

这里面申请了streaming和format内存
streaming是uvc_streaming 结构体,视频流,很重要,大部分参数都是存在里面。这函数里申请了之后进行了很多设置,不过现在我省略了写。
format内存存放的是视频的格式,frame存放的是如分辨率
这里面都把他设置到了streaming里面(streaming->format = format;streaming->nformats = nformats;)
最后调用uvc_parse_format函数分析格式:

static int uvc_parse_format(){    fmtdesc = uvc_format_by_guid(&buffer[5]);//通过GUID找到格式format    /*里面还会对frame进行各种分析和设置,     *如设置format->nframes得出最多有多少种分辨率选择     *暂时忽略*/}

里面uvc_format_by_guid函数会从uvc_fmts数组中通过匹配guid找到格式:

static struct uvc_format_desc uvc_fmts[] = {    {        .name       = "YUV 4:2:2 (YUYV)",        .guid       = UVC_GUID_FORMAT_YUY2,        .fcc        = V4L2_PIX_FMT_YUYV,    },    {        .name       = "YUV 4:2:2 (YUYV)",        .guid       = UVC_GUID_FORMAT_YUY2_ISIGHT,        .fcc        = V4L2_PIX_FMT_YUYV,    },    {        .name       = "YUV 4:2:0 (NV12)",        .guid       = UVC_GUID_FORMAT_NV12,        .fcc        = V4L2_PIX_FMT_NV12,    },    {        .name       = "MJPEG",        .guid       = UVC_GUID_FORMAT_MJPEG,        .fcc        = V4L2_PIX_FMT_MJPEG,    },    /*后面省略......*/}

.
这样【3】的工作就完成了,我们来看下【4】的:

int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev){    INIT_LIST_HEAD(&v4l2_dev->subdevs);//用来管理v4l2_device 下的subdevs实例    spin_lock_init(&v4l2_dev->lock);    v4l2_prio_init(&v4l2_dev->prio);    kref_init(&v4l2_dev->ref);    get_device(dev);    v4l2_dev->dev = dev;    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->driver_data 域 为 NULL        dev_set_drvdata(dev, v4l2_dev);//就将其指向 v4l2_dev    return 0;}

简单,没啥好讲的,就是初始化v4l2_dev->subdevs子设备实例的链表,然后设置名字和设置dev->driver_data

看下【5】调用uvc_ctrl_init_device

int uvc_ctrl_init_device(struct uvc_device *dev){    /*省略了部分内容*/    list_for_each_entry(entity, &dev->entities, list) {        bmControls = entity->extension.bmControls;//控制位图        bControlSize = entity->extension.bControlSize;//控制位域大小        entity->controls = kcalloc(ncontrols, sizeof(*ctrl),                       GFP_KERNEL);//分配ncontrols个uvc控制内存        if (entity->controls == NULL)            return -ENOMEM;        entity->ncontrols = ncontrols;//设置uvc控制个数        /* Initialize all supported controls */        ctrl = entity->controls;//指向uvc控制数组        for (i = 0; i < bControlSize * 8; ++i) {            if (uvc_test_bit(bmControls, i) == 0)//跳过控制位域设置0的                continue;            ctrl->entity = entity;            ctrl->index = i;//设置控制位域索引            uvc_ctrl_init_ctrl(dev, ctrl);//初始化uvc控件            ctrl++;//uvc控制 指向下一个uvc控制数组项        }    }}

uvc_ctrl_init_device主要就是初始化控制参数,里面就会遍历uvc设备实体entities链表,然后设置位图和位域大小
最后还会调用uvc_ctrl_init_ctrl函数设置背光,色温等等

接下来继续看【6】调用uvc_register_chains函数:
调用关系:

uvc_register_chains    uvc_register_terms(dev, chain)        uvc_stream_by_id        uvc_register_video    uvc_mc_register_entities(chain)

uvc_stream_by_id函数会通过函数传入的id和dev->streams链表的header.bTerminalLink匹配,寻找到stream
这不是重点,我们的重点是uvc_register_video函数,找到stream会就要注册:

static int uvc_register_video(struct uvc_device *dev,        struct uvc_streaming *stream){    /*部分内容省略......*/    struct video_device *vdev = &stream->vdev;    ret = uvc_queue_init(&stream->queue, stream->type, !uvc_no_drop_param);//初始化队列    ret = uvc_video_init(stream);//初始化    uvc_debugfs_init_stream(stream);    vdev->v4l2_dev = &dev->vdev;    vdev->fops = &uvc_fops;//v4l2操作函数集    vdev->ioctl_ops = &uvc_ioctl_ops;//设置真正的ioctl操作集    vdev->release = uvc_release;//释放方法    vdev->prio = &stream->chain->prio;    strlcpy(vdev->name, dev->name, sizeof vdev->name);    video_set_drvdata(vdev, stream);//将uvc视频流作为v4l2设备的驱动数据    ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1);//注册    return 0;}

这是非常重要的函数,我们来一点一点分析:
看下uvc_queue_init函数,队列初始化,队列这东西,我们视频传输时会调用到,在ioctl里操作:

static struct vb2_ops uvc_queue_qops = {    .queue_setup = uvc_queue_setup,    .buf_prepare = uvc_buffer_prepare,    .buf_queue = uvc_buffer_queue,    .buf_finish = uvc_buffer_finish,    .wait_prepare = vb2_ops_wait_prepare,    .wait_finish = vb2_ops_wait_finish,    .start_streaming = uvc_start_streaming,    .stop_streaming = uvc_stop_streaming,};int uvc_queue_init(struct uvc_video_queue *queue, enum v4l2_buf_type type,            int drop_corrupted){    queue->queue.type = type;    queue->queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF;    queue->queue.drv_priv = queue;    queue->queue.buf_struct_size = sizeof(struct uvc_buffer);    queue->queue.ops = &uvc_queue_qops;//stream->queue->queue.ops    queue->queue.mem_ops = &vb2_vmalloc_memops;    queue->queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC        | V4L2_BUF_FLAG_TSTAMP_SRC_SOE;    queue->queue.lock = &queue->mutex;    ret = vb2_queue_init(&queue->queue);//初始化queue    mutex_init(&queue->mutex);    spin_lock_init(&queue->irqlock);    INIT_LIST_HEAD(&queue->irqqueue);//初始化stream->queue->irqqueue    queue->flags = drop_corrupted ? UVC_QUEUE_DROP_CORRUPTED : 0;    return 0;}

里面先对队列进行初始化设置,如设置type和ops。
这里queue->queue.ops = &uvc_queue_qops非常重要,之后我们调用vidioc_streamon回调函数时就是调用到这里的uvc_queue_qops结构体里的.start_streaming函数
这函数里对各种队列进行了初始化:

vb2_queue_init(&queue->queue)    q->buf_ops = &v4l2_buf_ops;    vb2_core_queue_init(struct vb2_queue *q)        INIT_LIST_HEAD(&q->queued_list);//stream->queue->queue->queued_list        INIT_LIST_HEAD(&q->done_list);//stream->queue->done_listINIT_LIST_HEAD(&queue->irqqueue);//初始化stream->queue->irqqueue

我们继续看回uvc_register_video函数,里面接着调用了uvc_video_init函数初始化UVC视频设备:

int uvc_video_init(struct uvc_streaming *stream){    /*省略部分内容*/    struct uvc_streaming_control *probe = &stream->ctrl;//获取uvc数据流的uvs数据流控制对象    if (uvc_get_video_ctrl(stream, probe, 1, UVC_GET_DEF) == 0)//先得到定义的控制参数        uvc_set_video_ctrl(stream, probe, 1);//再设置uvc视频控制    ret = uvc_get_video_ctrl(stream, probe, 1, UVC_GET_CUR);//最后在get一次    for (i = stream->nformats; i > 0; --i) {        format = &stream->format[i-1];//获取对应的uvc格式        if (format->index == probe->bFormatIndex)            break;    }    probe->bFormatIndex = format->index;//设置uvc视频流控制的格式索引为uvc格式的索引    probe->bFrameIndex = frame->bFrameIndex;//设置uvc视频流控制的分辨率索引为uvc分辨率的索引    stream->def_format = format;    stream->cur_format = format;//设置uvc格式为uvc数据流的cur_format成员    stream->cur_frame = frame;//设置uvc帧为uvc数据流的cur_frame成员    if (stream->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {//视频采集        if (stream->dev->quirks & UVC_QUIRK_BUILTIN_ISIGHT)            stream->decode = uvc_video_decode_isight;        else if (stream->intf->num_altsetting > 1)            stream->decode = uvc_video_decode_isoc;//同步方式        else            stream->decode = uvc_video_decode_bulk;//bluk方式    }     return 0;}

这里面内容就比较多了,先得到,然后设置uvc的控制参数,里面会操作urb发出usb数据。
然后通过probe->bFormatIndex索引找到使用的format格式和通过probe->bFrameIndex找到对应的frame分辨率,然后设置到stream里。
最后选择解码方式,如同步方式或者bluk方式,解码方式会在数据完成时被回调函数complete里调用。

再次回到uvc_register_video函数,没办法,这个函数太重要了:
里面继续:

    vdev->fops = &uvc_fops;//v4l2操作函数集    vdev->ioctl_ops = &uvc_ioctl_ops;//设置真正的ioctl操作集    vdev->release = uvc_release;//释放方法    ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1);

里面就是vdev->v4l2_dev = &dev->vdev;这样v4l2_device就与video_device关联起来,也就是我们文章一开始那个图看到的。
然后设置fops操作函数vdev->fops = &uvc_fops,虽然这不是给用户空间使用的open、read、write函数,但是最后vdev->cdev->ops还是最调用到这个uvc_fops的,所以用户空间实际上的pen、read、write函数还是会在这调用。 然后ioctl操作函数最终是会调用到vdev->ioctl_ops = &uvc_ioctl_ops。可以说,V4L2最重要的就是各种形式的ioctl了,这里先不讲,下一节在分析看看。
然后最终就是我们的注册函数了:video_register_device里调用到__video_register_device函数:

int __video_register_device(struct video_device *vdev, int type, int nr,        int warn_if_nr_in_use, struct module *owner){    /*省略部分函数*/    vdev->minor = -1;//-1表明这个video device从未被注册过    switch (type) {//根据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;    case VFL_TYPE_SDR:        name_base = "swradio";        break;    default:        printk(KERN_ERR "%s called with unknown type: %d\n", __func__, type);        return -EINVAL;    }    switch (type) {//选择得到次设备号偏移值    case VFL_TYPE_GRABBER://用于视频输入/输出设备的 videoX        minor_offset = 0;        minor_cnt = 64;        break;    case VFL_TYPE_RADIO://用于广播调谐器的 radioX        minor_offset = 64;        minor_cnt = 64;        break;    case VFL_TYPE_VBI://用于垂直消隐数据的 vbiX (例如,隐藏式字幕,图文电视)        minor_offset = 224;        minor_cnt = 32;        break;    default:        minor_offset = 128;        minor_cnt = 64;        break;    }    nr = devnode_find(vdev, 0, minor_cnt);//获取一个没有被使用的设备节点序号    for (i = 0; i < VIDEO_NUM_DEVICES; i++)        if (video_device[i] == NULL)//从video_device[]数组中选择一个空缺项,这个空缺项的索引值放到i中            break;    vdev->minor = i + minor_offset;//设备的次设备号    video_device[vdev->minor] = vdev;//注意:将设置好的video_device放入到video_device[]    vdev->cdev->ops = &v4l2_fops;//操作用户空间操作函数集    ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);//添加字符设备到系统    ret = device_register(&vdev->dev);//设备注册    set_bit(V4L2_FL_REGISTERED, &vdev->flags);//将flags第0为设置为1,表示这个video_device是注册过的了    return 0;}

我们梳理一下里面做的事情:
1.确定设备名称,也就是我们在/dev/下生成的video啊,radio之类的
2.得到次设备的偏移值
3.找到一个空的video_device数组,把vdev存进去
4.设置vdev->cdev,这里就设置了vdev->cdev->ops = &v4l2_fops;里面就是真正的用户空间操作集合
5.注册video_device设备
6.就是标志此video_device以注册

最后【6】调用uvc_register_chains函数里还会调用一个uvc_mc_register_entities函数,里面继续调用uvc_mc_init_entity函数,这就是v4l2_device_register_subdev函数,进行注册v4l2_subdev,同时初始化然后连接到v4l2_dev->subdevs管理。

好了,【6】调用uvc_register_chains函数:就分析完了,我们最后剩一个了:

【7】调用uvc_status_init函数

int uvc_status_init(struct uvc_device *dev){    /*省略部分函数*/    struct usb_host_endpoint *ep = dev->int_ep;//获取usb_host_endpoint    uvc_input_init(dev);//初始化uvc输入设备,里面注册input设备    dev->status = kzalloc(UVC_MAX_STATUS_SIZE, GFP_KERNEL);//分配urb设备状态内存    dev->int_urb = usb_alloc_urb(0, GFP_KERNEL);//分配urb    pipe = usb_rcvintpipe(dev->udev, ep->desc.bEndpointAddress);//中断输入端点    usb_fill_int_urb(dev->int_urb, dev->udev, pipe,        dev->status, UVC_MAX_STATUS_SIZE, uvc_status_complete,        dev, interval);//填充中断urb    return 0;}

里面就是关于urb的一些东西了,看看就好。

最后,我们用户空间怎么才操作的?
看看__video_register_device函数里的:vdev->cdev->ops = &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,    .unlocked_ioctl = v4l2_ioctl,#ifdef CONFIG_COMPAT    .compat_ioctl = v4l2_compat_ioctl32,#endif    .release = v4l2_release,    .poll = v4l2_poll,    .llseek = no_llseek,};
static int v4l2_open(struct inode *inode, struct file *filp){    /*省略部分函数*/    struct video_device *vdev;    vdev = video_devdata(filp);//根据次设备号从video_devices[]数组中得到video_device    if (vdev->fops->open) {        if (video_is_registered(vdev))            ret = vdev->fops->open(filp);//实际就是vdev->fops        else            ret = -ENODEV;    }}

记得我们之前把video_device放入到video_device[]吗?就是这里取了出来
然后调用vdev->fops->open(filp)
vdev->fops就是我们在uvc_register_video函数里设置的:
vdev->fops = &uvc_fops

const struct v4l2_file_operations uvc_fops = {//实际的用户操作    .owner      = THIS_MODULE,    .open       = uvc_v4l2_open,    .release    = uvc_v4l2_release,    .unlocked_ioctl = video_ioctl2,#ifdef CONFIG_COMPAT    .compat_ioctl32 = uvc_v4l2_compat_ioctl32,#endif    .read       = uvc_v4l2_read,    .mmap       = uvc_v4l2_mmap,    .poll       = uvc_v4l2_poll,#ifndef CONFIG_MMU    .get_unmapped_area = uvc_v4l2_get_unmapped_area,#endif};

至于这个uvc_fops 里的回调函数,特别是ioctl,这是V4L2的重头,就在下一章试着分析吧,我对这个也是比较模糊……

下一章:嵌入式Linux驱动笔记(十八)——浅析V4L2框架之ioctl

原创粉丝点击