Linux中UVC驱动源码学习

这个系列后面应该会写很多篇文章。

此为第一篇。

源码路径

UVC源码路径为 drivers/media/usb/uvc

V4L2源码路径为 drivers/media/v4l2-core

前言

学习UVC源码可以先看一下uvc_v4l2.c文件中的const struct v4l2_ioctl_ops, const struct v4l2_file_operations 这两个结构体的实例化。

其中v4l2_ioctl_ops中保存了v4l2框架中函数指针和uvc驱动中功能函数的对应关系,这有助于我们将UVC驱动和V4L2框架结合起来理解。

而v4l2_file_operations结构体则是定义了用户空间对设备文件进行操作的接口。

另外,UVC源码可以配合V4L2部分源码一起看,有助于我们理解我们在应用层调用接口后,其执行流程。其中,V4L2外部接口与内部函数的对应关系我们可以在v4l2_ioctl.c文件中的static const struct v4l2_ioctl_info结构体的实例化中看到。

另外,这里还需要说明的是,UVC中的扩展单元无法直接在V4L2框架中找到与之对应的指令函数。这和我们在用户空间使用ioctl,其内部的调用顺序有关。我们可以在UVCvideo.h中找到扩展单元对应的接口:UVCIOC_CTRL_QUERY的宏定义以及另一个宏定义:UVCIOC_CTRL_MAP。然后,我们从上述所说的v4l2_file_operations结构体的实例化中可以看到:

const struct v4l2_file_operations uvc_fops = {
	.owner		= THIS_MODULE,
	.open		= uvc_v4l2_open,
	.release	= uvc_v4l2_release,
	.unlocked_ioctl	= video_ioctl2,
#ifdef CONFIG_COMPAT
	.compat_ioctl32	= uvc_v4l2_compat_ioctl32,
#endif
	.read		= uvc_v4l2_read,
	.mmap		= uvc_v4l2_mmap,
	.poll		= uvc_v4l2_poll,
#ifndef CONFIG_MMU
	.get_unmapped_area = uvc_v4l2_get_unmapped_area,
#endif
};
 

对于提供给用户空间的ioctl接口,使用的是video_ioctl12 / uvc_v4l2_compat_ioctl32函数(这二者分别在32位系统和64位系统下调用,这里随便以一个举例说明),然后我们再进入video_ioctl12 函数中可以看到其内部调用的是video_usercopy()函数,再进入此函数查看,可以发现其最终是通过一个通过形参传入的函数指针去执行,这个传入的函数便是 __video_do_ioctl函数。

static long __video_do_ioctl(struct file *file,
        unsigned int cmd, void *arg)
{
    struct video_device *vfd = video_devdata(file);
    struct mutex *req_queue_lock = NULL;
    struct mutex *lock; /* ioctl serialization mutex */
    const struct v4l2_ioctl_ops *ops = vfd->ioctl_ops;
    bool write_only = false;
    struct v4l2_ioctl_info default_info;
    const struct v4l2_ioctl_info *info;
    void *fh = file->private_data;
    struct v4l2_fh *vfh = NULL;
    int dev_debug = vfd->dev_debug;
    long ret = -ENOTTY;
​
    if (ops == NULL) {
        pr_warn("%s: has no ioctl_ops.\n",
                video_device_node_name(vfd));
        return ret;
    }
​
    if (test_bit(V4L2_FL_USES_V4L2_FH, &vfd->flags))
        vfh = file->private_data;
​
    /*
     * We need to serialize streamon/off with queueing new requests.
     * These ioctls may trigger the cancellation of a streaming
     * operation, and that should not be mixed with queueing a new
     * request at the same time.
     */
    if (v4l2_device_supports_requests(vfd->v4l2_dev) &&
        (cmd == VIDIOC_STREAMON || cmd == VIDIOC_STREAMOFF)) {
        req_queue_lock = &vfd->v4l2_dev->mdev->req_queue_mutex;
​
        if (mutex_lock_interruptible(req_queue_lock))
            return -ERESTARTSYS;
    }
​
    lock = v4l2_ioctl_get_lock(vfd, vfh, cmd, arg);
​
    if (lock && mutex_lock_interruptible(lock)) {
        if (req_queue_lock)
            mutex_unlock(req_queue_lock);
        return -ERESTARTSYS;
    }
​
    if (!video_is_registered(vfd)) {
        ret = -ENODEV;
        goto unlock;
    }
​
    if (v4l2_is_known_ioctl(cmd)) {                             //判断该指令是否为v4l2所知道,若知道,则将对应控制信息赋值给info
        info = &v4l2_ioctls[_IOC_NR(cmd)];
​
        if (!is_valid_ioctl(vfd, cmd) &&
            !((info->flags & INFO_FL_CTRL) && vfh && vfh->ctrl_handler))
            goto done;
​
        if (vfh && (info->flags & INFO_FL_PRIO)) {
            ret = v4l2_prio_check(vfd->prio, vfh->prio);
            if (ret)
                goto done;
        }
    } else {
        default_info.ioctl = cmd;
        default_info.flags = 0;
        default_info.debug = v4l_print_default;
        info = &default_info;
    }
​
    write_only = _IOC_DIR(cmd) == _IOC_WRITE;
    if (info != &default_info) {
        ret = info->func(ops, file, fh, arg);                       //若是v4l2所知道的指令,调用对应的任务函数
    } else if (!ops->vidioc_default) {
        ret = -ENOTTY;
    } else {
        ret = ops->vidioc_default(file, fh,                         //若不知道,则调用vidioc_default所指向的函数,该函数指向uvc_ioctl_default,最后经过层层调用,最终会执行扩展单元对应的相关任务函数。
            vfh ? v4l2_prio_check(vfd->prio, vfh->prio) >= 0 : 0,
            cmd, arg);
    }
​
done:
    if (dev_debug & (V4L2_DEV_DEBUG_IOCTL | V4L2_DEV_DEBUG_IOCTL_ARG)) {
        if (!(dev_debug & V4L2_DEV_DEBUG_STREAMING) &&
            (cmd == VIDIOC_QBUF || cmd == VIDIOC_DQBUF))
            goto unlock;
​
        v4l_printk_ioctl(video_device_node_name(vfd), cmd);
        if (ret < 0)
            pr_cont(": error %ld", ret);
        if (!(dev_debug & V4L2_DEV_DEBUG_IOCTL_ARG))
            pr_cont("\n");
        else if (_IOC_DIR(cmd) == _IOC_NONE)
            info->debug(arg, write_only);
        else {
            pr_cont(": ");
            info->debug(arg, write_only);
        }
    }
​
unlock:
    if (lock)
        mutex_unlock(lock);
    if (req_queue_lock)
        mutex_unlock(req_queue_lock);
    return ret;
}

随后该函数中会对cmd进行判断,若v4l2中包含此指令,则会直接通过v4l2_ioctls结构体(此结构体初始化前面也有提到)拿到该命令对应的 v4l2_ioctl_info,随后就可以直接通过调用其中的函数指针(这个函数指针在初始化时已经被赋值为了对应指令的任务函数)来执行任务啦。

而对于V4L2不知道的指令,该函数最后调用了v4l2_ioctl_ops结构体中的 vidioc_default指针来实现具体功能。ok,现在再跳转到这个vidioc_default指针所指向的结构体中去看看里面到底是怎么实现的(v4l2_ioctl_ops 结构体实例化前面已经说过了,函数指针就是在其初始化时赋值的),ok,我们回到其实例化时的代码,可以看到,该函数指针指向的是 uvc_ioctl_default这个函数,又继续找到这个函数的定义之处。内部实现很简单,其将cmd分为了两类,一类是UVCIOC_CTRL_MAP, 另一类就是 UVCIOC_CTRL_QUERY了,这二者的区别还待后续研究

下一篇文章我会从 uvc_ctrl.c开始讲起。另外这其中的许多错误,还希望各位大佬不吝赐教,本人也是本承着一边学习一边记录的心态。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值