Linux v4l2子系统(11):UVC驱动分析
UVC即USB Video Class,可以看出是基于USB接口的视频设备。相关规范在Video Class,目前最新的版本是Video Class 1.5。
uvc_driver.c:UVC驱动的主体。
uvc_v4l2.c:主要实现了uvc_fops和uvc_ioctl_ops两个数据结构。
uvc_ctrl.c:实现UVC的各种control。
uvc_debugfs.c:创建/sys/kernel/debug/usb/uvcvideo节点。
uvc_entity.c:
uvc_isight.c:对苹果iSight摄像头的支持。
uvc_queue.c:UVC的缓存管理。
uvc_status.c:
uvc_video.c:UVC视频流处理。
1. UVC驱动注册
UVC驱动程序struct uvc_driver起始就是对struct usb_driver的包裹。
struct usb_driver数据结构用于表示一个USB设备驱动。
struct uvc_driver {
struct usb_driver driver;
};
struct usb_driver {
const char *name;---------------------------------------------------驱动名称
int (*probe) (struct usb_interface *intf,
const struct usb_device_id *id);--------------------------对链接的设备进行初始化。
void (*disconnect) (struct usb_interface *intf);--------------------在设备断开后进行处理。
int (*unlocked_ioctl) (struct usb_interface *intf, unsigned int code,
void *buf);
int (*suspend) (struct usb_interface *intf, pm_message_t message);--当系统需要睡眠或者runtime睡眠时候调用,进入suspended状态。
int (*resume) (struct usb_interface *intf);-------------------------当设备被唤醒时调用。
int (*reset_resume)(struct usb_interface *intf);--------------------睡眠的设备使用复位来代替唤醒操作。
int (*pre_reset)(struct usb_interface *intf);
int (*post_reset)(struct usb_interface *intf);
const struct usb_device_id *id_table;-------------------------------设备ID列表,用于匹配以及特性设置。
struct usb_dynids dynids;
struct usbdrv_wrap drvwrap;
unsigned int no_dynamic_id:1;
unsigned int supports_autosuspend:1;--------------------------------1表示设备支持autosuspend,0表示不支持。
unsigned int disable_hub_initiated_lpm:1;
unsigned int soft_unbind:1;
};
1.1 UVC驱动初始化
uvc_init()进行UVC驱动的初始化注册,通过uvc_ids进行匹配具体设备;驱动的主要工作为设备初始化probe()、设备释放disconnect()、设备睡眠suspend()、设备唤醒resume()/reset_resume()。
uvc_cleanup()相对于uvc_init()进行反向操作。
static struct usb_device_id uvc_ids[] = { /* LogiLink Wireless Webcam */ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_INT_INFO, .idVendor = 0x0416, .idProduct = 0xa91a, .bInterfaceClass = USB_CLASS_VIDEO, .bInterfaceSubClass = 1, .bInterfaceProtocol = 0, .driver_info = UVC_QUIRK_PROBE_MINMAX }, /* Genius eFace 2025 */... /* Generic USB Video Class */ { USB_INTERFACE_INFO(USB_CLASS_VIDEO, 1, 0) }, {} }; MODULE_DEVICE_TABLE(usb, uvc_ids); struct uvc_driver uvc_driver = { .driver = { .name = "uvcvideo", .probe = uvc_probe, .disconnect = uvc_disconnect, .suspend = uvc_suspend, .resume = uvc_resume, .reset_resume = uvc_reset_resume, .id_table = uvc_ids, .supports_autosuspend = 1, }, }; static int __init uvc_init(void) { int ret; uvc_debugfs_init();--------------------------------注册调试节点 ret = usb_register(&uvc_driver.driver);------------注册uvcvideo USB设备驱动。 if (ret < 0) { uvc_debugfs_cleanup(); return ret; } printk(KERN_INFO DRIVER_DESC " (" DRIVER_VERSION ")\n"); return 0; } static void __exit uvc_cleanup(void) { usb_deregister(&uvc_driver.driver); uvc_debugfs_cleanup(); } module_init(uvc_init); module_exit(uvc_cleanup);
struct uvc_device {
struct usb_device *udev;---------------------------usb设备指针
struct usb_interface *intf;------------------------usb接口指针
unsigned long warnings;
__u32 quirks;
int intfnum;---------------------------------------usb接口数目
char name[32];-------------------------------------uvc设备名
struct mutex lock; /* Protects users */
unsigned int users;
atomic_t nmappings;
/* Video control interface */
#ifdef CONFIG_MEDIA_CONTROLLER
struct media_device mdev;
#endif
struct v4l2_device vdev;
__u16 uvc_version;----------------------------------uvc协议版本
__u32 clock_frequency;------------------------------设备时钟频率
struct list_head entities;--------------------------挂着Terminal或者Unit的uvc实体链表头
struct list_head chains;----------------------------uvc视频设备链表头
/* Video Streaming interfaces */
struct list_head streams;---------------------------uvc视频流链表头
atomic_t nstreams;
/* Status Interrupt Endpoint */
struct usb_host_endpoint *int_ep;
struct urb *int_urb;---------------------------------中断urb
__u8 *status;----------------------------------------uvc设备状态标志
struct input_dev *input;-----------------------------输入设备
char input_phys[64];---------------------------------输入设备设备节点路径
};
static int uvc_probe(struct usb_interface *intf, const struct usb_device_id *id) { struct usb_device *udev = interface_to_usbdev(intf);-----------------------通过usb接口usb_interface获取usb设备usb_device struct uvc_device *dev; int ret; if (id->idVendor && id->idProduct) uvc_trace(UVC_TRACE_PROBE, "Probing known UVC device %s " "(%04x:%04x)\n", udev->devpath, id->idVendor, id->idProduct); else uvc_trace(UVC_TRACE_PROBE, "Probing generic UVC device %s\n", udev->devpath); /* Allocate memory for the device and initialize it. */ if ((dev = kzalloc(sizeof *dev, GFP_KERNEL)) == NULL)----------------------分配uvc_device设备内存。 return -ENOMEM; INIT_LIST_HEAD(&dev->entities);--------------------------------------------初始化entities实体链表,Terminal或Unit。 INIT_LIST_HEAD(&dev->chains);----------------------------------------------初始化chains链表。 INIT_LIST_HEAD(&dev->streams);---------------------------------------------初始化视频流链表。 atomic_set(&dev->nstreams, 0); atomic_set(&dev->nmappings, 0); mutex_init(&dev->lock); dev->udev = usb_get_dev(udev);---------------------------------------------捆绑usb设备,并增加udev引用计数。 dev->intf = usb_get_intf(intf);--------------------------------------------捆绑usb解耦,并增加intf引用计数。 dev->intfnum = intf->cur_altsetting->desc.bInterfaceNumber;----------------获取usb接口描述符接口数 dev->quirks = (uvc_quirks_param == -1) ? id->driver_info : uvc_quirks_param; if (udev->product != NULL) strlcpy(dev->name, udev->product, sizeof dev->name); else snprintf(dev->name, sizeof dev->name, "UVC Camera (%04x:%04x)", le16_to_cpu(udev->descriptor.idVendor), le16_to_cpu(udev->descriptor.idProduct)); /* Parse the Video Class control descriptor. */ if (uvc_parse_control(dev) < 0) {------------------------------------------解析uvc视频类控制描述符。 uvc_trace(UVC_TRACE_PROBE, "Unable to parse UVC " "descriptors.\n"); goto error; } uvc_printk(KERN_INFO, "Found UVC %u.%02x device %s (%04x:%04x)\n", dev->uvc_version >> 8, dev->uvc_version & 0xff, udev->product ? udev->product : "<unnamed>", le16_to_cpu(udev->descriptor.idVendor), le16_to_cpu(udev->descriptor.idProduct)); if (dev->quirks != id->driver_info) { uvc_printk(KERN_INFO, "Forcing device quirks to 0x%x by module " "parameter for testing purpose.\n", dev->quirks); uvc_printk(KERN_INFO, "Please report required quirks to the " "linux-uvc-devel mailing list.\n"); } /* Register the media and V4L2 devices. */ #ifdef CONFIG_MEDIA_CONTROLLER dev->mdev.dev = &intf->dev; strlcpy(dev->mdev.model, dev->name, sizeof(dev->mdev.model)); if (udev->serial) strlcpy(dev->mdev.serial, udev->serial, sizeof(dev->mdev.serial)); strcpy(dev->mdev.bus_info, udev->devpath); dev->mdev.hw_revision = le16_to_cpu(udev->descriptor.bcdDevice); dev->mdev.driver_version = LINUX_VERSION_CODE; if (media_device_register(&dev->mdev) < 0) goto error; dev->vdev.mdev = &dev->mdev; #endif if (v4l2_device_register(&intf->dev, &dev->vdev) < 0) goto error; /* Initialize controls. */ if (uvc_ctrl_init_device(dev) < 0)-----------------------------------------对解析出来的控制进行初始化。 goto error; /* Scan the device for video chains. */ if (uvc_scan_device(dev) < 0)----------------------------------------------uvc扫描视频链。 goto error; /* Register video device nodes. */ if (uvc_register_chains(dev) < 0)------------------------------------------注册视频链设备。 goto error; /* Save our data pointer in the interface data. */ usb_set_intfdata(intf, dev);-----------------------------------------------将usb_interface->dev->driver_data指向dev。 /* Initialize the interrupt URB. */ if ((ret = uvc_status_init(dev)) < 0) {------------------------------------uvc设备状态初始化。 uvc_printk(KERN_INFO, "Unable to initialize the status " "endpoint (%d), status interrupt will not be " "supported.\n", ret); } uvc_trace(UVC_TRACE_PROBE, "UVC device initialized.\n"); usb_enable_autosuspend(udev);----------------------------------------------使能autosuspend。 return 0; error: uvc_unregister_video(dev); return -ENODEV; }
1.2 驱动模块参数
通过UVC模块的参数可以对驱动的行为进行控制,进行一些辅助调试。
2. uvc_fops和uvc_ioctl_ops
由video_register_device()分析可知v4l2字符设备操作函数集v4l2_fops调用uvc_fops,其中ioctl系统调用对应的video_ioctl2()调用uvc_ioctl_ops函数集。
static int uvc_register_video(struct uvc_device *dev,
struct uvc_streaming *stream)
{
...
vdev->fops = &uvc_fops;
vdev->ioctl_ops = &uvc_ioctl_ops;
...
ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
...
}
在《Linux v4l2专题 (1)v4l2框架分析》中对v4l2框架进行了分析,但是不适合讨论具体的系统调用对应关系。
此处结合《Linux v4l2专题 (3)基于v4l2+OpenCV的视频应用》场景应用,对从用户空间发起的操作,到内核的关系进行梳理。
2.1 uvc_fops
v4l2_fops相当于提供了一个框架,具体实现都落在具体驱动中,比如uvc_fops。
比如v4l2_open()调用uvc_v4l2_open(),两个操作函数集存在一一对应的关系。
const struct v4l2_file_operations uvc_fops = { .owner = THIS_MODULE, .open = uvc_v4l2_open,-----------------------v4l2_open() .release = uvc_v4l2_release,---------------------v4l2_release() .unlocked_ioctl = video_ioctl2,------------------v4l2_ioctl() #ifdef CONFIG_COMPAT .compat_ioctl32 = uvc_v4l2_compat_ioctl32,-------v4l2_compat_ioctl32() #endif .read = uvc_v4l2_read,-----------------------v4l2_read() .mmap = uvc_v4l2_mmap,-----------------------v4l2_mmap() .poll = uvc_v4l2_poll,-----------------------v4l2_poll() #ifndef CONFIG_MMU .get_unmapped_area = uvc_v4l2_get_unmapped_area,----get_unmapped_area() #endif };
2.2 ioctl:uvc_ioctl_ops
video_ioctl2()根据传入的cmd参数,到v4l2_ioctls[]中找对应的struct v4l2_ioctl_info数据结构。
从一个v4l2编程实例,重点分析常用的ioctl命令。v4l2_ioctls[]中的命令主要任务还是交给具体驱动的ioctl函数,比如uvc_ioctl_ops。
const struct v4l2_ioctl_ops uvc_ioctl_ops = { .vidioc_querycap = uvc_ioctl_querycap,----------------------------------VIDIOC_QUERYCAP .vidioc_enum_fmt_vid_cap = uvc_ioctl_enum_fmt_vid_cap,------------------VIDIOC_ENUM_FMT .vidioc_enum_fmt_vid_out = uvc_ioctl_enum_fmt_vid_out, .vidioc_g_fmt_vid_cap = uvc_ioctl_g_fmt_vid_cap,------------------------VIDIOC_G_FMT .vidioc_g_fmt_vid_out = uvc_ioctl_g_fmt_vid_out, .vidioc_s_fmt_vid_cap = uvc_ioctl_s_fmt_vid_cap,------------------------VIDIOC_S_FMT .vidioc_s_fmt_vid_out = uvc_ioctl_s_fmt_vid_out, .vidioc_try_fmt_vid_cap = uvc_ioctl_try_fmt_vid_cap,--------------------VIDIOC_TRY_FMT .vidioc_try_fmt_vid_out = uvc_ioctl_try_fmt_vid_out, .vidioc_reqbufs = uvc_ioctl_reqbufs,------------------------------------VIDIOC_REQBUFS .vidioc_querybuf = uvc_ioctl_querybuf,----------------------------------VIDIOC_QUERYBUF .vidioc_qbuf = uvc_ioctl_qbuf,------------------------------------------VIDIOC_QBUF .vidioc_expbuf = uvc_ioctl_expbuf,--------------------------------------VIDIOC_EXPBUF .vidioc_dqbuf = uvc_ioctl_dqbuf,----------------------------------------VIDIOC_DQBUF .vidioc_create_bufs = uvc_ioctl_create_bufs,----------------------------VIDIOC_CREATE_BUFS .vidioc_streamon = uvc_ioctl_streamon,----------------------------------VIDIOC_STREAMON .vidioc_streamoff = uvc_ioctl_streamoff,--------------------------------VIDIOC_STREAMOFF .vidioc_enum_input = uvc_ioctl_enum_input,------------------------------VIDIOC_ENUMINPUT .vidioc_g_input = uvc_ioctl_g_input,------------------------------------VIDIOC_G_INPUT .vidioc_s_input = uvc_ioctl_s_input,------------------------------------VIDIOC_S_INPUT .vidioc_queryctrl = uvc_ioctl_queryctrl,--------------------------------VIDIOC_QUERYCTRL .vidioc_query_ext_ctrl = uvc_ioctl_query_ext_ctrl,----------------------VIDIOC_QUERY_EXT_CTRL .vidioc_g_ctrl = uvc_ioctl_g_ctrl,--------------------------------------VIDIOC_G_CTRL .vidioc_s_ctrl = uvc_ioctl_s_ctrl,--------------------------------------VIDIOC_S_CTRL .vidioc_g_ext_ctrls = uvc_ioctl_g_ext_ctrls, .vidioc_s_ext_ctrls = uvc_ioctl_s_ext_ctrls, .vidioc_try_ext_ctrls = uvc_ioctl_try_ext_ctrls, .vidioc_querymenu = uvc_ioctl_querymenu, .vidioc_g_selection = uvc_ioctl_g_selection, .vidioc_g_parm = uvc_ioctl_g_parm,--------------------------------------VIDIOC_G_PARM .vidioc_s_parm = uvc_ioctl_s_parm,--------------------------------------VIDIOC_S_PARM .vidioc_enum_framesizes = uvc_ioctl_enum_framesizes,--------------------VIDIOC_ENUM_FRAMESIZES .vidioc_enum_frameintervals = uvc_ioctl_enum_frameintervals,------------VIDIOC_ENUM_FRAMEINTERVALS .vidioc_subscribe_event = uvc_ioctl_subscribe_event, .vidioc_unsubscribe_event = v4l2_event_unsubscribe, .vidioc_default = uvc_ioctl_default, };
2.2.1 查询设备驱动能力:VIDIOC_QUERYCAP
ioctl命令VIDIOC_QUERYCAP对应的调用:v4l_querycap()->uvc_ioctl_querycap(),和内核交流的数据结构是struct v4l2_capability。
struct v4l2_capability {
__u8 driver[16];-----------------驱动名称
__u8 card[32];-------------------即video_device->name
__u8 bus_info[32];---------------设备所在总线信息
__u32 version;--------------------Linux内核版本号
__u32 capabilities;--------------设备支持的操作,常用的有V4L2_CAP_VIDEO_CAPTURE/V4L2_CAP_VIDEO_OUTPUT
__u32 device_caps;
__u32 reserved[3];
};
static int v4l_querycap(const struct v4l2_ioctl_ops *ops,
struct file *file, void *fh, void *arg)
{
struct v4l2_capability *cap = (struct v4l2_capability *)arg;
int ret;
cap->version = LINUX_VERSION_CODE;
ret = ops->vidioc_querycap(file, fh, cap);
cap->capabilities |= V4L2_CAP_EXT_PIX_FORMAT;
WARN(!(cap->capabilities & V4L2_CAP_DEVICE_CAPS) ||
!cap->device_caps, "Bad caps for driver %s, %x %x",
cap->driver, cap->capabilities, cap->device_caps);-------------------对没有设置V4L2_CAP_DEVICE_CAPS打印警告信息。
cap->device_caps |= V4L2_CAP_EXT_PIX_FORMAT;
return ret;
}
static int uvc_ioctl_querycap(struct file *file, void *fh,
struct v4l2_capability *cap)
{
struct video_device *vdev = video_devdata(file);
struct uvc_fh *handle = file->private_data;
struct uvc_video_chain *chain = handle->chain;
struct uvc_streaming *stream = handle->stream;
strlcpy(cap->driver, "uvcvideo", sizeof(cap->driver));
strlcpy(cap->card, vdev->name, sizeof(cap->card));
usb_make_path(stream->dev->udev, cap->bus_info, sizeof(cap->bus_info));---根据usb_device格式化输出一个bus_info。
cap->capabilities = V4L2_CAP_DEVICE_CAPS | V4L2_CAP_STREAMING-------------V4L2_CAP_DEVICE_CAPS说明存在device_caps设置,V4L2_CAP_STREAMING表示通过ioctl进行straming。
| chain->caps;
if (stream->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;-------V4L2_CAP_VIDEO_CAPTURE表示设备是一个capture设备,V4L2_CAP_STREAMING表示可以通过ioctl进行streaming控制。
else
cap->device_caps = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING;
return 0;
}
2.2.2 VIDIOC_ENUM_FMT
v4l_enum_fmt()->uvc_ioctl_enum_fmt_vid_cap()->uvc_ioctl_enum_fmt()
2.2.3 VIDIOC_S_FMT
v4l_s_fmt()->uvc_ioctl_s_fmt_vid_cap()->uvc_v4l2_set_format()->uvc_v4l2_try_format()
2.2.4 VIDIOC_G_FMT
v4l_g_fmt()->uvc_ioctl_g_fmt_vid_cap()->uvc_v4l2_get_format()
2.2.5 VIDIOC_REQBUFS
ioctl命令VIDIOC_REQBUFS在内核调用:v4l_reqbufs()->uvc_ioctl_reqbufs()->uvc_request_buffers()->vb2_reqbufs()->vb2_core_reqbufs()->__vb2_queue_alloc()->__vb2_buf_mem_alloc()->vb2_vmalloc_alloc()。
从用户空间传入struct v4l2_request_buffers数据结构,对内核提出要求。内核根据count数目和memory处理方式(mmap, DMA等)进行缓存相关初始化。
这里有一个层级概念,一个v4l2_requestbuffers有若干个vb2_buffer,一个vb2_buffer可能有若干个plane。
其中_vb2_buf_mem_alloc()和__vb2_buf_mem_free()是一对逆操作,分别调用vb2_vmalloc_alloc()和vb2_vmalloc_put(),这两个函数调用内核特殊的内存分配,虚拟地址连续,初始化为0,适合用户空间使用的内存。
分配的帧缓冲区大小由()得到。
struct v4l2_requestbuffers {
__u32 count;------------------------------------------------申请缓冲区帧的数目
__u32 type; /* enum v4l2_buf_type */-----------------缓冲区数据格式
__u32 memory; /* enum v4l2_memory */-----------------内存处理方式,V4L2_MEMORY_MMAP,V4L2_MEMORY_USERPTR等。
__u32 reserved[2];
};
static int uvc_ioctl_reqbufs(struct file *file, void *fh,
struct v4l2_requestbuffers *rb)
{
struct uvc_fh *handle = fh;
struct uvc_streaming *stream = handle->stream;
int ret;
...
ret = uvc_request_buffers(&stream->queue, rb);---------------------------------------通过fh找到queue
...
}
int vb2_reqbufs(struct vb2_queue *q, struct v4l2_requestbuffers *req)
{
int ret = vb2_verify_memory_type(q, req->memory, req->type);-------------------------对req->memory和req->type做一些兼容性检查
return ret ? ret : vb2_core_reqbufs(q, req->memory, &req->count);
}
int vb2_core_reqbufs(struct vb2_queue *q, enum vb2_memory memory,
unsigned int *count)
{
unsigned int num_buffers, allocated_buffers, num_planes = 0;
int ret;
if (q->streaming) {
dprintk(1, "streaming active\n");
return -EBUSY;
}
.../*
* Make sure the requested values and current defaults are sane.
*/
num_buffers = min_t(unsigned int, *count, VB2_MAX_FRAME);----------------------------取count和VB2_MAX_FRAME小者。
num_buffers = max_t(unsigned int, num_buffers, q->min_buffers_needed);---------------取min_buffers_needed和num_buffers大者。
memset(q->plane_sizes, 0, sizeof(q->plane_sizes));
memset(q->alloc_ctx, 0, sizeof(q->alloc_ctx));
q->memory = memory;
/*
* Ask the driver how many buffers and planes per buffer it requires.
* Driver also sets the size and allocator context for each plane.
*/
ret = call_qop(q, queue_setup, q, NULL, &num_buffers, &num_planes,
q->plane_sizes, q->alloc_ctx);-------------------------------------------获取plane_sizes,下面内存分配需要。调用uvc_queue_ops->queue_setup()即uvc_queue_setup(),设置num_planes为1,plane_sizes为stream->ctrl.dwMaxVideoFrameSize。
if (ret)
return ret;
/* Finally, allocate buffers and video memory */
allocated_buffers =
__vb2_queue_alloc(q, memory, num_buffers, num_planes);--------------------------根据缓存类型memory,缓存个数num_buffers,每个缓存中plane数目num_planes开始申请相关内存,返回申请到的allocated_buffers。
if (allocated_buffers == 0) {
dprintk(1, "memory allocation failed\n");
return -ENOMEM;
}
/*
* There is no point in continuing if we can't allocate the minimum
* number of buffers needed by this vb2_queue.
*/
if (allocated_buffers < q->min_buffers_needed)--------------------------------------分配到的缓存数目不能小于当前vb2_queue定义的min_buffers_needed。
ret = -ENOMEM;
/*
* Check if driver can handle the allocated number of buffers.
*/
if (!ret && allocated_buffers < num_buffers) {--------------------------------------在不能满足分配num_buffers个缓存的情况下,且个数小于min_buffers_needed,重新进行queue_setup()操作。
num_buffers = allocated_buffers;
ret = call_qop(q, queue_setup, q, NULL, &num_buffers,
&num_planes, q->plane_sizes, q->alloc_ctx);
if (!ret && allocated_buffers < num_buffers)
ret = -ENOMEM;
/*
* Either the driver has accepted a smaller number of buffers,
* or .queue_setup() returned an error
*/
}
mutex_lock(&q->mmap_lock);
q->num_buffers = allocated_buffers;
if (ret < 0) {
/*
* Note: __vb2_queue_free() will subtract 'allocated_buffers'
* from q->num_buffers.
*/
__vb2_queue_free(q, allocated_buffers);-----------------------------------------经过努力之后还是失败,则释放申请的内存。
mutex_unlock(&q->mmap_lock);
return ret;
}
mutex_unlock(&q->mmap_lock);
/*
* Return the number of successfully allocated buffers
* to the userspace.
*/
*count = allocated_buffers;---------------------------------------------------------count返回给用户空间参数,告诉用户实际分配到的缓存数目。
q->waiting_for_buffers = !q->is_output;
return 0;
}
static int __vb2_queue_alloc(struct vb2_queue *q, enum vb2_memory memory,
unsigned int num_buffers, unsigned int num_planes)
{
unsigned int buffer;
struct vb2_buffer *vb;
int ret;
/* Ensure that q->num_buffers+num_buffers is below VB2_MAX_FRAME */
num_buffers = min_t(unsigned int, num_buffers,
VB2_MAX_FRAME - q->num_buffers);
for (buffer = 0; buffer < num_buffers; ++buffer) {----------------------------------循环处理num_buffers个缓存
/* Allocate videobuf buffer structures */
vb = kzalloc(q->buf_struct_size, GFP_KERNEL);
if (!vb) {
dprintk(1, "memory alloc for buffer struct failed\n");
break;
}
vb->state = VB2_BUF_STATE_DEQUEUED;---------------------------------------------对vb2_buffer进行初始化
vb->vb2_queue = q;
vb->num_planes = num_planes;
vb->index = q->num_buffers + buffer;
vb->type = q->type;
vb->memory = memory;
/* Allocate video buffer memory for the MMAP type */
if (memory == VB2_MEMORY_MMAP) {------------------------------------------------用户空间传入的参数v4l2_requestbuffer->memory
ret = __vb2_buf_mem_alloc(vb);
if (ret) {
dprintk(1, "failed allocating memory for "
"buffer %d\n", buffer);
kfree(vb);
break;
}
ret = call_vb_qop(vb, buf_init, vb);
if (ret) {
dprintk(1, "buffer %d %p initialization"
" failed\n", buffer, vb);
__vb2_buf_mem_free(vb);
kfree(vb);
break;
}
}
q->bufs[q->num_buffers + buffer] = vb;------------------------------------------num_buffers+buffer是下标,对应的vb2_buffer为上面配置好的vb。
}
__setup_lengths(q, buffer);---------------------------------------------------------更新buffer对应缓存的plane的vb2_plane->length
if (memory == VB2_MEMORY_MMAP)
__setup_offsets(q, buffer);
dprintk(1, "allocated %d buffers, %d plane(s) each\n",
buffer, num_planes);
return buffer;
}
static int __vb2_buf_mem_alloc(struct vb2_buffer *vb)
{
struct vb2_queue *q = vb->vb2_queue;
enum dma_data_direction dma_dir =
q->is_output ? DMA_TO_DEVICE : DMA_FROM_DEVICE;
void *mem_priv;
int plane;
/*
* Allocate memory for all planes in this buffer
* NOTE: mmapped areas should be page aligned
*/
for (plane = 0; plane < vb->num_planes; ++plane) {---------------------------------再轮询处理此buffer中的不同plane。
unsigned long size = PAGE_ALIGN(q->plane_sizes[plane]);------------------------这里的size在uvc_queue_setup()里面进行设置。
mem_priv = call_ptr_memop(vb, alloc, q->alloc_ctx[plane],
size, dma_dir, q->gfp_flags);------------------------------------调用vb2_buffer->mem_ops->alloc,即vb2_vmalloc_alloc()。返回指向struct vb2_vmalloc_buf数据结构的指针。
if (IS_ERR_OR_NULL(mem_priv))
goto free;
/* Associate allocator private data with this plane */
vb->planes[plane].mem_priv = mem_priv;
vb->planes[plane].length = q->plane_sizes[plane];
}
return 0;
free:
/* Free already allocated memory if one of the allocations failed */
for (; plane > 0; --plane) {
call_void_memop(vb, put, vb->planes[plane - 1].mem_priv);----------------------plane中分配的内存首地址和大小,即vb2_vmalloc_put()。
vb->planes[plane - 1].mem_priv = NULL;
}
return -ENOMEM;
}
static int __vb2_queue_free(struct vb2_queue *q, unsigned int buffers)
{
...
/* Call driver-provided cleanup function for each buffer, if provided */
for (buffer = q->num_buffers - buffers; buffer < q->num_buffers;
++buffer) {
struct vb2_buffer *vb = q->bufs[buffer];
if (vb && vb->planes[0].mem_priv)
call_void_vb_qop(vb, buf_cleanup, vb);------------------调用vb2_buffer->vb2_queue->ops->buf_cleanup()
}
/* Release video buffer memory */
__vb2_free_mem(q, buffers);-------------------------------------释放vb2_buffer中的vb2_plane相关内存。
...
/* Free videobuf buffers */
for (buffer = q->num_buffers - buffers; buffer < q->num_buffers;
++buffer) {
kfree(q->bufs[buffer]);-------------------------------------释放vb2_queue上的vb2_buffer。
q->bufs[buffer] = NULL;
}
q->num_buffers -= buffers;
if (!q->num_buffers) {
q->memory = 0;
INIT_LIST_HEAD(&q->queued_list);
}
return 0;
}
static void __vb2_free_mem(struct vb2_queue *q, unsigned int buffers)
{
unsigned int buffer;
struct vb2_buffer *vb;
for (buffer = q->num_buffers - buffers; buffer < q->num_buffers;
++buffer) {
vb = q->bufs[buffer];
if (!vb)
continue;
/* Free MMAP buffers or release USERPTR buffers */
if (q->memory == VB2_MEMORY_MMAP)
__vb2_buf_mem_free(vb);-----------------------------VB2_MEMORY_MMAP类型缓存释放
else if (q->memory == VB2_MEMORY_DMABUF)
__vb2_buf_dmabuf_put(vb);
else
__vb2_buf_userptr_put(vb);
}
}
static void __vb2_buf_mem_free(struct vb2_buffer *vb)
{
unsigned int plane;
for (plane = 0; plane < vb->num_planes; ++plane) {
call_void_memop(vb, put, vb->planes[plane].mem_priv);---调用vb2_buffer->mem_ops->put,由uvc_queue_init()可知mem_ops对应vb2_vmalloc_memops,此处即调用vb2_vmalloc_put()函数。
vb->planes[plane].mem_priv = NULL;
dprintk(3, "freed plane %d of buffer %d\n", plane, vb->index);
}
}
static void __setup_lengths(struct vb2_queue *q, unsigned int n) { unsigned int buffer, plane; struct vb2_buffer *vb; for (buffer = q->num_buffers; buffer < q->num_buffers + n; ++buffer) { vb = q->bufs[buffer]; if (!vb) continue; for (plane = 0; plane < vb->num_planes; ++plane) vb->planes[plane].length = q->plane_sizes[plane]; } } static void __setup_offsets(struct vb2_queue *q, unsigned int n) { unsigned int buffer, plane; struct vb2_buffer *vb; unsigned long off; if (q->num_buffers) { struct vb2_plane *p; vb = q->bufs[q->num_buffers - 1]; p = &vb->planes[vb->num_planes - 1]; off = PAGE_ALIGN(p->m.offset + p->length);-------------------------------计算从q->num_buffers开始的buffer偏移量。 } else { off = 0;-----------------------------------------------------------------如果没有已经分配的缓存,则从0开始。 } for (buffer = q->num_buffers; buffer < q->num_buffers + n; ++buffer) {-------开始轮询设置剩下缓存的offset。 vb = q->bufs[buffer]; if (!vb) continue; for (plane = 0; plane < vb->num_planes; ++plane) { vb->planes[plane].m.offset = off;------------------------------------前一个vb2_buffer或者vb2_plane的偏移量 dprintk(3, "buffer %d, plane %d offset 0x%08lx\n", buffer, plane, off); off += vb->planes[plane].length;-------------------------------------为下一个buffer或者plane的偏移做准备。 off = PAGE_ALIGN(off);-----------------------------------------------对偏移量进行页面对齐。 } } }
2.2.6 VIDIOC_QUERYBUF
v4l_querybuf()->uvc_ioctl_querybuf()->()->vb2_querybuf()->vb2_core_querybuf()->__fill_v4l2_buffer()
struct v4l2_buffer {
__u32 index;-------------------------------当前帧缓冲区序号,在v4l2_requestbuffers.count范围内。
__u32 type;--------------------------------缓冲区数据格式
__u32 bytesused;---------------------------已填充区域大小
__u32 flags;
__u32 field;
struct timeval timestamp;----------------------获取第一个字节的系统时间
struct v4l2_timecode timecode;
__u32 sequence;
/* memory location */
__u32 memory;------------------------------缓冲区内存处理方式
union {
__u32 offset;---------------------------缓冲区起始地址
unsigned long userptr;
struct v4l2_plane *planes;
__s32 fd;
} m;
__u32 length;------------------------------帧缓冲区的大小
__u32 reserved2;
__u32 reserved;
};
int vb2_querybuf(struct vb2_queue *q, struct v4l2_buffer *b)
{
struct vb2_buffer *vb;
int ret;
...
vb = q->bufs[b->index];
ret = __verify_planes_array(vb, b);--------------------------是否支持多plane,这里不支持,返回0。
return ret ? ret : vb2_core_querybuf(q, b->index, b);
}
int vb2_core_querybuf(struct vb2_queue *q, unsigned int index, void *pb)
{
return call_bufop(q, fill_user_buffer, q->bufs[index], pb);
}
static int __fill_v4l2_buffer(struct vb2_buffer *vb, void *pb)-------参数pb是struct v4l2_buffer,即将返回给用户空间参数;参数vb是vb2_buffer,是内核维护的数据。
{
struct v4l2_buffer *b = pb;
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
struct vb2_queue *q = vb->vb2_queue;
unsigned int plane;
/* Copy back data such as timestamp, flags, etc. */
b->index = vb->index;----------------------------------当前缓存index
b->type = vb->type;
b->memory = vb->memory;
b->bytesused = 0;--------------------------------------还没有被使用,所以bytesused为0。
b->flags = vbuf->flags;
b->field = vbuf->field;
b->timestamp = vbuf->timestamp;
b->timecode = vbuf->timecode;
b->sequence = vbuf->sequence;
b->reserved2 = 0;
b->reserved = 0;
if (q->is_multiplanar) {
...
} else {----------------------------------------------对于单plane情况,length和bytesused都固定,主要是memory不同地址有区别。
b->length = vb->planes[0].length;-----------------当前缓存的大小,在__setup_length()中进行设置。
b->bytesused = vb->planes[0].bytesused;
if (q->memory == VB2_MEMORY_MMAP)
b->m.offset = vb->planes[0].m.offset;---------当前缓存的偏移量,也即当前缓存的起始地址。在__vb2_queue_alloc中调用__setup_offsets()进行设置。
else if (q->memory == VB2_MEMORY_USERPTR)
b->m.userptr = vb->planes[0].m.userptr;
else if (q->memory == VB2_MEMORY_DMABUF)
b->m.fd = vb->planes[0].m.fd;
}
/*
* Clear any buffer state related flags.
*/
b->flags &= ~V4L2_BUFFER_MASK_FLAGS;
b->flags |= q->timestamp_flags & V4L2_BUF_FLAG_TIMESTAMP_MASK;
if ((q->timestamp_flags & V4L2_BUF_FLAG_TIMESTAMP_MASK) !=
V4L2_BUF_FLAG_TIMESTAMP_COPY) {
/*
* For non-COPY timestamps, drop timestamp source bits
* and obtain the timestamp source from the queue.
*/
b->flags &= ~V4L2_BUF_FLAG_TSTAMP_SRC_MASK;
b->flags |= q->timestamp_flags & V4L2_BUF_FLAG_TSTAMP_SRC_MASK;
}
switch (vb->state) {---------------------------------状态机的设置
case VB2_BUF_STATE_QUEUED:
case VB2_BUF_STATE_ACTIVE:
b->flags |= V4L2_BUF_FLAG_QUEUED;
break;
case VB2_BUF_STATE_ERROR:
b->flags |= V4L2_BUF_FLAG_ERROR;
/* fall through */
case VB2_BUF_STATE_DONE:
b->flags |= V4L2_BUF_FLAG_DONE;
break;
case VB2_BUF_STATE_PREPARED:
b->flags |= V4L2_BUF_FLAG_PREPARED;
break;
case VB2_BUF_STATE_PREPARING:
case VB2_BUF_STATE_DEQUEUED:
case VB2_BUF_STATE_REQUEUEING:
/* nothing */
break;
}
if (vb2_buffer_in_use(q, vb))
b->flags |= V4L2_BUF_FLAG_MAPPED;
return 0;
}
2.2.7 VIDIOC_QBUF
命令VIDIOC_QBUF将序号为v4l2_buffer->index的缓冲区放入vb2_queue->queued_list中。
调用轨迹如下:v4l_qbuf()->uvc_ioctl_qbuf()->uvc_queue_buffer()->vb2_qbuf()->vb2_internal_qbuf()->vb2_core_qbuf()。
static int vb2_internal_qbuf(struct vb2_queue *q, struct v4l2_buffer *b) { int ret = vb2_queue_or_prepare_buf(q, b, "qbuf"); return ret ? ret : vb2_core_qbuf(q, b->index, b);---------------------用户空间传入的参数b->index是缓存的序号。 } int vb2_core_qbuf(struct vb2_queue *q, unsigned int index, void *pb) { struct vb2_buffer *vb; int ret; vb = q->bufs[index];--------------------------------------------------根据用户空间传入缓存序号,在vb2_queue上找到对应的vb2_buffer。 switch (vb->state) { case VB2_BUF_STATE_DEQUEUED: ret = __buf_prepare(vb, pb); if (ret) return ret; break; case VB2_BUF_STATE_PREPARED: break; case VB2_BUF_STATE_PREPARING: dprintk(1, "buffer still being prepared\n"); return -EINVAL; default: dprintk(1, "invalid buffer state %d\n", vb->state); return -EINVAL; } /* * Add to the queued buffers list, a buffer will stay on it until * dequeued in dqbuf. */ list_add_tail(&vb->queued_entry, &q->queued_list); q->queued_count++; q->waiting_for_buffers = false; vb->state = VB2_BUF_STATE_QUEUED; call_bufop(q, set_timestamp, vb, pb); trace_vb2_qbuf(q, vb); /* * If already streaming, give the buffer to driver for processing. * If not, the buffer will be given to driver on next streamon. */ if (q->start_streaming_called) __enqueue_in_driver(vb); /* Fill buffer information for the userspace */ ret = call_bufop(q, fill_user_buffer, vb, pb); if (ret) return ret; /* * If streamon has been called, and we haven't yet called * start_streaming() since not enough buffers were queued, and * we now have reached the minimum number of queued buffers, * then we can finally call start_streaming(). */ if (q->streaming && !q->start_streaming_called && q->queued_count >= q->min_buffers_needed) { ret = vb2_start_streaming(q); if (ret) return ret; } dprintk(1, "qbuf of buffer %d succeeded\n", vb->index); return 0; } static int __buf_prepare(struct vb2_buffer *vb, const void *pb) { struct vb2_queue *q = vb->vb2_queue; int ret; if (q->error) { dprintk(1, "fatal error occurred on queue\n"); return -EIO; } vb->state = VB2_BUF_STATE_PREPARING; switch (q->memory) { case VB2_MEMORY_MMAP: ret = __qbuf_mmap(vb, pb); break; case VB2_MEMORY_USERPTR: ret = __qbuf_userptr(vb, pb); break; case VB2_MEMORY_DMABUF: ret = __qbuf_dmabuf(vb, pb); break; default: WARN(1, "Invalid queue type\n"); ret = -EINVAL; } if (ret) dprintk(1, "buffer preparation failed: %d\n", ret); vb->state = ret ? VB2_BUF_STATE_DEQUEUED : VB2_BUF_STATE_PREPARED; return ret; } static int __qbuf_mmap(struct vb2_buffer *vb, const void *pb) { int ret = call_bufop(vb->vb2_queue, fill_vb2_buffer, vb, pb, vb->planes);-----------------------------------调用__fill_vb2_buffer()。 return ret ? ret : call_vb_qop(vb, buf_prepare, vb); } static void __enqueue_in_driver(struct vb2_buffer *vb) { struct vb2_queue *q = vb->vb2_queue; unsigned int plane; vb->state = VB2_BUF_STATE_ACTIVE; atomic_inc(&q->owned_by_drv_count); trace_vb2_buf_queue(q, vb); /* sync buffers */ for (plane = 0; plane < vb->num_planes; ++plane) call_void_memop(vb, prepare, vb->planes[plane].mem_priv); call_void_vb_qop(vb, buf_queue, vb); }
2.2.8 VIDIOC_DQBUF
命令VIDIOC_QBUF是将已经填充好的vb2_buffer从vb2_queue->done_list中取出,交给用户空间进行处理。
调用轨迹如下:v4l_dqbuf()->uvc_ioctl_dqbuf()->uvc_dequeue_buffer()->vb2_dqbuf()->vb2_internal_dqbuf()->vb2_core_dqbuf()。
vb2_core_dqbuf()是核心操作函数,取出缓存给用户空间使用。
VIDIOC_DQBUF本身并不执行内存的搬移动作,它会睡眠等待缓存。
static int uvc_ioctl_dqbuf(struct file *file, void *fh, struct v4l2_buffer *buf) { struct uvc_fh *handle = fh; struct uvc_streaming *stream = handle->stream; if (!uvc_has_privileges(handle)) return -EBUSY; return uvc_dequeue_buffer(&stream->queue, buf, file->f_flags & O_NONBLOCK);-----------------------取打开/dev/videoX文件的标志位,是否nonblock方式打开。 } int vb2_core_dqbuf(struct vb2_queue *q, void *pb, bool nonblocking) { struct vb2_buffer *vb = NULL; int ret; ret = __vb2_get_done_vb(q, &vb, pb, nonblocking);-----------将vb从缓存队列q中移除,返回给用户空间使用。 if (ret < 0) return ret; switch (vb->state) { case VB2_BUF_STATE_DONE: dprintk(3, "returning done buffer\n"); break; case VB2_BUF_STATE_ERROR: dprintk(3, "returning done buffer with errors\n"); break; default: dprintk(1, "invalid buffer state\n"); return -EINVAL; } call_void_vb_qop(vb, buf_finish, vb);-----------------------调用uvc_buffer_finish()。 /* Fill buffer information for the userspace */ ret = call_bufop(q, fill_user_buffer, vb, pb);--------------调用__fill_v4l2_buffer()根据参数vb填充struct v4l2_buffer类型的pb,这样用户空间就得到了缓存的地址。 if (ret) return ret; /* Remove from videobuf queue */ list_del(&vb->queued_entry);--------------------------------将当前缓存从vb2_buffer->queued_list中移除。 q->queued_count--; trace_vb2_dqbuf(q, vb); /* go back to dequeued state */ __vb2_dqbuf(vb);--------------------------------------------设置当前缓存的状态为dequeued。 dprintk(1, "dqbuf of buffer %d, with state %d\n", vb->index, vb->state); return 0; } static int __vb2_get_done_vb(struct vb2_queue *q, struct vb2_buffer **vb, void *pb, int nonblocking) { unsigned long flags; int ret = 0; /* * Wait for at least one buffer to become available on the done_list. */ ret = __vb2_wait_for_done_vb(q, nonblocking);-------------------------------------至少等待一个缓存可用,即done_list不为空。 if (ret) return ret; /* * Driver's lock has been held since we last verified that done_list * is not empty, so no need for another list_empty(done_list) check. */ spin_lock_irqsave(&q->done_lock, flags); *vb = list_first_entry(&q->done_list, struct vb2_buffer, done_entry);--------------取done_list第一个元素,vb2_buffer指针。 if (pb) ret = call_bufop(q, verify_planes_array, *vb, pb);-----------------------------调用__verify_planes_array()对vb2_buffer中的plane进行检查,对于非multi plane返回0。 if (!ret) list_del(&(*vb)->done_entry);--------------------------------------------------在ret为0的情况下,将当前vb2_buffer从vb2_queue->done_list中移除。 spin_unlock_irqrestore(&q->done_lock, flags); return ret; } static int __vb2_wait_for_done_vb(struct vb2_queue *q, int nonblocking) { for (;;) { int ret; ... if (!list_empty(&q->done_list)) {--------------------------------------如果done_list为空,则继续for循环;不为空,则跳出当前循环。 /* * Found a buffer that we were waiting for. */ break; } if (nonblocking) { dprintk(1, "nonblocking and no buffers to dequeue, " "will not wait\n"); return -EAGAIN; } call_void_qop(q, wait_prepare, q);------------------------------------调用vb2_ops_wait_prepare(),释放vb2_queue->lock。 dprintk(3, "will sleep waiting for buffers\n"); ret = wait_event_interruptible(q->done_wq, !list_empty(&q->done_list) || !q->streaming || q->error);----------------------------------------------------此处进行睡眠等待,当缓存准备好之后,会被唤醒。谁唤醒?在什么地方唤醒? call_void_qop(q, wait_finish, q);-------------------------------------调用vb2_ops_wait_finish(),获取vb2_queue->lock。 if (ret) { dprintk(1, "sleep was interrupted\n"); return ret; } } return 0; } static void __vb2_dqbuf(struct vb2_buffer *vb) { struct vb2_queue *q = vb->vb2_queue; unsigned int i; /* nothing to do if the buffer is already dequeued */ if (vb->state == VB2_BUF_STATE_DEQUEUED) return; vb->state = VB2_BUF_STATE_DEQUEUED;----------------------------------------设置vb2_buffer->state为dequeued。 ... }
2.2.9 VIDIOC_STREAMON
v4l_streamon()->uvc_ioctl_streamon()->uvc_queue_streamon()->vb2_streamon()->vb2_core_streamon()->vb2_start_streaming()->call_qop(q, start_streaming,...)->uvc_start_streaming()
2.2.10 VIDIOC_STREAMOFF
v4l_streamoff()->uvc_ioctl_streamoff()->uvc_queue_streamoff()->vb2_streamoff()->vb2_core_streamoff()->__vb2_queue_cancel()->uvc_stop_streaming()
2.3 mmap
v4l2_mmap()->uvc_v4l2_mmap()->uvc_queue_mmap()->vb2_mmap->vb2_vmalloc_mmap()
int vb2_mmap(struct vb2_queue *q, struct vm_area_struct *vma)
{
unsigned long off = vma->vm_pgoff << PAGE_SHIFT;
struct vb2_buffer *vb;
unsigned int buffer = 0, plane = 0;
int ret;
unsigned long length;
if (q->memory != VB2_MEMORY_MMAP) {
dprintk(1, "queue is not currently set up for mmap\n");
return -EINVAL;
}
/*
* Check memory area access mode.
*/
if (!(vma->vm_flags & VM_SHARED)) {
dprintk(1, "invalid vma flags, VM_SHARED needed\n");
return -EINVAL;
}
if (q->is_output) {
if (!(vma->vm_flags & VM_WRITE)) {
dprintk(1, "invalid vma flags, VM_WRITE needed\n");
return -EINVAL;
}
} else {
if (!(vma->vm_flags & VM_READ)) {
dprintk(1, "invalid vma flags, VM_READ needed\n");
return -EINVAL;
}
}
if (vb2_fileio_is_active(q)) {
dprintk(1, "mmap: file io in progress\n");
return -EBUSY;
}
/*
* Find the plane corresponding to the offset passed by userspace.
*/
ret = __find_plane_by_offset(q, off, &buffer, &plane);---------------------根据偏移地址off,在q中找到对应的buffer和plane。
if (ret)
return ret;
vb = q->bufs[buffer];------------------------------------------------------根据buffer,在q中找到vb2_buffer数据结构。
/*
* MMAP requires page_aligned buffers.
* The buffer length was page_aligned at __vb2_buf_mem_alloc(),
* so, we need to do the same here.
*/
length = PAGE_ALIGN(vb->planes[plane].length);
if (length < (vma->vm_end - vma->vm_start)) {
dprintk(1,
"MMAP invalid, as it would overflow buffer length\n");
return -EINVAL;
}
mutex_lock(&q->mmap_lock);
ret = call_memop(vb, mmap, vb->planes[plane].mem_priv, vma);--------------调用vb2_vmalloc_mmap()进行
mutex_unlock(&q->mmap_lock);
if (ret)
return ret;
dprintk(3, "buffer %d, plane %d successfully mapped\n", buffer, plane);
return 0;
}
2.4 poll
v4l2_poll()->uvc_v4l2_poll()->uvc_queue_poll()->vb2_poll()
2.5 open
v4l2_open()->uvc_v4l2_open()
2.6 close
v4l2_release()->uvc_v4l2_release()
3. UVC video buffers queue management
3.1 缓存队列初始化
int uvc_queue_init(struct uvc_video_queue *queue, enum v4l2_buf_type type,
int drop_corrupted)
{
int ret;
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;--------------------------------------缓存队列的操作方式
queue->queue.mem_ops = &vb2_vmalloc_memops;------------------------------缓存的操作方式vmalloc
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);
if (ret)
return ret;
mutex_init(&queue->mutex);
spin_lock_init(&queue->irqlock);
INIT_LIST_HEAD(&queue->irqqueue);
queue->flags = drop_corrupted ? UVC_QUEUE_DROP_CORRUPTED : 0;
return 0;
}
void uvc_queue_release(struct uvc_video_queue *queue)
{
mutex_lock(&queue->mutex);
vb2_queue_release(&queue->queue);
mutex_unlock(&queue->mutex);
}
int vb2_queue_init(struct vb2_queue *q) { /* * Sanity check */ if (WARN_ON(!q) || WARN_ON(q->timestamp_flags & ~(V4L2_BUF_FLAG_TIMESTAMP_MASK | V4L2_BUF_FLAG_TSTAMP_SRC_MASK))) return -EINVAL; /* Warn that the driver should choose an appropriate timestamp type */ WARN_ON((q->timestamp_flags & V4L2_BUF_FLAG_TIMESTAMP_MASK) == V4L2_BUF_FLAG_TIMESTAMP_UNKNOWN); /* Warn that vb2_memory should match with v4l2_memory */ if (WARN_ON(VB2_MEMORY_MMAP != (int)V4L2_MEMORY_MMAP) || WARN_ON(VB2_MEMORY_USERPTR != (int)V4L2_MEMORY_USERPTR) || WARN_ON(VB2_MEMORY_DMABUF != (int)V4L2_MEMORY_DMABUF)) return -EINVAL; if (q->buf_struct_size == 0) q->buf_struct_size = sizeof(struct vb2_v4l2_buffer); q->buf_ops = &v4l2_buf_ops; q->is_multiplanar = V4L2_TYPE_IS_MULTIPLANAR(q->type); q->is_output = V4L2_TYPE_IS_OUTPUT(q->type); return vb2_core_queue_init(q); }
3.2 uvc_queue_qops
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, };
4. 调用路径跟踪脚本
结合之前对v4l2框架和UVC驱动的分析,v4l2设备首先是字符设备,那么对这些设备的操作都经过v4l2_fops,具体到UVC驱动经过uvc_fops,其中的ioctl经过uvc_ioctl_ops;v4l2_subdev相关操作经过uvc_subdev_ops;v4l2_event相关操作经过uvc_ctrl_sub_ev_ops。
其中videobuf相关队列操作经过uvc_queue_qops,对缓存的操作经过vb2_vmalloc_memops。
v4l2_fops:v4l2_read v4l2_write v4l2_open v4l2_get_unmapped_area v4l2_mmap v4l2_ioctl v4l2_compat_ioctl32 v4l2_release v4l2_poll no_llseek
uvc_fops:uvc_v4l2_open uvc_v4l2_release video_ioctl2 uvc_v4l2_compat_ioctl32 uvc_v4l2_read uvc_v4l2_mmap uvc_v4l2_poll uvc_v4l2_get_unmapped_area
uvc_ioctl_ops:uvc_ioctl_* v4l2_event_unsubscribe
uvc_queue_qops:uvc_queue_setup uvc_buffer_prepare uvc_buffer_queue uvc_buffer_finish vb2_ops_wait_prepare vb2_ops_wait_finish uvc_start_streaming uvc_stop_streaming
vb2_vmalloc_memops:vb2_vmalloc_*
#!/bin/bash
DPATH="/sys/kernel/debug/tracing"
PID=$$
## Quick basic checks
[ `id -u` -ne 0 ] && { echo "needs to be root" ; exit 1; } # check for root permissions
[ -z $1 ] && { echo "needs process name as argument" ; exit 1; } # check for args to this function
mount | grep -i debugfs &> /dev/null
[ $? -ne 0 ] && { echo "debugfs not mounted, mount it first"; exit 1; } #checks for debugfs mount
# flush existing trace data
echo nop > $DPATH/current_tracer
echo > $DPATH/set_ftrace_filter
echo " SyS_* vfs_* do_vfs_ioctl vm_mmap_pgoff do_mmap" >> $DPATH/set_ftrace_filter
echo "v4l2_open v4l2_release v4l2_read v4l2_write v4l2_poll v4l2_ioctl v4l2_mmap v4l2_compat_ioctl32" >> $DPATH/set_ftrace_filter
echo "uvc_v4l2_open uvc_v4l2_release video_ioctl2 uvc_v4l2_compat_ioctl32 uvc_v4l2_read uvc_v4l2_mmap uvc_v4l2_poll" >> $DPATH/set_ftrace_filter
echo "uvc_ioctl_*" >> $DPATH/set_ftrace_filter
echo "video_ioctl2 __video_do_ioctl " >> $DPATH/set_ftrace_filter
echo "v4l_* v4l2_* uvc_* uvc_queue_*" >> $DPATH/set_ftrace_filter
echo "vb2_* vb2_queue_* __vb2_*" >> $DPATH/set_ftrace_filter
echo "vb2_vmalloc_* __fill_v4l2_*" >> $DPATH/set_ftrace_filter
echo "usb_*" >> $DPATH/set_ftrace_filter
# set function tracer
echo function_graph > $DPATH/current_tracer
# write current process id to set_ftrace_pid file
echo $PID > $DPATH/set_ftrace_pid
# start the tracing
echo 1 > $DPATH/tracing_on
# execute the process
exec $*
#sudo cat $DPATH/trace > /home/al/v4l2/trace.txt
通过脚本执行capture_my:
sudo ./traceprocess.sh ./capture_my
然后读取/sys/kernel/debug/tracing/trace保存。
- 梳理流程,了解每个系统调用都做什么?从用户空间到v4l2层、到具体UVC驱动、到VideoBuffer、到具体的USB操作。
- 分析个操作性能,每个关键节点的耗时;找出耗时点,优化。
- 尝试进行效果调试,通过哪些ioctl命令?
参考文档:
《The Linux USB Video Class (UVC) driver》
《Linux摄像头驱动学习之:(四)UVC-摄像头驱动框架分析》、《Linux摄像头驱动学习之:(五)UVC-分析设备描述符》、《Linux摄像头驱动学习之:(六)UVC-基本框架代码分析》
《uvc摄像头代码解析1》、《uvc摄像头代码解析2》、《uvc摄像头代码解析3》、《uvc摄像头代码解析4》、《uvc摄像头代码解析5》、《uvc摄像头代码解析6》、《uvc摄像头代码解析7》