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

2019-07-13 03:15发布

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

欢迎和我一起交流。


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为例,分析下实现过程:
driversmediausbuvcuvc_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. "); 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函数设置背光, {MOD}温等等 接下来继续看【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 ", __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