UVC 驱动调用过程与驱动框架的简单分析

Posted lxk0825

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了UVC 驱动调用过程与驱动框架的简单分析相关的知识,希望对你有一定的参考价值。

UVC 驱动整体调用流程:

/* 打开设备描述符 */
1. open:
        uvc_v4l2_open

/* 查询设备属性 */
2. VIDIOC_QUERYCAP
        if (video->streaming->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
            cap->capabilities = V4L2_CAP_VIDEO_CAPTURE
                      | V4L2_CAP_STREAMING;
        else
            cap->capabilities = V4L2_CAP_VIDEO_OUTPUT
                      | V4L2_CAP_STREAMING;

/* 枚举设备支持的格式 */
3. VIDIOC_ENUM_FMT
        format = &video->streaming->format[fmt->index];

/* 得到设备当前所使用的 format 与 frame */
4. VIDIOC_G_FMT
        uvc_v4l2_get_format
            struct uvc_format *format = video->streaming->cur_format;
            struct uvc_frame *frame = video->streaming->cur_frame;

/* Check if the hardware supports the requested format. */
5. VIDIOC_TRY_FMT
        uvc_v4l2_try_format
             
/* 设置数据,此时并没有真正的设置,而是在启动视频流时将数据发送给设备 */
6. VIDIOC_S_FMT
        uvc_v4l2_set_format
            uvc_v4l2_try_format
            video->streaming->cur_format = format;
            video->streaming->cur_frame = frame;

/* 分配视频缓冲区 */
7. VIDIOC_REQBUFS
        uvc_alloc_buffers
            for (; nbuffers > 0; --nbuffers) {
                mem = vmalloc_32(nbuffers * bufsize);
                if (mem != NULL)
                    break;
            }

/* 查询并获取到分配的缓冲区信息 */
8. VIDIOC_QUERYBUF
        uvc_query_buffer
            __uvc_query_buffer

/* 映射缓冲区地址到用户空间 */
9. mmap
        uvc_v4l2_mmap

/* 将 V4L2 信息块放入队列 */
10. VIDIOC_QBUF
        uvc_queue_buffer
            list_add_tail(&buf->queue, &queue->irqqueue);
            list_add_tail(&buf->stream, &queue->mainqueue);

/* 设置设备并启动视频流 */
11. VIDIOC_STREAMON
        uvc_video_enable(video, 1)
            /* Commit the streaming parameters. */
            uvc_commit_video
                /* 设置 format, frame */
                uvc_set_video_ctrl  
                    
            /* 启动:Initialize isochronous/bulk URBs and allocate transfer buffers. */
            uvc_init_video(video, GFP_KERNEL);
                    uvc_init_video_isoc / uvc_init_video_bulk
                        
                    usb_submit_urb    

/* 休眠等待数据 */
12. poll
        uvc_v4l2_poll            
            uvc_queue_poll
                poll_wait(file, &buf->wait, wait);

/* 取出信息块 */
13. VIDIOC_DQBUF
        uvc_dequeue_buffer
            list_del(&buf->stream);

/* 关闭视频流 */
14. VIDIOC_STREAMOFF            
        uvc_video_enable(video, 0);
            usb_kill_urb(urb);
            usb_free_urb(urb);
驱动分析,首先找到 UVC 驱动的入口点:
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,
    },
};

usb_register(&uvc_driver.driver);

 

注册了 usb 驱动,如果系统中出现了与其 id_table 匹配的设备,则驱动会与它建立关系并调用 probe 函数:

我们选择比较重要的函数进行分析,在 probe 中的函数调用:

uvc_register_chains -> uvc_register_terms -> uvc_register_video(对类型为 UVC_TT_STREAMING 的video调用本函数)

uvc_register_video 函数如下:

static int uvc_register_video(struct uvc_device *dev,
        struct uvc_streaming *stream)
{
    struct video_device *vdev;
    int ret;

    /* 对 video 做一些初始化 */
    ret = uvc_video_init(stream);
    if (ret < 0) {
        uvc_printk(KERN_ERR, "Failed to initialize the device "
            "(%d).
", ret);
        return ret;
    }

    uvc_debugfs_init_stream(stream);

    /* 分配一个 video device */
    vdev = video_device_alloc();
    if (vdev == NULL) {
        uvc_printk(KERN_ERR, "Failed to allocate video device (%d).
",
               ret);
        return -ENOMEM;
    }

    /* 配置 video device */
    vdev->v4l2_dev = &dev->vdev;
    vdev->fops = &uvc_fops;
    vdev->release = uvc_release;
    strlcpy(vdev->name, dev->name, sizeof vdev->name);

    stream->vdev = vdev;
    video_set_drvdata(vdev, stream);

    /* 注册 video device */
    ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
    if (ret < 0) {
        uvc_printk(KERN_ERR, "Failed to register video device (%d).
",
               ret);
        stream->vdev = NULL;
        video_device_release(vdev);
        return ret;
    }

    atomic_inc(&dev->nstreams);
    return 0;
}

在这里面有个重要的结构,即:

const struct v4l2_file_operations uvc_fops = {
    .owner      = THIS_MODULE,
    .open       = uvc_v4l2_open,
    .release    = uvc_v4l2_release,
    .unlocked_ioctl = uvc_v4l2_ioctl,
#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_v4l2_ioctl 就是实现 V4L2 操作的函数集。

这个 unlocked_ioctl 属性的赋值需要注意一下,当它为 uvc_v4l2_ioctl(当前内核所使用)时,调用的是 uvc_v4l2.c 中现有的函数; 但如果将它赋值为 video_ioctl2 时,内核将调用我们在驱动中 vdev 的 ioctl_ops 属性所赋给的函数集进行操作。

__video_do_ioctl 中:
    const struct v4l2_ioctl_ops *ops = vfd->ioctl_ops; 之后就调用 ops 中的一系列函数来操作 video

所以这一系列的函数可以由我们自己来编写。

以上是关于UVC 驱动调用过程与驱动框架的简单分析的主要内容,如果未能解决你的问题,请参考以下文章

杂记4--森云相机UVC驱动编译过程一些问题解决记录

28从零写UVC驱动之实现设置属性

由于uvc驱动函数缺少return语句而导致内核Oops一例

由于uvc驱动函数缺少return语句而导致内核oops的一例

有人用arm开发板用UVC驱动的USB camera采集到640*480的视频过吗?

tty初探—uart驱动框架分析uart_add_one_port