这个系列后面应该会写很多篇文章。
此为第一篇。
源码路径
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开始讲起。另外这其中的许多错误,还希望各位大佬不吝赐教,本人也是本承着一边学习一边记录的心态。