深入学习Linux摄像头(二)v4l2驱动框架
深入学习Linux摄像头系列
深入学习Linux摄像头(一)v4l2应用编程
深入学习Linux摄像头(二)v4l2驱动框架
深入学习Linux摄像头(三)虚拟摄像头驱动分析
深入学习Linux摄像头(四)三星平台fimc驱动详解
深入学习Linux摄像头(二)v4l2驱动框架
文章目录
- 深入学习Linux摄像头(二)v4l2驱动框架
- 一、V4L2 框架
- 1.1 相关对象
- 1.2 V4L2 框架
- 二、V4L2的数据结构
- 2.1 V4L2主要对象的数据结构
- 2.2 V4L2提供的注册接口
- 三、源码剖析
- 3.1 V4L2驱动模板
- 3.2 V4L2源码剖析
一、V4L2 框架
1.1 相关对象
v4l2驱动框架主要的对象有video_device
、v4l2_device
、v4l2_subdev
、videobuf
video_device
一个字符设备,为用户空间提供设备节点(/dev/videox),提供系统调用的相关操作(open、ioctl…)
v4l2_device
嵌入到video_device中,表示一个v4l2设备的实例
v4l2_subdev
依附在v4l2_device之下,并表示一个v4l2设备的子设备,一个v4l2_devide下可以有多个sub_device
videobuf
v4l2驱动的缓存管理
下面有必要对v4l2_device和v4l2_subdev来进行说明
subdev的设计目的是为了多路复用,就是用一个v4l2_device可以服务多个v4l2_subdev
下面以我们手机的摄像头来举例
CMOS摄像头
对于一款CMOS摄像头来说,有两个接口,一个是
摄像头接口
,一个是I2C接口
摄像头接口负责传输图像数据,I2C接口负责传输控制信息,所以又可以将CMOS摄像头看作是一个I2C模块
如下图所示
芯片片上资源
在一款芯片上面,摄像头相关的有
摄像头控制器
、摄像头接口
、I2C总线
SOC上可以有多个摄像头控制器,多个摄像头接口,多个I2C总线
摄像头控制器负责接收和处理摄像头数据,摄像头接口负责传输图像数据,I2C总线负责传输控制信息
如下图所示
对于手机而言,一般都有两个摄像头,一个前置摄像头,一个后置摄像头,其接发下图所示
我们可以选择让控制器去操作哪一个摄像头,这就做到了使用一个摄像头控制器来控制多个摄像头,这就是多路复用
上面说要使用一个摄像头控制器去操作多个摄像头,这是我们的目的,那么在软件中是怎么实现的呢?
我们回到V4L2来,再来谈v4l2_device
和v4l2_subdev
上面我们介绍到v4l2_device
表示一个v4l2实例
在V4L2驱动中,使用v4l2_device
来表示摄像头控制器
使用v4l2_subdev
来表示具体的某一个摄像头的I2C控制模块
,进而通过其控制摄像头
v4l2_device
里有一个v4l2_subdev
链表,可以选择v4l2_device
去控制哪一个v4l2_subdev
相信到此,你对v4l2_device
和v4l2_subdev
就有所了解了
当然某些驱动是没有v4l2_subdev,只有video_device
经过上面的讲解,我们用一张图来总结
前面说video_device是一个字符设备,从图中可以看出,video_device内含一个cdev
v4l2_device是一个v4l2实例,嵌入到video_device中
v4l2_device维护者一个链表管理v4l2_subdev,v4l2_subdev表示摄像头的I2C控制模块
1.2 V4L2 框架
在理清楚V4L2中的主要对象后,我们来介绍V4L2的框架
在介绍V4L2驱动框架前,我们先回顾一下简单的字符设备的编写
- 分配一个字符设备(cdev)
- 设置一个fops
- 注册字符设备
复杂的字符设备
对于复杂的字符设备,内核都是采用分层的方法,一般分驱动核心层还有硬件相关层
核心层会帮你完成字符设备的分配,fops的设置,注册字符设备,并向硬件相关层提供一个相应的对象和注册接口
硬件相关层则需要分配相应的对象,设置对象和对象的fops,并注册到核心层中
当应用层发生系统调用,会先来到核心层,核心层再通过回调函数调用到硬件相关层的驱动
对于V4L2的驱动框架也是如此,可分为V4L2驱动核心层和硬件相关层
下面先用一张图来总结大致V4L2的驱动框架
从图中可以看出V4L2分为核心层还有硬件相关层
核心层负责注册字符设备,然后提供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
来操作硬件
二、V4L2的数据结构
介绍完V4L2的驱动框架后,来看一看内核中各对象的数据结构
2.1 V4L2主要对象的数据结构
video_device
struct video_device {/* character device */struct cdev *cdev;/* v4l2_device parent */struct v4l2_device *v4l2_dev;/* device ops */const struct v4l2_file_operations *fops;/* ioctl callbacks */const struct v4l2_ioctl_ops *ioctl_ops; };
可以看到video_device中含有一个cdev还有v4l2_device,此外还有fops和ioctl_ops,从应用层进行系统调用会经过v4l2的核心层回调到这里
其中
v4l2_file_operations
和v4l2_ioctl_ops
如下struct v4l2_file_operations {struct module *owner;ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);unsigned int (*poll) (struct file *, struct poll_table_struct *);long (*ioctl) (struct file *, unsigned int, unsigned long);long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);unsigned long (*get_unmapped_area) (struct file *, unsigned long,unsigned long, unsigned long, unsigned long);int (*mmap) (struct file *, struct vm_area_struct *);int (*open) (struct file *);int (*release) (struct file *); };
熟悉v4l2应用编程的应该都知道v4l2有很多ioctl操作,具体实现都在这里
struct v4l2_ioctl_ops {int (*vidioc_querycap)(struct file *file, void *fh, struct v4l2_capability *cap);/* Buffer handlers */int (*vidioc_reqbufs) (struct file *file, void *fh, struct v4l2_requestbuffers *b);int (*vidioc_querybuf)(struct file *file, void *fh, struct v4l2_buffer *b);int (*vidioc_qbuf) (struct file *file, void *fh, struct v4l2_buffer *b);int (*vidioc_dqbuf) (struct file *file, void *fh, struct v4l2_buffer *b);/* Stream on/off */int (*vidioc_streamon) (struct file *file, void *fh, enum v4l2_buf_type i);int (*vidioc_streamoff)(struct file *file, void *fh, enum v4l2_buf_type i);... };
v4l2_device
struct v4l2_device {/* used to keep track of the registered subdevs */struct list_head subdevs;... };
可以看到v4l2_device中有一个v4l2_subdev的链表,v4l2_device的主要目的时用来管理v4l2_subdev
v4l2_subdev
struct v4l2_subdev {struct list_head list;struct v4l2_device *v4l2_dev;const struct v4l2_subdev_ops *ops; };
v4l2_subdev中有一个v4l2_subdev_ops,实现了一系列的操作,供v4l2_device调用
struct v4l2_subdev_ops {const struct v4l2_subdev_core_ops *core;const struct v4l2_subdev_tuner_ops *tuner;const struct v4l2_subdev_audio_ops *audio;const struct v4l2_subdev_video_ops *video;const struct v4l2_subdev_vbi_ops *vbi;const struct v4l2_subdev_ir_ops *ir;const struct v4l2_subdev_sensor_ops *sensor; };
struct v4l2_subdev_core_ops {...int (*s_config)(struct v4l2_subdev *sd, int irq, void *platform_data);int (*init)(struct v4l2_subdev *sd, u32 val);int (*s_gpio)(struct v4l2_subdev *sd, u32 val);int (*g_ctrl)(struct v4l2_subdev *sd, struct v4l2_control *ctrl);int (*s_ctrl)(struct v4l2_subdev *sd, struct v4l2_control *ctrl);int (*s_std)(struct v4l2_subdev *sd, v4l2_std_id norm);long (*ioctl)(struct v4l2_subdev *sd, unsigned int cmd, void *arg);... };
struct v4l2_subdev_video_ops {...int (*enum_fmt)(struct v4l2_subdev *sd, struct v4l2_fmtdesc *fmtdesc);int (*g_fmt)(struct v4l2_subdev *sd, struct v4l2_format *fmt);int (*try_fmt)(struct v4l2_subdev *sd, struct v4l2_format *fmt);int (*s_fmt)(struct v4l2_subdev *sd, struct v4l2_format *fmt);int (*cropcap)(struct v4l2_subdev *sd, struct v4l2_cropcap *cc);int (*g_crop)(struct v4l2_subdev *sd, struct v4l2_crop *crop);int (*s_crop)(struct v4l2_subdev *sd, struct v4l2_crop *crop);int (*g_parm)(struct v4l2_subdev *sd, struct v4l2_streamparm *param);int (*s_parm)(struct v4l2_subdev *sd, struct v4l2_streamparm *param);... };
2.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.1 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操作
3.2 V4L2源码剖析
下面我们来分析分析源码
在上面的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,应用层发生系统调用会率先调用到这里,我们来好好看一看这些调用
v4l2_open
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 }
从这个函数可以看到,发生系统调用首先来到v4l2核心层的字符设备,然后再回调到对应的video_device,video_device在前面已经实现了
v4l2_file_operations
和v4l2_ioctl_ops
一系列回调v4l2_ioctl
V4L2的应用编程会有非常多的ioctl,会先调用到此处
static int v4l2_ioctl(struct inode *inode, struct file *filp,unsigned int cmd, unsigned long arg) {struct video_device *vdev = video_devdata(filp);/* 回调到video_device中 */return vdev->fops->ioctl(filp, cmd, arg); }
下面来看一看video_device怎么实现ioctl
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, };
从上面驱动程序的编写,我们可以知道video_device对应ioctl就是video_ioctl2,这个函数是内核提供的,我们看一看这个函数的内容
long video_ioctl2(struct file *file,unsigned int cmd, unsigned long arg) {__video_do_ioctl(file, cmd, parg); }
static long __video_do_ioctl(struct file *file,unsigned int cmd, void *arg) {/* 获取video_device */struct video_device *vfd = video_devdata(file);/* 获取video_device的ioctl_ops */const struct v4l2_ioctl_ops *ops = vfd->ioctl_ops;switch (cmd) {case VIDIOC_QUERYCAP:ops->vidioc_querycap(file, fh, cap);case VIDIOC_ENUM_FMT:ops->vidioc_enum_fmt_vid_cap(file, fh, f);...} }
可以看出,最终会调用到video_device实现的
v4l2_ioctl_ops
/* 实现各种系统调用 */ 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, };
所以系统调用最先都会调用到字符设备的fops,然后经过v4l2核心层最终调用到video_device这里
关于v4l2的驱动框架就分析到这里,关于更加详细的实现v4l2驱动,将在后续文章中通过实例讲解
深入学习Linux摄像头(二)v4l2驱动框架相关推荐
- 从零开始学习linux的I2C设备驱动框架——写一个简单的SHT20驱动
目录 0.测试环境说明 1.设备树的修改 2.设备驱动框架 3.I2C数据传输过程 3.1 struct i2c_msg 3.2 SHT20的数据收发 4.I2C适配器超时等待时间的修改 本文资源 参 ...
- 深入学习Linux摄像头(三)虚拟摄像头驱动分析
深入学习Linux摄像头系列 深入学习Linux摄像头(一)v4l2应用编程 深入学习Linux摄像头(二)v4l2驱动框架 深入学习Linux摄像头(三)虚拟摄像头驱动分析 深入学习Linux摄像头 ...
- 深入学习Linux摄像头(一)v4l2应用编程
深入学习Linux摄像头系列 深入学习Linux摄像头(一)v4l2应用编程 深入学习Linux摄像头(二)v4l2驱动框架 深入学习Linux摄像头(三)虚拟摄像头驱动分析 深入学习Linux摄像头 ...
- 深入学习Linux摄像头(四)三星平台fimc驱动详解
深入学习Linux摄像头系列 深入学习Linux摄像头(一)v4l2应用编程 深入学习Linux摄像头(二)v4l2驱动框架 深入学习Linux摄像头(三)虚拟摄像头驱动分析 深入学习Linux摄像头 ...
- Linux V4l2驱动 -- 框架概述
V4l2框架简述 1 硬件 常用的电脑摄像头是USB接口,主流的智能手机摄像头是MIPI接口,另外还有像树莓派等硬件使用的CSI接口的设备.常用的智能手机Camera采用的MIPI接口,电路框架以及电 ...
- Linux驱动学习--HDMI开发(二)HDMI驱动源码分析(RK平台)
目录 一.引言 二.驱动框架 ------> dts节点 ------> HDMI DDC 驱动 ------> HDMI HDCP驱动 ------> HDMI CEC驱动 ...
- V4L2系列 之 V4L2驱动框架
目录 前言 一.V4L2驱动框架概览 1.应用层 ->中间层->驱动层 2.主要代码文件(Linux 4.19版本内核) 二.怎么写V4L2驱动 1.如何写一个设备的驱动? 2.Video ...
- Camera 从应用层看V4L2驱动框架
1.V4L2驱动框架简介 V4L2可用于采集图片.视频和音频数据的通用 API 接口,配合适当的视频采集设备和相应的驱 动程序,可以实现图片.视频.音频等的采集. 2.V4L2视频采集原理 当启动视频 ...
- Windows驱动开发学习笔记(二)—— 驱动调试内核编程基础
Windows驱动开发学习笔记(二)-- 驱动调试&内核编程基础 基础知识 驱动调试 PDB(Program Debug Database) WinDbg 加载 PDB 实验:调试 .sys ...
最新文章
- Linux 学习手记(6): 磁盘、分区、MBR与GPT
- SylixOS 基于STM32平台的GPIO模仿I2C总线的驱动开发流程
- Plate impulse response spatial interpolation with sub-Nyquist sampling
- Java:封装的概念,UML类图,构造方法,this关键字,static关键字,方法重载,包package
- const的一些注意事项
- c++ windows获得当前工作目录文件_使用命令行修改当前工作目录
- Oracle的分页实现
- VB与Java颜色值的转换
- 滤波器开发之三:基于算数平均的阶进平滑滤波器
- linux source多个文件夹,linux下source命令使用详解
- mysql数据库编程(c 语言)_【C/C++学院】(23)Mysql数据库编程--C语言编程实现mysql客户端...
- 如何将ImageRanger与外部存储一起使用NAS或USBUSB驱动器?
- pandas DataFrame 根据多列的值做判断,生成新的列值
- 解决 The file will have its original line endings in your working directory
- 服务器ajax无响应时间,ajax 服务器响应时间
- hdu 5294 Tricks Device 最短路建图+最小割
- java io流上传图片_SpringBoot上传图片和IO流的基本操作
- C语言------内存管理
- linux 内核源码下载
- MTK6765智能安全帽方案介绍
热门文章
- 微信小程序:设置字体跟随手机系统
- Wordress博客添加音乐播放器插件
- 如何在素材中心中下载字体并安装?
- 一般网站上传图片的大小是多少?网站图片上传格式大小建议 附带简单修图方法
- 图像算法原理与实践——图像修复之 全变分模型
- Linux kail环境下安装pyrit 问题详解
- UnityShader入门精要-屏幕后处理效果 亮度饱和度对比度、边缘检测、高斯模糊、bloom效果、运动模糊
- 这三个文件在 C:\Program Files (x86)\Microsoft Office\root\Office16 下怎么会是符号链接?
- 二维离散型随机变量及其分布
- 中国电信CTWing物联网平台接入指南(一)之开发流程