v4l2_device
里有一个v4l2_subdev
链表,可以选择v4l2_device
去控制哪一个v4l2_subdev。
video_device是一个字符设备,从图中可以看出,video_device内含一个cdev
v4l2_device是一个v4l2实例,嵌入到video_device中
v4l2_device维护者一个链表管理v4l2_subdev,v4l2_subdev表示摄像头的I2C控制模块。
核心层负责注册字符设备,然后提供video_device对象和相应的注册接口给硬件相关层使用
硬件相关层需要分配一个video_device并设置它,然后向核心层注册,核心层会为其注册字符设备
并且创建设备节点(/dev/videox)。同时硬件相关层还需要分配和设置相应的v4l2_device和
v4l2_subdev,其中v4l2_device的一个比较重要的意义就是管理v4l2_subdev,当然有一些驱动并
不需要实现v4l2_subdev,此时v4l2_device的意义就不是很大了。
当应用层通过/dev/video来操作设备的时候,首先会来到V4L2的核心层,核心层通过注册进的
video_device的回调函数调用相应的操作函数,video_device可以直接操作硬件或者是通过
v4l2_subdev来操作硬件。
2.
V4L2提供的注册接口
video_device
注册:
int video_register_device(struct video_device *vdev, int type, int nr);
注销:
void video_unregister_device(struct video_device *vdev);
v4l2_device
注册:
int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev);
注销:
void v4l2_device_unregister(struct v4l2_device *v4l2_dev);
v4l2_subdev
注册:
int v4l2_device_register_subdev(struct v4l2_device *v4l2_dev,
struct v4l2_subdev *sd);
注销:
void v4l2_device_unregister_subdev(struct v4l2_subdev *sd);
3.V4L2驱动模板源码剖析
此示例中并没有设计到v4l2_subdev,这部分将会在后面分析具体的驱动中出现。
#include <...>
static struct video_device* video_dev;
static struct v4l2_device v4l2_dev;
/* 实现各种系统调用 */
static const struct v4l2_file_operations video_dev_fops = {
.owner = THIS_MODULE,
.release = vdev_close,
.read = vdev_read,
.poll = vdev_poll,
.ioctl = video_ioctl2,
.mmap = vdev_mmap,
};
/* 实现各种系统调用 */
static const struct v4l2_ioctl_ops video_dev_ioctl_ops = {
.vidioc_querycap = vidioc_querycap,
.vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
.vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap,
.vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap,
.vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
.vidioc_reqbufs = vidioc_reqbufs,
.vidioc_querybuf = vidioc_querybuf,
.vidioc_qbuf = vidioc_qbuf,
.vidioc_dqbuf = vidioc_dqbuf,
.vidioc_enum_input = vidioc_enum_input,
.vidioc_g_input = vidioc_g_input,
.vidioc_s_input = vidioc_s_input,
.vidioc_streamon = vidioc_streamon,
.vidioc_streamoff = vidioc_streamoff,
};
static int __init video_init(void)
{
/* 分配并设置一个video_device */
video_dev = video_device_alloc();
video_dev->fops = &video_dev_fops;
video_dev->ioctl_ops = &video_dev_ioctl_ops;
video_dev->release = video_device_release;
video_dev->tvnorms = V4L2_STD_525_60;
video_dev->current_norm = V4L2_STD_NTSC_M;
/* 注册一个v4l2_device */
v4l2_device_register(video_dev->dev, &v4l2_dev);
video_dev->v4l2_dev = &video_dev;
/* 注册一个video_device字符设备 */
video_register_device(video_dev, VFL_TYPE_GRABBER, -1);
return 0;
}
static void __exit video_exit(void)
{
video_unregister_device(video_dev);
v4l2_device_unregister(&v4l2_dev);
video_device_release(video_dev);
}
module_init(video_init);
module_exit(video_exit);
如果你熟悉v4l2应用编程的话,你应该知道v4l2有许多ioctl操作,上面的模板就实现了很多ioctl操作。
在上面的video_init
中,我们分配并设置了video_dev
,注册了v4l2_device
(v4l2_device_register),然后向v4l2核心层注册video_device
(video_register_device)
我们先来看v4l2_device_register
int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)
{
INIT_LIST_HEAD(&v4l2_dev->subdevs);
spin_lock_init(&v4l2_dev->lock);
dev_set_drvdata(dev, v4l2_dev);
...
}
从源码中可以看出v4l2_device_register
并没有做什么事,只是初始化链表,自旋锁,还有设置数据,这函数并不是我们的重点
下面来仔细分析video_register_device
int video_register_device(struct video_device *vdev, int type, int nr)
{
/* 分配字符设备 */
vdev->cdev = cdev_alloc();
/* 设置fops */
vdev->cdev->ops = &v4l2_fops;
/* 注册字符设备 */
cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
/* 生成设备节点 */
dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num);
device_register(&vdev->dev);
/* 设置全局数组 */
video_device[vdev->minor] = vdev;
}
可以看到这个函数会为video_device分配一个cdev,然后设置fops,向内核注册字符设备,再者生成设备节点,然后设置video_device全局数组,video_device一个全局数组。
static struct video_device *video_device[VIDEO_NUM_DEVICES];
保存着注册的video_device
接下来看一下其中设置的fops(v4l2_fops)
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,
.ioctl = v4l2_ioctl,
.release = v4l2_release,
.poll = v4l2_poll,
.llseek = no_llseek,
};
这个是video_device中的字符设备对应的fops,应用层发生系统调用会率先调用到这里,我们来好好看一看这些调用。
static int v4l2_open(struct inode *inode, struct file *filp)
{
struct video_device *vdev;
/* 根据次设备获得video_device */
vdev = video_devdata(filp);
/* 回调video_device的fops */
if (vdev->fops->open)
ret = vdev->fops->open(filp); //回调video
}
**自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**
**深知大多数嵌入式工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**
**因此收集整理了一份《2024年嵌入式&物联网开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**
![img](https://img-blog.csdnimg.cn/img_convert/b1994446b19ffa6441388e7eab33b748.png)
![img](https://img-blog.csdnimg.cn/img_convert/914f2a69848b2424aac0a693a544dd7e.jpeg)
![img](https://img-blog.csdnimg.cn/img_convert/ce2e36e7fcbd0f1d5ce9fe1160c0896f.png)
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上嵌入式&物联网开发知识点,真正体系化!**
![img](https://img-blog.csdnimg.cn/img_convert/8d5cb330d85f51dee92f4be2be5375ce.png)
![img](https://img-blog.csdnimg.cn/img_convert/bf86c9011d0247b479b1c8788cfcacc2.png)
**由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**
**如果你觉得这些内容对你有帮助,可以+V:Vip1104z获取!!! (备注:嵌入式)**
<img src="https://img-community.csdnimg.cn/images/73bb5de17851459088c6af944156ee24.jpg" alt="img" style="zoom: 67%;" />
# 最后
**资料整理不易,觉得有帮助的朋友可以帮忙点赞分享支持一下小编~**
**你的支持,我的动力;祝各位前程似锦,offer不断,步步高升!!!**
个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**
**如果你觉得这些内容对你有帮助,可以+V:Vip1104z获取!!! (备注:嵌入式)**
<img src="https://img-community.csdnimg.cn/images/73bb5de17851459088c6af944156ee24.jpg" alt="img" style="zoom: 67%;" />
# 最后
**资料整理不易,觉得有帮助的朋友可以帮忙点赞分享支持一下小编~**
**你的支持,我的动力;祝各位前程似锦,offer不断,步步高升!!!**
**[更多资料点击此处获qu!!](https://bbs.csdn.net/topics/618376385)**