当前位置:网站首页>V4L2 驱动层分析
V4L2 驱动层分析
2022-06-28 05:27:00 【哐哐砸电脑】
一、Camera V4L2 驱动层分析
Linux系统中视频输入设备主要包括以下四个部分:
1.字符设备驱动:V4L2本身就是一个字符设备,具有字符设备所有的特性,暴露接口给用户空间;
2.V4L2驱动核心:主要是构建一个内核中标准视频设备驱动的框架,为视频操作提供统一的接口函数;
3.平台V4L2设备驱动:在V4L2框架下,根据平台自身的特性实现与平台相关的V4L2驱动部分,包括注册video_device和v4l2_dev;
4.具体的sensor驱动:主要上电、提供工作时钟、视频图像裁剪、流IO开启等,实现各种设备控制方法供上层调用并注册v4l2_subdev。
V4L2核心源码位于drivers/media/v4l2-core,根据功能可以划分为四类:
1.字符设备模块:由v4l2-dev.c实现,主要作用申请字符主设备号、注册class和提供video device注册注销等相关函数。
2.V4L2基础框架:由v4l2-device.c、v4l2-subdev.c、v4l2-fh.c、v4l2-ctrls.c等文件构建V4L2基础框架。
3.videobuf管理
由videobuf2-core.c、videobuf2-dma-contig.c、videobuf2-dma-sg.c、videobuf2-memops.c、videobuf2-vmalloc.c、v4l2-mem2mem.c等文件实现,完成videobuffer的分配、管理和注销。
4.Ioctl框架:由v4l2-ioctl.c文件实现,构建V4L2ioctl的框架。
创建v4l2_device结构体,填充信息,通过v4l2_device_register方法向系统注册并且创建video设备节点。 //“kernel/msm-4.19/drivers/media/v4l2-core/v4l2-device.c”
创建media_device结构体,填充信息,通过media_device_register向系统注册,并创建media设备节点,并将其赋值给v4l2_device中的mdev。 //“kernel/msm-4.19/drivers/media/media-device.c”
创建v4l2_subdev结构体,填充信息,通过v4l2_device_register_subdev向系统注册,并将其挂载到v4l2_device设备中 //“kernel/msm-4.19/drivers/media/v4l2-core/v4l2-device.c”
创建对应的media_entity,并通过media_device_register_entity方法其添加到media controller中进行统一管理。 //“kernel/msm-4.19/drivers/media/media-device.c”
二、V4L2基础框架
2.1 /media/v4l2-core/v4l2-dev.c
在该文件中,主要是负责创建/sys/classs/video4linux目录 ,当有设备注册进来时,创建对应的 /dev/videox 、/dev/vbix、/dev/radiox、/dev/subdevx等节点。
主要工作如下:
1.将字符设备号(81,0)到(81,255)这期间256个字次设备号,均申请为 v4l2 使用,name=video4linux
2.注册 /sys/classs/video4linux目录
@ kernel/msm-4.4/drivers/media/v4l2-core/v4l2-dev.c
static struct class video_class = {
.name = VIDEO_NAME, // video4linux
.dev_groups = video_device_groups,
};
static int __init videodev_init(void)
{
dev_t dev = MKDEV(VIDEO_MAJOR, 0); // VIDEO_MAJOR: 81
printk(KERN_INFO "Linux video capture interface: v2.00\n");
// 1. 将字符设备号(81,0) 到 (81,255) 这期间256个字次设备号,均申请为 v4l2 使用,name=video4linux
ret = register_chrdev_region(dev, VIDEO_NUM_DEVICES, VIDEO_NAME); //VIDEO_NUM_DEVICES: 256 VIDEO_NAME:"video4linux"
======> int register_chrdev_region(dev_t from, unsigned count, const char *name)
// 2. 注册 /sys/classs/video4linux 目录
ret = class_register(&video_class);
return 0;
}
2.2 注册V4L2设备 __video_register_device()
当用设备需要注册为 v4l2 subdev 时,会调用video_register_device()函数进行注册:
"kernel/msm-4.19/drivers/media/platform/msm/camera_v2/camera/camera.c"
int camera_init_v4l2(struct device *dev, unsigned int *session)
{
struct msm_video_device *pvdev;
struct v4l2_device *v4l2_dev = NULL;
int rc = 0;
pvdev = kzalloc(sizeof(struct msm_video_device),
GFP_KERNEL);
if (WARN_ON(!pvdev)) {
rc = -ENOMEM;
goto init_end;
}
pvdev->vdev = video_device_alloc(); //分配video_device内存
if (WARN_ON(!pvdev->vdev)) {
rc = -ENOMEM;
goto video_fail;
}
v4l2_dev = kzalloc(sizeof(struct v4l2_device), GFP_KERNEL); //分配v4l2_dev 内存
if (WARN_ON(!v4l2_dev)) {
rc = -ENOMEM;
goto v4l2_fail;
}
#if defined(CONFIG_MEDIA_CONTROLLER)
v4l2_dev->mdev = kzalloc(sizeof(struct media_device), //分配media_device 内存
GFP_KERNEL);
if (!v4l2_dev->mdev) {
rc = -ENOMEM;
goto mdev_fail;
}
media_device_init(v4l2_dev->mdev);
strlcpy(v4l2_dev->mdev->model, MSM_CAMERA_NAME,
sizeof(v4l2_dev->mdev->model)); //model 为msm_camera
v4l2_dev->mdev->dev = dev;
rc = media_device_register(v4l2_dev->mdev); //media_device 注册
if (WARN_ON(rc < 0))
goto media_fail;
rc = media_entity_pads_init(&pvdev->vdev->entity, 0, NULL); //建立media_entity与media_pad之间的链接:
if (WARN_ON(rc < 0))
goto entity_fail;
pvdev->vdev->entity.function = QCAMERA_VNODE_GROUP_ID;
#endif
v4l2_dev->notify = NULL;
pvdev->vdev->v4l2_dev = v4l2_dev;
rc = v4l2_device_register(dev, pvdev->vdev->v4l2_dev); // 设置父设备为dev ,信息根据传入参数. 例如:dv4l2_dev->name =qcom,camera ca0c000.qcom,cci:qcom,c
if (WARN_ON(rc < 0))
goto register_fail;
strlcpy(pvdev->vdev->name, "msm-sensor", sizeof(pvdev->vdev->name));
pvdev->vdev->release = video_device_release;
pvdev->vdev->fops = &camera_v4l2_fops; // 配置 video_device 的字符设备操作函数
pvdev->vdev->ioctl_ops = &camera_v4l2_ioctl_ops; // 配置 v4l2 IOCTRL
pvdev->vdev->minor = -1;
pvdev->vdev->vfl_type = VFL_TYPE_GRABBER;
pvdev->vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
rc = video_register_device(pvdev->vdev,
VFL_TYPE_GRABBER, -1); 调用__video_register_device()
kernel/msm-4.19/drivers/media/platform/msm/camera_v2/msm.c
static int msm_probe(struct platform_device *pdev)
{
struct msm_video_device *pvdev = NULL;
static struct dentry *cam_debugfs_root;
int rc = 0;
// 1. 初始化一个 v4l2_device 类型的结构体,并分配好结构体内存
msm_v4l2_dev = kzalloc(sizeof(*msm_v4l2_dev),
GFP_KERNEL);
if (WARN_ON(!msm_v4l2_dev)) {
rc = -ENOMEM;
goto probe_end;
}
pvdev = kzalloc(sizeof(struct msm_video_device),
GFP_KERNEL);
if (WARN_ON(!pvdev)) {
rc = -ENOMEM;
goto pvdev_fail;
}
// 2. 分配 video_device 结构体内存
pvdev->vdev = video_device_alloc();
if (WARN_ON(!pvdev->vdev)) {
rc = -ENOMEM;
goto video_fail;
}
#if defined(CONFIG_MEDIA_CONTROLLER)
// 3. 分配 media_device 结构体内存
msm_v4l2_dev->mdev = kzalloc(sizeof(struct media_device),
GFP_KERNEL);
if (!msm_v4l2_dev->mdev) {
rc = -ENOMEM;
goto mdev_fail;
}
// 4.初始化 media_device 结构体
media_device_init(msm_v4l2_dev->mdev);
strlcpy(msm_v4l2_dev->mdev->model, MSM_CONFIGURATION_NAME,sizeof(msm_v4l2_dev->mdev->model)); //MSM_CONFIGURATION_NAME = "msm_config" 代码中open 节点,会比较是否为smsm_config
msm_v4l2_dev->mdev->dev = &(pdev->dev);
// 5. 注册 media_device , 使用的 v4l2
rc = media_device_register(msm_v4l2_dev->mdev);
/**
media_device_register()
media_devnode_register ()
device_initialize() //初始化media 创建mediaX
cdev_init() //初始化字符设备
cdev_device_add() //
device_create_file(&devnode->dev, &dev_attr_model) // 创建的节点 sys/devices/platform/soc/ca00000.qcom,msm-cam/media0/model
*/
if (WARN_ON(rc < 0))
goto media_fail;
if (WARN_ON((rc == media_entity_pads_init(&pvdev->vdev->entity,
0, NULL)) < 0))
goto entity_fail;
pvdev->vdev->entity.function = QCAMERA_VNODE_GROUP_ID;
#endif
msm_v4l2_dev->notify = msm_sd_notify;
pvdev->vdev->v4l2_dev = msm_v4l2_dev;
// 6. 设置父设备为 pdev->dev (也就是 qcom,msm-cam 的设备信息)
rc = v4l2_device_register(&(pdev->dev), pvdev->vdev->v4l2_dev);
/**
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);
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 */
if (WARN_ON(!v4l2_dev->name[0]))
return -EINVAL;
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));
printk("v4l2_dev->name =%s \n",v4l2_dev->name); //log 信息v4l2_dev->name =msm ca00000.qcom,msm-cam . dev->driver->name 驱动中设置的名字,dev_name(dev) dtsi 中的lable
if (!dev_get_drvdata(dev))
dev_set_drvdata(dev, v4l2_dev);
return 0;
}
*/
if (WARN_ON(rc < 0))
goto register_fail;
// 7. 注册 video_device设备
strlcpy(pvdev->vdev->name, "msm-config", sizeof(pvdev->vdev->name));
pvdev->vdev->release = video_device_release;
pvdev->vdev->fops = &msm_fops;
pvdev->vdev->ioctl_ops = &g_msm_ioctl_ops;
pvdev->vdev->minor = -1;
pvdev->vdev->vfl_type = VFL_TYPE_GRABBER;
rc = video_register_device(pvdev->vdev,
VFL_TYPE_GRABBER, -1); // 节点 /dev/vdieoX
2.3 __video_register_device
以"qcom,msm-cam"为例,其注册时,传递的 nr = -1,说明从第一个开始分配,也就是 /dev/video0。
但是如果有其他先执行video_register_device . /dev/video0 可以是其他值. 可以查看节点
/sys/class/video4linux # cat video0/name
sde_rotator
因为"platform/msm/sde/rotator/sde_rotator_dev.c" 先执行,传入的nr = -1, so /dev/video0 为sde_rotator
kernel/msm-4.4/drivers/media/v4l2-core/v4l2-dev.c
/**
* __video_register_device - register video4linux devices
* @vdev: video device structure we want to register
* @type: type of device to register
* @nr: which device node number (0 == /dev/video0, 1 == /dev/video1, ... -1 == first free)
* @warn_if_nr_in_use: warn if the desired device node number was already in use and another number was chosen instead.
* @owner: module that owns the video device node
*
* The registration code assigns minor numbers and device node numbersbased on the requested type and registers the new device node with the kernel.
*
* This function assumes that struct video_device was zeroed when it was allocated and does not contain any stale date.
*
* An error is returned if no free minor or device node number could be found, or if the registration of the device node failed.
*
* Zero is returned on success.
*
* Valid types are
* %VFL_TYPE_GRABBER - A frame grabber
* %VFL_TYPE_VBI - Vertical blank data (undecoded)
* %VFL_TYPE_RADIO - A radio card
* %VFL_TYPE_SUBDEV - A subdevice
* %VFL_TYPE_SDR - Software Defined Radio
*/
int __video_register_device(struct video_device *vdev, int type, int nr, int warn_if_nr_in_use, struct module *owner)
{
int minor_cnt = VIDEO_NUM_DEVICES;
const char *name_base;
/* A minor value of -1 marks this video device as never having been registered */
vdev->minor = -1;
// 1. 初始化 fh->list
/* v4l2_fh support */
INIT_LIST_HEAD(&vdev->fh_list);
// 2. 检查设备类型
/* Part 1: check device type */
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;
case VFL_TYPE_SDR: name_base = "swradio"; break; /* Use device name 'swradio' because 'sdr' was already taken. */
}
vdev->vfl_type = type; // VFL_TYPE_GRABBER
vdev->cdev = NULL;
// 3. 寻找一个不在使用的 次设备号, 主设备号为 81,(0~63 为video)(128,191 为sub-dev)
/* Part 2: find a free minor, device node number and device index. */
/* Keep the ranges for the first four types for historical reasons.
* Newer devices (not yet in place) should use the range of 128-191 and just pick the first free minor there (new style). */
switch (type) {
case VFL_TYPE_GRABBER: minor_offset = 0; minor_cnt = 64; break;
case VFL_TYPE_RADIO: minor_offset = 64; minor_cnt = 64; break;
case VFL_TYPE_VBI: minor_offset = 224; minor_cnt = 32; break;
default: minor_offset = 128; minor_cnt = 64; break;
}
/* Pick a device node number */
nr = devnode_find(vdev, nr == -1 ? 0 : nr, minor_cnt);
if (nr == minor_cnt)
nr = devnode_find(vdev, 0, minor_cnt);
/* The device node number and minor numbers are independent, so
we just find the first free minor number. */
for (i = 0; i < VIDEO_NUM_DEVICES; i++)
if (video_device[i] == NULL)
break;
vdev->minor = i + minor_offset;
vdev->num = nr;
devnode_set(vdev);
// 4. 获取 index,将当前需要注册的 video_device 设备保存在 video_device[]全局数组中
vdev->index = get_index(vdev);
video_device[vdev->minor] = vdev;
// 5. 分配对应的字符设备 /dev/video0,字符设备号,就是前面的 (81,minor)
/* Part 3: Initialize the character device */
vdev->cdev = cdev_alloc();
vdev->cdev->ops = &v4l2_fops;
vdev->cdev->owner = owner;
ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
// 6. 分配对应的sys节点 /sys/class/video4linux/video0
/* Part 4: register the device with sysfs */
vdev->dev.class = &video_class;
vdev->dev.devt = MKDEV(VIDEO_MAJOR, vdev->minor);
vdev->dev.parent = vdev->dev_parent;
dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num);
ret = device_register(&vdev->dev);
// 7. 注册release 时调用的函数
/* Register the release callback that will be called when the last reference to the device goes away. */
vdev->dev.release = v4l2_device_release;
/* Increase v4l2_device refcount */
v4l2_device_get(vdev->v4l2_dev);
// 8. 将该 v4l2 subdevice 当成一个 entity 注册到 media device
/* Part 5: Register the entity. */
if (vdev->v4l2_dev->mdev && vdev->vfl_type != VFL_TYPE_SUBDEV) {
vdev->entity.type = MEDIA_ENT_T_DEVNODE_V4L;
vdev->entity.name = vdev->name;
vdev->entity.info.dev.major = VIDEO_MAJOR;
vdev->entity.info.dev.minor = vdev->minor;
ret = media_device_register_entity(vdev->v4l2_dev->mdev,&vdev->entity);
}
/* Part 6: Activate this minor. The char device can now be used. */
set_bit(V4L2_FL_REGISTERED, &vdev->flags);
return 0;
}
EXPORT_SYMBOL(__video_register_device);
2.3.1 字符设备操作函数 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,
.unlocked_ioctl = v4l2_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = v4l2_compat_ioctl32,
#endif
.release = v4l2_release,
.poll = v4l2_poll,
.llseek = no_llseek,
};
创建成功 /dev/video0 节点后,后续要打开对应的节点时,会调用 fops对应的操作函数,对应的代码在注册时赋值的。
"kernel/msm-4.19/drivers/media/platform/msm/camera_v2/msm.c"
strlcpy(pvdev->vdev->name, "msm-config", sizeof(pvdev->vdev->name));
pvdev->vdev->release = video_device_release;
pvdev->vdev->fops = &msm_fops;
pvdev->vdev->ioctl_ops = &g_msm_ioctl_ops;
static struct v4l2_file_operations msm_fops = {
.owner = THIS_MODULE,
.open = msm_open,
.poll = msm_poll,
.release = msm_close,
.unlocked_ioctl = video_ioctl2,
#ifdef CONFIG_COMPAT
.compat_ioctl32 = video_ioctl2,
#endif
};
"kernel/msm-4.19/drivers/media/platform/msm/camera_v2/camera/camera.c"
strlcpy(pvdev->vdev->name, "msm-sensor", sizeof(pvdev->vdev->name));
pvdev->vdev->release = video_device_release;
pvdev->vdev->fops = &camera_v4l2_fops;
pvdev->vdev->ioctl_ops = &camera_v4l2_ioctl_ops;
static struct v4l2_file_operations camera_v4l2_fops = {
.owner = THIS_MODULE,
.open = camera_v4l2_open,
.poll = camera_v4l2_poll,
.release = camera_v4l2_close,
.unlocked_ioctl = video_ioctl2,
#ifdef CONFIG_COMPAT
.compat_ioctl32 = camera_v4l2_compat_ioctl,
#endif
};
log 信息
02-11 06:44:53.201 0 0 W : v4l2_open
02-11 06:44:53.201 0 0 W : msm_open
02-11 06:44:54.756 0 0 W : v4l2_open
02-11 06:44:54.756 0 0 W : camera_v4l2_open
根据open 不同的节点调用不同的v4l2_file_operations
2.3.2 v4l2_ioctrl
kernel/msm-4.4/drivers/media/v4l2-core/v4l2-compat-ioctl32.c //iotctrl 同理open ,有默认的v4l2 的 ioctrl , 有对应驱动的ioctrl
long v4l2_compat_ioctl32(struct file *file, unsigned int cmd, unsigned long arg)
{
if (_IOC_TYPE(cmd) == 'V' && _IOC_NR(cmd) < BASE_VIDIOC_PRIVATE)
ret = do_video_ioctl(file, cmd, arg);
else if (vdev->fops->compat_ioctl32)
ret = vdev->fops->compat_ioctl32(file, cmd, arg);
return ret;
}
2.4 注册子设备 /media/v4l2-core/v4l2-subdev.c
当有sub-dev 需要注册到v4l2 时,调用 v4l2_device_register_subdev()函数。
最终调用 __video_register_device(),传递参数 VFL_TYPE_SUBDEV,说明是注册 sub_dev 设备。
参考:https://blog.csdn.net/Ciellee/article/details/105483079
边栏推荐
- Online yaml to JSON tool
- Is it enough for the project manager to finish the PMP? no, it isn't!
- msa.h:没有那个文件或目录
- Animation de ligne
- Zzuli:1071 decomposing prime factor
- Keil C51的Data Overlaying机制导致的函数重入问题
- 双向电平转换电路
- Wireless sensor network learning notes (I)
- Operation of 2022 power cable judgment question simulation examination platform
- MCLK configuration of Qualcomm platform camera
猜你喜欢

jsp连接Oracle实现登录注册

Study on modified triphosphate: lumiprobe amino-11-ddutp

CpG solid support research: lumiprobe general CpG type II

【C语言练习——打印空心正方形及其变形】

WordPress zibll sub theme 6.4.1 happy version is free of authorization

How to do a good job of dam safety monitoring

线条动画

gorm事务体验

Quartus replication IP core
![[JVM] - Division de la mémoire en JVM](/img/d8/29a5dc0ff61e35d73f48effb858770.png)
[JVM] - Division de la mémoire en JVM
随机推荐
JS中的链表(含leetcode例题)<持续更新~>
Online yaml to JSON tool
2022 special operation certificate examination question bank and simulation examination for safety management personnel of fireworks and firecrackers business units
独立站卖家都在用的五大电子邮件营销技巧,你知道吗?
分享|智慧环保-生态文明信息化解决方案(附PDF)
FB、WhatsApp群发消息在2022年到底有多热门?
SlicePlane的Heading角度与Math.atan2(y,x)的对应转换关系
Programmer - Shepherd
Quartus replication IP core
中小型水库大坝安全自动监测系统解决方案
数据中台:数据治理的建设思路以及落地经验
Jdbc的使用
Opencv实现目标检测
Binder面试之:内存管理单元
Zzuli:1071 decomposing prime factor
Realizing color detection with OpenCV
Why does the company choose cloud database? What is its charm!
JSP connects with Oracle to realize login and registration (simple)
109. simple chat room 12: realize client-side one-to-one chat
北斗三号短报文终端在大坝安全监测方案的应用