一、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.cstatic 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: 81printk(KERN_INFO "Linux video capture interface: v2.00\n");// 1. 将字符设备号(81,0) 到 (81,255) 这期间256个字次设备号,均申请为 v4l2 使用,name=video4linuxret = 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_camerav4l2_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;
#endifv4l2_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,cif (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 IOCTRLpvdev->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_configmsm_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 创建mediaXcdev_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;
#endifmsm_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 中的lableif (!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_GRABBERvdev->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, sowe 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_open02-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

V4L2 驱动层分析相关推荐

  1. V4L2驱动开发详解

    环境: OS:Ubuntu 16.04 (Win10 hypev) Kernel Version:3.13.0-24-generic 这里终极目标是注册一个/dev/video0的设备,再通过一个应用 ...

  2. 基于全志A64平台v4l2驱动分析

    纪念再一次使用这里,刚好开通好博客,写下近年来的第一篇. 最近要做一个全志A64平台的vfe驱动培训,组织了下v4l2与vfe驱动分析.这里记录下. 全志A64芯片csi部份不自带isp(其实是有带一 ...

  3. V4L2驱动框架简单分析

    转自:https://www.cnblogs.com/huty/p/8518234.html 概述 Video4Linux2是Linux内核中关于视频设备的内核驱动框架,为上层的访问底层的视频设备提供 ...

  4. 【Android 事件分发】事件分发源码分析 ( 驱动层通过中断传递事件 | WindowManagerService 向 View 层传递事件 )

    Android 事件分发 系列文章目录 [Android 事件分发]事件分发源码分析 ( 驱动层通过中断传递事件 | WindowManagerService 向 View 层传递事件 ) 文章目录 ...

  5. 【鸿蒙OS开发入门】18 - HDF驱动子系统:加速度计传感器 Driver层驱动代码分析

    [鸿蒙OS开发入门]18 - HDF驱动子系统:加速度计传感器 Driver层代码分析 一.如何添加速度计传感器驱动代码(代码.编译.配置) 1.驱动代码实现 2.驱动编译配置 2.1 linux 编 ...

  6. 深入学习Linux摄像头(二)v4l2驱动框架

    深入学习Linux摄像头系列 深入学习Linux摄像头(一)v4l2应用编程 深入学习Linux摄像头(二)v4l2驱动框架 深入学习Linux摄像头(三)虚拟摄像头驱动分析 深入学习Linux摄像头 ...

  7. v4l2驱动编写篇【转】

    转自:http://blog.csdn.net/michaelcao1980/article/details/53008418 大部分所需的信息都在这里.作为一个驱动作者,当挖掘头文件的时候,你可能也 ...

  8. Linux V4l2驱动 -- 框架概述

    V4l2框架简述 1 硬件 常用的电脑摄像头是USB接口,主流的智能手机摄像头是MIPI接口,另外还有像树莓派等硬件使用的CSI接口的设备.常用的智能手机Camera采用的MIPI接口,电路框架以及电 ...

  9. V4L2系列 之 V4L2驱动框架

    目录 前言 一.V4L2驱动框架概览 1.应用层 ->中间层->驱动层 2.主要代码文件(Linux 4.19版本内核) 二.怎么写V4L2驱动 1.如何写一个设备的驱动? 2.Video ...

最新文章

  1. qpython3安装kivy_kivy库的安装
  2. SRAM(静态随机存储器)
  3. .net core WebApi 使用Swagger生成API文档
  4. 计算机PS英语词汇,操作计算机必读(必会)的53个英文单词共享
  5. fcfs调度算法_FCFS:先来先服务调度算法
  6. 合同的不含税与税额怎么算
  7. 学习language C
  8. stm32—光敏电阻传感器的初步使用
  9. 传统运维 VS 互联网运维 框架体系大观
  10. 主动降噪(ANC),让你静心聆听声音
  11. ecshop修改模板可输出php代码,修改ecshop模板体会
  12. 最大面额钞票10的21次方
  13. windows7安装中文语言包及文乱码问题解决方法
  14. 为何互联网大厂都在采用蓝绿发布、滚动发布、灰度发布?
  15. sql server查询分析器的一个选项...
  16. 360安卓_数据 | TalkingData:360手机助手份额32.27%居第一
  17. HDUOJ 4686 Arc of Dream
  18. 看漫画MHGmhgui,Python爬虫之神奇的eval,附赠一个压缩模块
  19. 如何才能静下心来学习?
  20. mega16实现跑马灯

热门文章

  1. 深入研究Espresso的Idling Resource
  2. ewb文件用multisim打开_EWB的使用
  3. python五角星程序显示错误_python画五角星和六角星程序
  4. 【JZOJ5234】外星人的路径
  5. Python学习:垃圾回收机制
  6. 冷冰:Android核心分析系列
  7. 博弈问题总集第三类----Staircase Nim
  8. java 数组总结(赋值,反转,添加,查找)
  9. PMCAFF微课堂|范冰:Growth Hacker——用能量颠覆你的想象
  10. Android反编译工具与反编译步骤及常见问题