linux-3.4.2 的v4l2驱动框架分析

一般的驱动框架中,都是分配某个结构体,然后设置注册该结构体,该结构体有个上层管理者,一般是和应用程序交互的入口,V4l2框架框是否也是如此呢,下面进行源码分析。

首先uvc_driver.c里分配了uvc_driver结构体,然后在init函数里进行了注册,当外接的摄像头id和uvc_driver结构体的id匹配时,就会调用uvc_driver里的proble函数,在proble函数里最终依次调用了v4l2_device_register、 video_device_alloc、video_register_device,这里video_register_device才是重要的,在uvc_register_video里分配了video_device结构体并用video_register_device进行注册,函数调用的层级关系如下:

uvc_probev4l2_device_registeruvc_register_chainsuvc_register_termsuvc_register_videovideo_device_allocvideo_register_devicestatic int uvc_register_video(struct uvc_device *dev,struct uvc_streaming *stream)
{struct video_device *vdev;int ret;/* Initialize the streaming interface with default streaming* parameters.*/ret = uvc_video_init(stream);if (ret < 0) {uvc_printk(KERN_ERR, "Failed to initialize the device ""(%d).\n", ret);return ret;}uvc_debugfs_init_stream(stream);/* Register the device with V4L. */vdev = video_device_alloc();if (vdev == NULL) {uvc_printk(KERN_ERR, "Failed to allocate video device (%d).\n",ret);return -ENOMEM;}/* We already hold a reference to dev->udev. The video device will be* unregistered before the reference is released, so we don't need to* get another one.*/vdev->v4l2_dev = &dev->vdev;vdev->fops = &uvc_fops;vdev->release = uvc_release;strlcpy(vdev->name, dev->name, sizeof vdev->name);/* Set the driver data before calling video_register_device, otherwise* uvc_v4l2_open might race us.*/stream->vdev = vdev;video_set_drvdata(vdev, stream);ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1);if (ret < 0) {uvc_printk(KERN_ERR, "Failed to register video device (%d).\n",ret);stream->vdev = NULL;video_device_release(vdev);return ret;}atomic_inc(&dev->nstreams);return 0;
}

video_register_device调用__video_register_device,__video_register_device是v4l2-dev.c里的函数,这里面会设置video_device 的cdev属性,cdev包含了一个file_operations结构体,并这这里被设置为v4l2_fops,这里其实就是字符设备驱动那一套,name_base 就是设备文件名的一部分,然后通过cdev_add和device_register进行添加注册,最终应用程序就是操作v4l2_fops这个结构体里面的读写等函数。

int __video_register_device(struct video_device *vdev, int type, int nr,int warn_if_nr_in_use, struct module *owner)
{...switch (type) {case VFL_TYPE_GRABBER:name_base = "video";break;case VFL_TYPE_VBI:name_base = "vbi";break;case VFL_TYPE_RADIO:name_base = "radio";break;case VFL_TYPE_SUBDEV:name_base = "v4l-subdev";break;default:printk(KERN_ERR "%s called with unknown type: %d\n",__func__, type);return -EINVAL;}...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;}/* Part 4: register the device with sysfs */vdev->dev.class = &video_class;vdev->dev.devt = MKDEV(VIDEO_MAJOR, vdev->minor);if (vdev->parent)vdev->dev.parent = vdev->parent;dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num);ret = device_register(&vdev->dev);....return ret;
}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,
};

///
以虚拟视频驱动vivi.c为例进行分析
初始化函数里调用了vivi_create_instance,vivi_create_instance里首先调用了v4l2_device_register,该函数主要对dev->v4l2_dev进行了初始化,并提供了锁等机制,v4l2_dev主要为video_device服务的,后面会把它赋值给video_device的v4l2_dev。
然后是初始化ctrl_handler这个结构体,通过v4l2_ctrl_new_std构造一个个新的属性来设置ctrl_handler,这些属性就像形成了一个链表,主要是为了给应用程序调用ioctl的时候使用,构造完成后把链表头hdl赋值给dev->v4l2_dev.ctrl_handler,因为后面dev->v4l2_dev赋值给了video_device的v4l2_dev,所以video_device就和这个链表进行了关联。
vivi.c驱动也是使用的V4l2框架,在构造完ctrl_handler链表后,通过video_device_alloc分配了video_device ,只不过这里把vivi_template赋值给了这个video_device ,然后设置了debug 和v4l2_dev 这些属性,最终调用v4l2-dev.c里的__video_register_device进行注册。

static int __init vivi_init(void)
{...ret = vivi_create_instance(i);...return ret;
}static int __init vivi_create_instance(int inst)
{struct vivi_dev *dev;struct video_device *vfd;struct v4l2_ctrl_handler *hdl;struct vb2_queue *q;int ret;ret = v4l2_device_register(NULL, &dev->v4l2_dev);...dev->fmt = &formats[0];dev->width = 640;dev->height = 480;hdl = &dev->ctrl_handler;v4l2_ctrl_handler_init(hdl, 11);dev->volume = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,V4L2_CID_AUDIO_VOLUME, 0, 255, 1, 200);dev->brightness = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,V4L2_CID_BRIGHTNESS, 0, 255, 1, 127);dev->contrast = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,V4L2_CID_CONTRAST, 0, 255, 1, 16);dev->saturation = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops,V4L2_CID_SATURATION, 0, 255, 1, 127);...v4l2_ctrl_auto_cluster(2, &dev->autogain, 0, true);dev->v4l2_dev.ctrl_handler = hdl;vfd = video_device_alloc();if (!vfd)goto unreg_dev;*vfd = vivi_template;vfd->debug = debug;vfd->v4l2_dev = &dev->v4l2_dev;...ret = video_register_device(vfd, VFL_TYPE_GRABBER, video_nr);...return ret;
}//v4l2-device.c
int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)
{if (v4l2_dev == NULL)return -EINVAL;INIT_LIST_HEAD(&v4l2_dev->subdevs);spin_lock_init(&v4l2_dev->lock);mutex_init(&v4l2_dev->ioctl_lock);v4l2_prio_init(&v4l2_dev->prio);kref_init(&v4l2_dev->ref);get_device(dev);v4l2_dev->dev = dev;if (dev == NULL) {/* If dev == NULL, then name must be filled in by the caller */WARN_ON(!v4l2_dev->name[0]);return 0;}/* Set name to driver name + device name if it is empty. */if (!v4l2_dev->name[0])snprintf(v4l2_dev->name, sizeof(v4l2_dev->name), "%s %s",dev->driver->name, dev_name(dev));if (!dev_get_drvdata(dev))dev_set_drvdata(dev, v4l2_dev);return 0;
}

应用程序是如何调用的呢
应用程序其实就是操作的__video_register_device这个函数创建的设备文件,也就是执行v4l2_fops 这个结构体里面的函数,例如当打开文件时,会调用v4l2_open这个函数,而v4l2_open会通过video_devdata函数去video_device这个数组里找到对应的video_device ,video_device 是在
__video_register_device注册时存放到数组里的。v4l2_open取到video_device进行相关判断后就会调用其fops里的对应open函数(vdev->fops->open(filp)),读写时也是一样。

int __video_register_device(struct video_device *vdev, int type, int nr,int warn_if_nr_in_use, struct module *owner)
{...vdev->cdev->ops = &v4l2_fops;if (vdev->v4l2_dev) {if (vdev->ctrl_handler == NULL)//这里会把之前的ctrl_handler给vdev->ctrl_handler,方便ioctrl的调用vdev->ctrl_handler = vdev->v4l2_dev->ctrl_handler;}..video_device[vdev->minor] = vdev;...
}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 int v4l2_open(struct inode *inode, struct file *filp)
{struct video_device *vdev;vdev = video_devdata(filp);...mutex_unlock(&videodev_lock);if (vdev->fops->open) {...if (video_is_registered(vdev))ret = vdev->fops->open(filp);...}return ret;
}struct video_device *video_devdata(struct file *file)
{return video_device[iminor(file->f_path.dentry->d_inode)];
}

ioctrl是如何调用的呢,其实也是和读写类似,应用程序调用ioctrl实际上就是调用v4l2_fops里的unlocked_ioctl (v4l2_ioctl),v4l2_ioctl也是从数组里取出video_device判断后调用video_device的fops的unlocked_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 long v4l2_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{struct video_device *vdev = video_devdata(filp);...if (vdev->fops->unlocked_ioctl) {...if (video_is_registered(vdev))ret = vdev->fops->unlocked_ioctl(filp, cmd, arg);}...	
}

vivi_template 赋值给了video_device ,所以就是调用vivi_template 里fops的unlocked_ioctl函数(video_ioctl2),video_ioctl2最终会调用video_usercopy,video_usercopy又会调用__video_do_ioctl函数

static struct video_device vivi_template = {.name		= "vivi",.fops           = &vivi_fops,.ioctl_ops 	= &vivi_ioctl_ops,.release	= video_device_release,.tvnorms              = V4L2_STD_525_60,.current_norm         = V4L2_STD_NTSC_M,
};static const struct v4l2_file_operations vivi_fops = {.owner		= THIS_MODULE,.open           = v4l2_fh_open,.release        = vivi_close,.read           = vivi_read,.poll		= vivi_poll,.unlocked_ioctl = video_ioctl2, /* V4L2 ioctl handler */.mmap           = vivi_mmap,
};long video_ioctl2(struct file *file,unsigned int cmd, unsigned long arg)
{return video_usercopy(file, cmd, arg, __video_do_ioctl);
}

所以ioctl最终会调用到__video_do_ioctl,__video_do_ioctl也会从数组里面取到video_device ,然后通过判断命令参数cmd会进行相关属性设置,而在vivi.c里构造的ctrl_handler此时就会被使用。

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;...switch (cmd) {/* --- controls ---------------------------------------------- */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)ret = v4l2_queryctrl(vfd->ctrl_handler, p);else if (ops->vidioc_queryctrl)ret = ops->vidioc_queryctrl(file, fh, p);elsebreak;if (!ret)dbgarg(cmd, "id=0x%x, type=%d, name=%s, min/max=%d/%d, ""step=%d, default=%d, flags=0x%08x\n",p->id, p->type, p->name,p->minimum, p->maximum,p->step, p->default_value, p->flags);elsedbgarg(cmd, "id=0x%x\n", p->id);break;}...default:if (!ops->vidioc_default)break;ret = ops->vidioc_default(file, fh, ret_prio >= 0, cmd, arg);break;} /* switch */if (vfd->debug & V4L2_DEBUG_IOCTL_ARG) {if (ret < 0) {v4l_print_ioctl(vfd->name, cmd);printk(KERN_CONT " error %ld\n", ret);}}return ret;
}

总结:其实V4l2框架总体上也可分为两大层,一层是和硬件交互真正被调用的(对应vivi.c中vivi_template这个结构体),一层是上面的管理层(对应v4l2-dev.c中v4l2_fops这个结构体),应用程序执行时是调用管理层中v4l2_fops里的函数,而v4l2_fops又会调用vivi_template里fops对应的函数

具体过程为:在驱动程序(拿vivi.c来说)中分配一个video_device,然后把vivi_template的值给了video_device,接下来对video_device进行其他属性相关设置后会调用v4l2-dev.c中的__video_register_device进行注册,__video_register_device中会设置video_device的cdev的ops属性为v4l2_fops,同时会用字符设备那一套把video_device进行添加和注册,也就是会添加一个字符设备文件,最后以video_device的次设备号为下标把video_device放在一个数组里面。

应用程序调用时,其实就是去打开__video_register_device里注册的字符设备文件,本质上就是去调用v4l2_fops里的那些函数,例如当打开一个文件时,就会调用v4l2_fops的open函数,open函数里会通过打开的文件属性获得数组下标找到video_device,在进行相关判断后会调用video_device的fops的open函数,也就是vivi_template的fops的open函数,其他读写函数调用也是如此。所以V4l2框架总体上分为两大层,上层是和应该程序对接的,下层才是真正硬件操作函数,上层的操作最终会映射到下层对应的操作函数。

另外,对于ioctl来说,它的操作稍微复杂,但总体上调用流程和读写等函数差不多。因为ioctl一般就用来设置属性,例如分辨率和声音等,而这些属性在video_device注册时就进行了设置,然后再和video_device关联,最终ioctl的处理函数就会用到这些属性,这是它不同于其他函数的地方。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/47058.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

动漫3D虚拟人物制作为企业数字化转型提供强大动力

一个 3D 虚拟数字人角色的制作流程&#xff0c;可以分为概念设定-3D 建模-贴图-蒙皮-动画-引擎测试六个步骤&#xff0c;涉及到的岗位有原画师、模型师、动画师等。角色概念设定、贴图绘制一般是由视觉设计师来完成;而建模、装配(骨骼绑定)、渲染动画是由三维设计师来制作完成。…

R package org.Hs.eg.db to convert gene id

文章目录 install使用org.Hs.egENSEMBL将Ensembl id convert to gene idorg.Hs.egGENENAME 将Ensembl id convert to gene nameorg.Hs.egSYMBOL 将 gene symbol convert to gene id我现在有一些ensembl id 如何转为 gene name注意你会遇到一些record不全的情况&#xff0c;gtf文…

Pytorch-day10-模型部署推理-checkpoint

模型部署&推理 模型部署模型推理 我们会将PyTorch训练好的模型转换为ONNX 格式&#xff0c;然后使用ONNX Runtime运行它进行推理 1、ONNX ONNX( Open Neural Network Exchange) 是 Facebook (现Meta) 和微软在2017年共同发布的&#xff0c;用于标准描述计算图的一种格式…

商业智能BI是什么都不明白,如何实现数字化?

2021年下半年中国商业智能软件市场规模为4.8亿美元&#xff0c;2021年度市场规模达到7.8亿美元&#xff0c;同比增长34.9%&#xff0c;呈现飞速增长的趋势。数字化时代&#xff0c;商业智能BI对于企业的落地应用有着巨大价值&#xff0c;逐渐成为了现代企业信息化、数字化转型中…

【Leetcode Sheet】Weekly Practice 3

Leetcode Test 833 字符串中的查找与替换(8.15) 你会得到一个字符串 s (索引从 0 开始)&#xff0c;你必须对它执行 k 个替换操作。替换操作以三个长度均为 k 的并行数组给出&#xff1a;indices, sources, targets。 要完成第 i 个替换操作: 检查 子字符串 sources[i] 是否…

怎么借助ChatGPT处理数据结构的问题

目录 使用ChatGPT进行数据格式化转换 代码示例 ChatGPT格式化数据提示语 代码示例 批量格式化数据提示语 代码示例 ChatGPT生成的格式化批处理代码 使用ChatGPT合并不同数据源的数据 合并数据提示语 自动合并数据提示语 ChatGPT生成的自动合并代码 结论 数据合并是…

在Windows下安装PIP+Phantomjs+Selenium

最近准备深入学习Python相关的爬虫知识了&#xff0c;如果说在使用Python爬取相对正规的网页使用"urllib2 BeautifulSoup 正则表达式"就能搞定的话&#xff1b;那么动态生成的信息页面&#xff0c;如Ajax、JavaScript等就需要通过"Phantomjs CasperJS Selen…

【从零开始的rust web开发之路 二】axum中间件和共享状态使用

系列文章目录 第一章 axum学习使用 第二章 axum中间件使用 文章目录 系列文章目录前言一、中间件是什么二、中间件使用常用中间件使用中间件使用TraceLayer中间件实现请求日志打印自定义中间件 共享状态 前言 上篇文件讲了路由和参数相应相关的。axum还有个关键的地方是中间件…

clickhouse-备份恢复

一、简介 备份恢复是数据库常用的手段&#xff0c;可能大多数公司很少会对大数据所使用的数据进行备份&#xff0c;这里还是了解下比较好&#xff0c;下面做了一些简单的介绍&#xff0c;详细情况可以通过官网来查看&#xff0c;经过测试发现Disk中增量备份并不好用&#xff0…

电工-学习电工有哪些好处

学习电工有哪些好处&#xff1f;在哪学习电工&#xff1f; 学习电工有哪些好处&#xff1f;在哪学习电工&#xff1f;学习电工可以做什么&#xff1f;优势有哪些&#xff1f; 学习电工可以做什么&#xff1f;学习电工有哪些好处&#xff1f; 就业去向&#xff1a;可在企业单位…

“深入探索JVM内部机制:理解Java虚拟机的运行原理“

标题&#xff1a;深入探索JVM内部机制&#xff1a;理解Java虚拟机的运行原理 摘要&#xff1a;本篇博客将深入探索Java虚拟机&#xff08;JVM&#xff09;的内部机制&#xff0c;帮助读者理解JVM的运行原理。我们将介绍JVM的组成结构&#xff0c;包括类加载器、运行时数据区域…

基于微信小程序的垃圾分类系统设计与实现(2.0 版本,附前后端代码)

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝30W、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 1 简介 视频演示地址&#xff1a; 基于微信小程序的智能垃圾分类回收系统&#xff0c;可作为毕业设计 小…

PyCharm PyQt5 开发环境搭建

环境 python&#xff1a;3.6.x PyCharm&#xff1a;PyCharm 2019.3.5 (Community Edition) 安装PyQT5 pip install PyQt5 -i https://pypi.douban.com/simplepip install PyQt5-tools -i https://pypi.douban.com/simple配置PyCharm PyQtUIC Program &#xff1a;D:\Pytho…

Android kotlin 跳转手机热点开关页面和判断热点是否打开

Android kotlin 跳转手机热点开关页面和判断热点是否打开 判断热点是否打开跳转手机热点开关页面顺带介绍一些其他常用的设置页面跳转 其他热点的一些相关知识Local-only hotspot 参考 判断热点是否打开 网上方法比较多&#xff0c;我这边使用了通过WifiManager 拿反射的getWi…

从C语言到C++_33(C++11_上)initializer_list+右值引用+完美转发+移动构造/赋值

目录 1. 列表初始化initializer_list 2. 前面提到的一些知识点 2.1 小语法 2.2 STL中的一些变化 3. 右值和右值引用 3.1 右值和右值引用概念 3.2 右值引用类型的左值属性 3.3 左值引用与右值引用比较 3.4 右值引用的使用场景 3.4.1 左值引用的功能和短板 3.4.2 移动…

使用ApplicationRunner简化Spring Boot应用程序的初始化和启动

ApplicationRunner这个接口&#xff0c;我们一起来了解这个组件&#xff0c;并简单使用它吧。&#x1f92d; 引言 在开发Spring Boot应用程序时&#xff0c;应用程序的初始化和启动是一个重要的环节。ApplicationRunner是Spring Boot提供的一个有用的接口&#xff0c;可以帮助…

【Spring Boot】SpringBoot和数据库交互: 使用Spring Data JPA

文章目录 1. 数据库和Java应用程序1.1 为什么需要数据库交互1.2 传统的数据库交互方法 2. 什么是JPA2.1 JPA的定义2.2 JPA的优势 3. Spring Data JPA介绍3.1 Spring Data JPA的特性3.2 如何简化数据库操作 4. 在SpringBoot中集成Spring Data JPA4.1 添加依赖4.2 配置数据源 5. …

【javaweb】学习日记Day3 - Ajax 前后端分离开发 入门

目录 一、Ajax 1、简介 2、Axios &#xff08;没懂 暂留&#xff09; &#xff08;1&#xff09;请求方式别名 &#xff08;2&#xff09;发送get请求 &#xff08;3&#xff09;发送post请求 &#xff08;4&#xff09;案例 二、前端工程化 1、Vue项目-目录结构 2、…

第10步---MySQL的日志操作

第10步---MySQL的日志操作 错误日志 慢日志 1.查看错误日志 -- 查看日志信息 show VARIABLES like log_error%;2.查看binlog 高版本是默认开启的&#xff0c;低的是默认是不开启的 binlog日志文件是与事务相关 -- 查看binlog日志的格式 show variables like binlog_format;-- …

vue-router在vue2/3区别

构建选项区别 vue2-router const router-new VueRouter({mode:history,base:_name,})vue-next-router import { createRouter,createWebHistory} from vue-next-router const routercreateRouter({history:createHistory(/) })在上述代码中我们发现,vue2中的构建选项mode和ba…