vivid源码分析
文章目录
- vivid源码分析
- 如何编写V4L2驱动
- 分析vivid.c的open,read,write,ioctl过程
- open
- read
- ioctl
- v4l2_ctrl_handler使用过程
如何编写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,返回它的值