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

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

欢迎和我一起交流。


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_list
INIT_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

评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值