上次培训时讲到,V4L2的功能大部分都是通过ioctl导出给用户使用的。那么今天就看看这些ioctl是如何对应到驱动中的代码的。
下面以VIDIOC_QUERYCAP为例讲解。
static const struct file_operations v4l2_fops = { .owner = THIS_MODULE, .read = v4l2_read, .write = v4l2_write, .open = v4l2_open, .get_unmapped_area = v4l2_get_unmapped_area, .mmap = v4l2_mmap, .unlocked_ioctl = v4l2_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = v4l2_compat_ioctl32, #endif .release = v4l2_release, .poll = v4l2_poll, .llseek = no_llseek, }; vdev->cdev->ops = &v4l2_fops; vdev->cdev->owner = owner; ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1); if (ret < 0) { printk(KERN_ERR "%s: cdev_add failed\n", __func__); kfree(vdev->cdev); vdev->cdev = NULL; goto cleanup; }
从上面的代码可以看出来,v4l2在注册字符设备时,将该字符设备的file_operations赋值为v4l2_fops。下面看看v4l2_ioctl的实现。
static long v4l2_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct video_device *vdev = video_devdata(filp);
int ret = -ENODEV;
if (vdev->fops->unlocked_ioctl) {
if (vdev->lock && mutex_lock_interruptible(vdev->lock))
return -ERESTARTSYS;
if (video_is_registered(vdev))
ret = vdev->fops->unlocked_ioctl(filp, cmd, arg);
if (vdev->lock)
mutex_unlock(vdev->lock);
} else if (vdev->fops->ioctl) {
static DEFINE_MUTEX(v4l2_ioctl_mutex);
struct mutex *m = vdev->v4l2_dev ?
&vdev->v4l2_dev->ioctl_lock : &v4l2_ioctl_mutex;
if (cmd != VIDIOC_DQBUF && mutex_lock_interruptible(m))
return -ERESTARTSYS;
if (video_is_registered(vdev))
ret = vdev->fops->ioctl(filp, cmd, arg);
if (cmd != VIDIOC_DQBUF)
mutex_unlock(m);
} else
ret = -ENOTTY;
return ret;
}
可以看到v4l2_ioctl的处理分为两种情况:unlocked_ioctl和正常的ioctl(具体区别后面在仔细讲)。不论哪种情况,都会调用vdev->fops->ioctl函数。看一下soc_camera驱动中此函数是如何实现的:
static struct v4l2_file_operations soc_camera_fops = {
.owner = THIS_MODULE,
.open = soc_camera_open,
.release = soc_camera_close,
.unlocked_ioctl = video_ioctl2,
.read = soc_camera_read,
.mmap = soc_camera_mmap,
.poll = soc_camera_poll,
};
一般情况下,unlocked_ioctl都是指向video_ioctl2函数的。那么我们接下来看看video_ioctl2的代码:
long video_ioctl2(struct file *file,
unsigned int cmd, unsigned long arg)
{
return video_usercopy(file, cmd, arg, __video_do_ioctl);
}
video_ioctl2函数接着调用video_usercopy函数。video_usercopy函数会对用户传递过来的参数检查,将用户空间的数据拷贝到内核空间,调用我们指定的__video_do_ioctl函数,并将内核空间的数据拷贝到用户空间。
接下来终于到分析关键函数了,__video_do_ioctl :
static long __video_do_ioctl(struct file *file,
unsigned int cmd, void *arg)
{
struct video_device *vfd = video_devdata(file);
const struct v4l2_ioctl_ops *ops = vfd->ioctl_ops;
void *fh = file->private_data;
struct v4l2_fh *vfh = NULL;
int use_fh_prio = 0;
long ret_prio = 0;
long ret = -ENOTTY;
if (ops == NULL) {
printk(KERN_WARNING "videodev: \"%s\" has no ioctl_ops.\n",
vfd->name);
return ret;
}
if ((vfd->debug & V4L2_DEBUG_IOCTL) &&
!(vfd->debug & V4L2_DEBUG_IOCTL_ARG)) {
v4l_print_ioctl(vfd->name, cmd);
printk(KERN_CONT "\n");
}
if (test_bit(V4L2_FL_USES_V4L2_FH, &vfd->flags)) {
vfh = file->private_data;
use_fh_prio = test_bit(V4L2_FL_USE_FH_PRIO, &vfd->flags);
}
if (use_fh_prio)
ret_prio = v4l2_prio_check(vfd->prio, vfh->prio);
switch (cmd) {
/* --- capabilities ------------------------------------------ */
case VIDIOC_QUERYCAP:
{
struct v4l2_capability *cap = (struct v4l2_capability *)arg;
if (!ops->vidioc_querycap)
break;
cap->version = LINUX_VERSION_CODE;
ret = ops->vidioc_querycap(file, fh, cap);
if (!ret)
dbgarg(cmd, "driver=%s, card=%s, bus=%s, "
"version=0x%08x, "
"capabilities=0x%08x, "
"device_caps=0x%08x\n",
cap->driver, cap->card, cap->bus_info,
cap->version,
cap->capabilities,
cap->device_caps);
break;
}
......
return ret;
}
由于代码太长,只节选了VIDIOC_QUERYCAP的代码。__video_do_ioctl做完参数检查后,就是判断cmd的值并作相应处理。基本上所有的case的操作都一样:判断ops结构体中所对应的函数是否有效,若有效则使用用户指定的参数调用,否则返回-ENOTTY.