如何编写V4L2驱动
- 分配/设置/注册v4l2_device.v4l2_device_register,v4l2_device(辅助作用,提供自旋锁,引用计数等
- 分配video_device.video_device_alloc
- 设置
vfd->v4l2_dev
分析vivid.c的open,read,write,ioctl过程
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, };
static const struct v4l2_file_operations vivid_fops = { .owner = THIS_MODULE, .open = v4l2_fh_open, .release = vivid_fop_release, .read = vb2_fop_read, .write = vb2_fop_write, .poll = vb2_fop_poll, .unlocked_ioctl = video_ioctl2, .mmap = vb2_fop_mmap, };
open
app: open(“/dev/video0”,…)
drv: v4l2_fops.v4l2_open
vdev = video_devdata(filp); // 根据次设备号从数组中得到video_device
ret = vdev->fops->open(filp);
vivi_ioctl_ops.open
v4l2_fh_open
/* Override for the open function */ static int v4l2_open(struct inode *inode, struct file *filp) { struct video_device *vdev; int ret = 0; // 检查视频设备是否可用 mutex_lock(&videodev_lock); vdev = video_devdata(filp); // 如果视频设备已经被移除,则返回ENODEV。 if (vdev == NULL || !video_is_registered(vdev)) { mutex_unlock(&videodev_lock); return -ENODEV; } // 增加设备引用计数 video_get(vdev); mutex_unlock(&videodev_lock); if (vdev->fops->open) { // 如果视频设备已经注册,则调用open函数 if (video_is_registered(vdev)) ret = vdev->fops->open(filp); else ret = -ENODEV; } if (vdev->dev_debug & V4L2_DEV_DEBUG_FOP) printk(KERN_DEBUG "%s: open (%d)\n", video_device_node_name(vdev), ret); // 如果出现错误,则减少引用计数 if (ret) video_put(vdev); return ret; }
根据次设备号从数组中得到video_device
vdev = video_devdata(filp);
struct video_device *video_devdata(struct file *file) { return video_device[iminor(file_inode(file))]; }
该数组在__video_register_device中设置
__video_register_device get_index
vdev->minor = i + minor_offset; // 设置设备节点号 vdev->num = nr; // 设置设备编号 devnode_set(vdev); // 设置设备节点 /* Should not happen since we thought this minor was free */ WARN_ON(video_device[vdev->minor] != NULL); // 如果设备节点已被占用,打印警告信息 vdev->index = get_index(vdev); // 获取设备索引 video_device[vdev->minor] = vdev; // 将设备添加到 video_device 数组中 mutex_unlock(&videodev_lock); // 解锁
以次设备号为下标存起来
static int get_index(struct video_device *vdev) { /* This can be static since this function is called with the global videodev_lock held. */ static DECLARE_BITMAP(used, VIDEO_NUM_DEVICES); int i; // 初始化used数组 bitmap_zero(used, VIDEO_NUM_DEVICES); // 遍历video_device数组,将v4l2_dev相同的设备的index标记在used数组中 for (i = 0; i < VIDEO_NUM_DEVICES; i++) { if (video_device[i] != NULL && video_device[i]->v4l2_dev == vdev->v4l2_dev) { set_bit(video_device[i]->index, used); } } // 返回used数组中第一个为0的位的下标 return find_first_zero_bit(used, VIDEO_NUM_DEVICES); }
下面调用到vivid_fops中的v4l2_fh_open函数(硬件相关层的函数)
if (vdev->fops->open) { // 如果视频设备已经注册,则调用open函数 if (video_is_registered(vdev)) ret = vdev->fops->open(filp);
static const struct v4l2_file_operations vivid_fops = { .owner = THIS_MODULE, .open = v4l2_fh_open, .release = vivid_fop_release, .read = vb2_fop_read, .write = vb2_fop_write, .poll = vb2_fop_poll, .unlocked_ioctl = video_ioctl2, .mmap = vb2_fop_mmap, };
read
app: read …
drv: v4l2_fops.v4l2_read
struct video_device *vdev = video_devdata(filp);
ret = vdev->fops->read(filp, buf, sz, off);
// 读取函数的实现 static ssize_t v4l2_read(struct file *filp, char __user *buf, size_t sz, loff_t *off) { // 获取video_device结构体 struct video_device *vdev = video_devdata(filp); // 初始化返回值 int ret = -ENODEV; // 检查是否实现了read函数 if (!vdev->fops->read) return -EINVAL; // 调用驱动程序的read函数 if (video_is_registered(vdev)) ret = vdev->fops->read(filp, buf, sz, off); // 打印调试信息 if ((vdev->dev_debug & V4L2_DEV_DEBUG_FOP) && (vdev->dev_debug & V4L2_DEV_DEBUG_STREAMING)) printk(KERN_DEBUG "%s: read: %zd (%d)\n", video_device_node_name(vdev), sz, ret); return ret; }
ioctl
app: ioctl
drv: v4l2_fops.unlocked_ioctl
v4l2_ioctl
struct video_device *vdev = video_devdata(filp);
ret = vdev->fops->unlocked_ioctl(filp, cmd, arg);
video_ioctl2
video_usercopy(file, cmd, arg, __video_do_ioctl);
__video_do_ioctl
struct video_device *vfd = video_devdata(file);
根据APP传入的cmd来获得、设置"某些属性"
v4l2_ctrl_handler的使用过程:
__video_do_ioctl
struct video_device *vfd = video_devdata(file);
case VIDIOC_QUERYCTRL: { struct v4l2_queryctrl *p = arg; if (vfh && vfh->ctrl_handler) ret = v4l2_queryctrl(vfh->ctrl_handler, p); else if (vfd->ctrl_handler) // 在哪设置?在video_register_device ret = v4l2_queryctrl(vfd->ctrl_handler, p); // 根据ID在ctrl_handler里找到v4l2_ctrl,返回它的值
// v4l2_ioctl函数的实现 static long v4l2_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { // 获取video_device结构体 struct video_device *vdev = video_devdata(filp); // 初始化返回值 int ret = -ENODEV; // 检查是否实现了unlocked_ioctl函数 if (vdev->fops->unlocked_ioctl) { // 获取锁 struct mutex *lock = v4l2_ioctl_get_lock(vdev, cmd); // 如果获取锁失败,则返回ERESTARTSYS if (lock && mutex_lock_interruptible(lock)) return -ERESTARTSYS; // 调用驱动程序的unlocked_ioctl函数 if (video_is_registered(vdev)) ret = vdev->fops->unlocked_ioctl(filp, cmd, arg); // 释放锁 if (lock) mutex_unlock(lock); } else if (vdev->fops->ioctl) { /* This code path is a replacement for the BKL. It is a major * hack but it will have to do for those drivers that are not * yet converted to use unlocked_ioctl. * * All drivers implement struct v4l2_device, so we use the * lock defined there to serialize the ioctls. * * However, if the driver sleeps, then it blocks all ioctls * since the lock is still held. This is very common for * VIDIOC_DQBUF since that normally waits for a frame to arrive. * As a result any other ioctl calls will proceed very, very * slowly since each call will have to wait for the VIDIOC_QBUF * to finish. Things that should take 0.01s may now take 10-20 * seconds. * * The workaround is to *not* take the lock for VIDIOC_DQBUF. * This actually works OK for videobuf-based drivers, since * videobuf will take its own internal lock. */ // 获取锁 struct mutex *m = &vdev->v4l2_dev->ioctl_lock; // 如果获取锁失败,则返回ERESTARTSYS if (cmd != VIDIOC_DQBUF && mutex_lock_interruptible(m)) return -ERESTARTSYS; // 调用驱动程序的ioctl函数 if (video_is_registered(vdev)) ret = vdev->fops->ioctl(filp, cmd, arg); // 释放锁 if (cmd != VIDIOC_DQBUF) mutex_unlock(m); } else ret = -ENOTTY; return ret; }
// 调用驱动程序的ioctl函数
if (video_is_registered(vdev))
ret = vdev->fops->ioctl(filp, cmd, arg);
/* * video_ioctl2 - V4L2 ioctl handler * * @file: file pointer * @cmd: ioctl command * @arg: argument * * This function is the V4L2 ioctl handler. It calls video_usercopy to copy * the arguments from user space to kernel space, then calls __video_do_ioctl * to handle the ioctl command, and finally copies the results back to user * space. * * Return: 0 on success, negative error code on failure. */ long video_ioctl2(struct file *file, unsigned int cmd, unsigned long arg) { return video_usercopy(file, cmd, arg, __video_do_ioctl); }
video_ioctl2
__video_do_ioctl
static long __video_do_ioctl(struct file *file, unsigned int cmd, void *arg) { // 获取video_device结构体 struct video_device *vfd = video_devdata(file); // 获取v4l2_ioctl_ops结构体 const struct v4l2_ioctl_ops *ops = vfd->ioctl_ops; // 判断是否为只写操作 bool write_only = false; // 定义默认的ioctl信息 struct v4l2_ioctl_info default_info; // 定义ioctl信息 const struct v4l2_ioctl_info *info; // 获取file结构体的私有数据 void *fh = file->private_data; // 获取v4l2_fh结构体 struct v4l2_fh *vfh = NULL; // 获取dev_debug int dev_debug = vfd->dev_debug; // 定义返回值 long ret = -ENOTTY; // 判断是否有ioctl_ops if (ops == NULL) { pr_warn("%s: has no ioctl_ops.\n", video_device_node_name(vfd)); return ret; } // 判断是否使用v4l2_fh if (test_bit(V4L2_FL_USES_V4L2_FH, &vfd->flags)) vfh = file->private_data; // 判断是否为已知的ioctl if (v4l2_is_known_ioctl(cmd)) { // 获取ioctl信息 info = &v4l2_ioctls[_IOC_NR(cmd)]; // 判断是否为有效的ioctl if (!test_bit(_IOC_NR(cmd), vfd->valid_ioctls) && !((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 { // 设置默认的ioctl信息 default_info.ioctl = cmd; default_info.flags = 0; default_info.debug = v4l_print_default; info = &default_info; } write_only = _IOC_DIR(cmd) == _IOC_WRITE; // 判断是否为标准的ioctl if (info->flags & INFO_FL_STD) { // 定义vidioc_op函数指针类型 typedef int (*vidioc_op)(struct file *file, void *fh, void *p); // 获取ioctl_ops const void *p = vfd->ioctl_ops; // 获取vidioc函数指针 const vidioc_op *vidioc = p + info->u.offset; // 调用vidioc函数 ret = (*vidioc)(file, fh, arg); } // 判断是否为函数ioctl else if (info->flags & INFO_FL_FUNC) { // 调用函数ioctl ret = info->u.func(ops, file, fh, arg); } // 判断是否有默认的ioctl else if (!ops->vidioc_default) { ret = -ENOTTY; } // 调用默认的ioctl else { ret = ops->vidioc_default(file, fh, 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)) return ret; // 打印ioctl信息 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); } } } return ret;
v4l2_ctrl_handler使用过程
static long __video_do_ioctl(struct file *file, unsigned int cmd, void *arg) { // 获取video_device结构体 struct video_device *vfd = video_devdata(file); // 获取v4l2_ioctl_ops结构体 const struct v4l2_ioctl_ops *ops = vfd->ioctl_ops; // 判断是否为只写操作 bool write_only = false; // 定义默认的ioctl信息 struct v4l2_ioctl_info default_info; // 定义ioctl信息 const struct v4l2_ioctl_info *info; // 获取file结构体的私有数据 void *fh = file->private_data; // 获取v4l2_fh结构体 struct v4l2_fh *vfh = NULL; // 获取dev_debug int dev_debug = vfd->dev_debug; // 定义返回值 long ret = -ENOTTY; // 判断是否有ioctl_ops if (ops == NULL) { pr_warn("%s: has no ioctl_ops.\n", video_device_node_name(vfd)); return ret; } // 判断是否使用v4l2_fh if (test_bit(V4L2_FL_USES_V4L2_FH, &vfd->flags)) vfh = file->private_data; // 判断是否为已知的ioctl if (v4l2_is_known_ioctl(cmd)) { // 获取ioctl信息 info = &v4l2_ioctls[_IOC_NR(cmd)]; // 判断是否为有效的ioctl if (!test_bit(_IOC_NR(cmd), vfd->valid_ioctls) && !((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 { // 设置默认的ioctl信息 default_info.ioctl = cmd; default_info.flags = 0; default_info.debug = v4l_print_default; info = &default_info; } write_only = _IOC_DIR(cmd) == _IOC_WRITE; // 判断是否为标准的ioctl if (info->flags & INFO_FL_STD) { // 定义vidioc_op函数指针类型 typedef int (*vidioc_op)(struct file *file, void *fh, void *p); // 获取ioctl_ops const void *p = vfd->ioctl_ops; // 获取vidioc函数指针 const vidioc_op *vidioc = p + info->u.offset; // 调用vidioc函数 ret = (*vidioc)(file, fh, arg); } // 判断是否为函数ioctl else if (info->flags & INFO_FL_FUNC) { // 调用函数ioctl ret = info->u.func(ops, file, fh, arg); } // 判断是否有默认的ioctl else if (!ops->vidioc_default) { ret = -ENOTTY; } // 调用默认的ioctl else { ret = ops->vidioc_default(file, fh, 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)) return ret; // 打印ioctl信息 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); } } return ret; }
// 判断是否为有效的ioctl if (!test_bit(_IOC_NR(cmd), vfd->valid_ioctls) && !((info->flags & INFO_FL_CTRL) && vfh && vfh->ctrl_handler)) goto done; ctrl_handler在video_register_device中设置
// 设置设备类型 vdev->vfl_type = type; // 初始化字符设备指针 vdev->cdev = NULL; // 如果设备的父设备指针为空,则将其指向 v4l2_dev 的设备指针 if (vdev->dev_parent == NULL) vdev->dev_parent = vdev->v4l2_dev->dev; // 如果控制处理程序指针为空,则将其指向 v4l2_dev 的控制处理程序指针 if (vdev->ctrl_handler == NULL) vdev->ctrl_handler = vdev->v4l2_dev->ctrl_handler; /* 如果优先级状态指针为空,则使用 v4l2_device 的优先级状态。*/ if (vdev->prio == NULL) vdev->prio = &vdev->v4l2_dev->prio;
在ctrl_handler里找到v4l2_ctrl,返回它的值