LXR | KVM | PM | Time | Interrupt | Systems Performance | Bootup Optimization

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

uvc摄像头代码解析之描述符

posted on 2024-04-16 23:59  ArnoldLu  阅读(50)  评论(0编辑  收藏  举报

导航