当前位置: 首页 > news >正文

祝贺网站上线网站信息同步

祝贺网站上线,网站信息同步,搜索引擎有哪些分类,18岁以上准备好纸巾免费网站上一篇我们讲解了如何编写基于V4L2的应用程序编写#xff0c;本文主要讲解内核中V4L2架构#xff0c;以及一些最重要的结构体、注册函数。 厂家在实现自己的摄像头控制器驱动时#xff0c;总体上都遵循这个架构来实现#xff0c;但是不同厂家、不同型号的SoC#xff0c;具…上一篇我们讲解了如何编写基于V4L2的应用程序编写本文主要讲解内核中V4L2架构以及一些最重要的结构体、注册函数。 厂家在实现自己的摄像头控制器驱动时总体上都遵循这个架构来实现但是不同厂家、不同型号的SoC具体的驱动实现仍然会有一些差别。 读者可以通过本文了解各个结构体与对应的摄像头模块、SoC上控制器模块、以及他们之间接口关系并能够了解这些硬件模块与V4L2架构之间关系。 下一张我们基于瑞芯微rk3568来详细讲解具体V4L2的实现。 一、V4L2架构 V4L2子系统是Linux内核中关于Video视频设备的API接口是V4LVideo for Linux子系统的升级版本。 V4LVideo for Linux是Linux内核中关于视频设备的API接口,出现于Linux内核2.1版本经过修改bug和添加功能Linux内核2.5版本推出了V4L2Video for Linux Two子系统功能更多且更稳定。 V4L2子系统向上为虚拟文件系统提供了统一的接口应用程序可通过虚拟文件系统访问Video设备。 V4L2子系统向下给Video设备提供接口同时管理所有Video设备。 二、V4L2架构包括哪些设备 Video设备又分为主设备和从设备对于Camera来说 主设备 Camera Host控制器为主设备负责图像数据的接收和传输 从设备 从设备为Camera Sensor一般为I2C接口可通过从设备控制Camera采集图像的行为如图像的大小、图像的FPS等。 V4L2的主设备号是81次设备号范围0~255 这些次设备号又分为多类设备 视频设备次设备号范围0-63Radio收音机设备次设备号范围64-127Teletext设备次设备号范围192-223VBI设备次设备号范围224-255。 V4L2设备对应的设备节点有**/dev/videoX、/dev/vbiX、/dev/radioX**。 本文只讨论视频设备视频设备对应的设备节点是**/dev/videoX**视频设备以高频摄像头或Camera为输入源Linux内核驱动该类设备接收相应的视频信息并处理。 V4L2框架的架构如下图所示: user space 应用程序主要通过libv4l库来操作摄像头 也可以基于字符设备/dev/videoX自己编写应用程序 guvcview:用于调试usb摄像头还有个软件cheese也可以 v4l2 utilities: v4l2 的工具集参考前面第3篇文章 kernel space sensor、ISP、VIPP、CSI、CCI都为从设备 从dphy物理层获取视频数据册通过vb2子模块 CCI 主要是通过GPIO供电、片选、I2C下发配置命令给sensor实现配置sensor EHCI/OHCIUSB类型摄像头 hardware CSIC Controller从dphy获取mipi协议帧 I2C Controller与sensor的i2c block通信 GPIO Controllersensor通常需要供电或者片选 external device sensror摄像头的接口主要有:USB,DVP.MIPI(CSI) 三、Linux内核中V4L2驱动代码 Linux系统中视频输入设备主要包括以下四个部分 1.字符设备驱动 V4L2本身就是一个字符设备具有字符设备所有的特性暴露接口给用户空间 2.V4L2驱动核心 主要是构建一个内核中标准视频设备驱动的框架为视频操作提供统一的接口函数 3.平台V4L2设备驱动 在V4L2框架下根据平台自身的特性实现与平台相关的V4L2驱动部分包括注册video_device和v4l2_device 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文件实现构建V4L2 ioctl的框架。 瑞芯微平台还包括ISP的驱动框架下面是rk3568对应的ISP相关代码 Linux Kernel-4.19|-- arch/arm/boot/dts DTS配置文件|-- drivers/phy/rockchip|-- phy-rockchip-mipi-rx.c mipi dphy驱动|-- phy-rockchip-csi2-dphy-common.h|-- phy-rockchip-csi2-dphy-hw.c|-- phy-rockchip-csi2-dphy.c|-- drivers/media|-- v4l2-core|-- platform/rockchip/cif RKCIF驱动|-- platform/rockchip/isp RKISP驱动|-- dev.c 包含 probe、异步注册、clock、pipeline、 iommu及media/v4l2 framework|-- capture_v21.c 包含 mp/sp/rawwr的配置及 vb2帧中断处理|-- dmarx.c 包含 rawrd的配置及 vb2帧中断处理|-- isp_params.c 3A相关参数设置|-- isp_stats.c 3A相关统计|-- isp_mipi_luma.c mipi数据亮度统计|-- regs.c 寄存器相关的读写操作|-- rkisp.c isp subdev和entity注册,包含从 mipi 接收数据并有 crop 功能|-- csi.c csi subdev和mipi配置|-- bridge.c bridge subdevisp和ispp交互桥梁|-- platform/rockchip/ispp rkispp驱动|-- dev.c 包含 probe、异步注册、clock、pipeline、 iommu及media/v4l2 framework|-- stream.c 包含 4路video输出的配置及 vb2帧中断处理|-- rkispp.c ispp subdev和entity注册|-- params.c TNR/NR/SHP/FEC/ORB参数设置|-- stats.c ORB统计信息|-- i2c|-- ov13850.c CIS(cmos image sensor)驱动四、结构体详解 V4L2中有几个最重要的几个结构体v4l2_device、video_device、v4l2_subdev等。 他们大致关系如下 1.v4l2_device主设备 V4L2主设备实例使用struct v4l2_device结构体表示v4l2_device是V4L2子系统的入口管理着V4L2子系统的主设备和从设备 v4l2_device用来描述一个v4l2设备实例可以包含多个子设备对应的是例如 I2C、CSI、MIPI 等设备它们是从属于一个 V4L2 device 之下的 简单设备可以仅分配这个结构体但在大多数情况下都会将这个结构体嵌入到一个更大的结构体中以提供v4l2框架的功能比如struct isp_device 需要与媒体框架整合的驱动必须手动设置dev-driver_data指向包含v4l2_device结构体实例的驱动特定设备结构体。这可以在注册V4L2设备实例前通过dev_set_drvdata()函数完成。 同时必须设置v4l2_device结构体的mdev域指向适当的初始化并注册过的media_device实例。 [include/media/v4l2-device.h]struct v4l2_device {struct device *dev; // 父设备指针#if defined(CONFIG_MEDIA_CONTROLLER) // 多媒体设备配置选项// 用于运行时数据流的管理struct media_device *mdev;#endif// 注册的子设备的v4l2_subdev结构体都挂载此链表中struct list_head subdevs;// 同步用的自旋锁spinlock_t lock;// 独一无二的设备名称默认使用driver name bus IDchar name[V4L2_DEVICE_NAME_SIZE];// 被一些子设备回调的通知函数但这个设置与子设备相关。子设备支持的任何通知必须在// include/media/subdevice.h 中定义一个消息头。void (*notify)(struct v4l2_subdev *sd, unsigned int notification, void *arg);// 提供子设备主要是video和ISP设备在用户空间的特效操作接口// 比如改变输出图像的亮度、对比度、饱和度等等struct v4l2_ctrl_handler *ctrl_handler;// 设备优先级状态struct v4l2_prio_state prio;/* BKL replacement mutex. Temporary solution only. */struct mutex ioctl_lock;// struct v4l2_device结构体的引用计数等于0时才释放struct kref ref;// 引用计数ref为0时调用release函数进行释放资源和清理工作void (*release)(struct v4l2_device *v4l2_dev);};注册函数 v4l2_device_register使用v4l2_device_register注册v4l2_device结构体.如果v4l2_dev-name为空则它将被设置为从dev中衍生出的值为了更加精确形式为驱动名后跟bus_id。 如果在调用v4l2_device_register前已经设置好了则不会被修改。如果dev为NULL则必须在调用v4l2_device_register前设置v4l2_dev-name。可以基于驱动名和驱动的全局atomic_t类型的实例编号通过v4l2_device_set_name()设置name。 这样会生成类似ivtv0、ivtv1等名字。若驱动名以数字结尾则会在编号和驱动名间插入一个破折号如cx18-0、cx18-1等。 dev参数通常是一个指向pci_dev、usb_interface或platform_device的指针很少使其为NULL除非是一个ISA设备或者当一个设备创建了多个PCI设备使得v4l2_dev无法与一个特定的父设备关联。 使用v4l2_device_unregister卸载v4l2_device结构体。如果dev-driver_data域指向 v4l2_dev将会被重置为NULL。主设备注销的同时也会自动注销所有子设备。如果你有一个热插拔设备如USB设备则当断开发生时父设备将无效。 由于v4l2_device有一个指向父设备的指针必须被清除同时标志父设备已消失所以必须调用v4l2_device_disconnect函数清理v4l2_device中指向父设备的dev指针。v4l2_device_disconnect并不注销主设备因此依然要调用v4l2_device_unregister函数注销主设备。 [include/media/v4l2-device.h]// 注册v4l2_device结构体并初始化v4l2_device结构体// dev-父设备结构体指针若为NULL在注册之前设备名称name必须被设置// v4l2_dev-v4l2_device结构体指针// 返回值-0成功小于0-失败int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)// 卸载注册的v4l2_device结构体// v4l2_dev-v4l2_device结构体指针void v4l2_device_unregister(struct v4l2_device *v4l2_dev)// 设置设备名称填充v4l2_device结构体中的name成员// v4l2_dev-v4l2_device结构体指针// basename-设备名称基本字符串// instance-设备计数调用v4l2_device_set_name后会自加1// 返回值-返回设备计数自加1的值int v4l2_device_set_name(struct v4l2_device *v4l2_dev, const char *basename, atomic_t *instance)// 热插拔设备断开时调用此函数// v4l2_dev-v4l2_device结构体指针void v4l2_device_disconnect(struct v4l2_device *v4l2_dev); 同一个硬件的情况下。如ivtvfb驱动是一个使用ivtv硬件的帧缓冲驱动同时alsa驱动也使用此硬件。可以使用如下例程遍历所有注册的设备static int callback(struct device *dev, void *p){struct v4l2_device *v4l2_dev dev_get_drvdata(dev);/* 测试这个设备是否已经初始化 */if (v4l2_dev NULL)return 0;...return 0;}int iterate(void *p){struct device_driver *drv;int err;/* 在PCI 总线上查找ivtv驱动。pci_bus_type是全局的. 对于USB总线使用usb_bus_type。 */drv driver_find(ivtv, pci_bus_type);/* 遍历所有的ivtv设备实例 */err driver_for_each_device(drv, NULL, p, callback);put_driver(drv);return err;}2. video_device V4L2子系统使用v4l2_device结构体管理设备设备的具体操作方法根据设备类型决定 前面说过管理的设备分为很多种 若是视频设备则需要注册video_device结构体并提供相应的操作方法。 对于视频设备Camera而言Camera控制器可以视为主设备接在Camera控制器上的摄像头可以视为从设备。 struct video_device {const struct v4l2_file_operations *fops;struct cdev *cdev; //vdev-cdev-ops v4l2_fops; 字符设备描述符struct v4l2_device *v4l2_dev;struct v4l2_ctrl_handler *ctrl_handler;struct vb2_queue *queue; //q-ops dmarx_vb2_ops; buf操作真正驱动回调函数…………const struct v4l2_ioctl_ops *ioctl_ops;//vdev-ioctl_ops rkisp_dmarx_ioctl; ………… };注册函数 [rk_android11.0_sdk_220718\kernel\drivers\media\v4l2-core\v4l2-dev.c]static inline int __must_check video_register_device(struct video_device *vdev,int type, int nr) {return __video_register_device(vdev, type, nr, 1, vdev-fops-owner); } 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;//次设备个数默认为256const char *name_base;/* A minor value of -1 marks this video device as neverhaving been registered */vdev-minor -1;/* the release callback MUST be present 如果之前没有声明销毁函数则报错*/if (WARN_ON(!vdev-release))return -EINVAL;/* the v4l2_dev pointer MUST be present 如果之前未注册v4l2_device则报错*/if (WARN_ON(!vdev-v4l2_dev))return -EINVAL;/* Part 1: check device type */switch (type) {//根据设备类型类注册设备摄像头设备为VFL_TYPE_GRABBER类型case VFL_TYPE_GRABBER:name_base video;··········vdev-vfl_type type;vdev-cdev NULL;if (vdev-dev_parent NULL)vdev-dev_parent vdev-v4l2_dev-dev;if (vdev-ctrl_handler NULL)//设置video_device的ctrl_handler存在v4l2_device结构体中vdev-ctrl_handler vdev-v4l2_dev-ctrl_handler;/* Part 2: find a free minor, device node number and device index. *//*2.寻找空闲次设备号设备个数和设备下标*//* Pick a device node number 寻找一个空项位置*/mutex_lock(videodev_lock);nr devnode_find(vdev, nr -1 ? 0 : nr, minor_cnt);//if (nr minor_cnt)nr devnode_find(vdev, 0, minor_cnt);if (nr minor_cnt) {printk(KERN_ERR could not get a free device node number\n);mutex_unlock(videodev_lock);return -ENFILE;} #ifdef CONFIG_VIDEO_FIXED_MINOR_RANGES/* 1-on-1 mapping of device node number to minor number */i nr; #else/* 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;if (i VIDEO_NUM_DEVICES) {mutex_unlock(videodev_lock);printk(KERN_ERR could not get a free minor\n);return -ENFILE;} #endifvdev-minor i minor_offset;vdev-num nr;devnode_set(vdev);/* Should not happen since we thought this minor was free */vdev-index get_index(vdev);video_device[vdev-minor] vdev;if (vdev-ioctl_ops)determine_valid_ioctls(vdev);/* Part 3: Initialize the character device */vdev-cdev cdev_alloc();if (vdev-cdev NULL) {ret -ENOMEM;goto cleanup;}vdev-cdev-ops v4l2_fops;//设置字符设备的系统调用函数vdev-cdev-owner owner;//注册字符设备ret cdev_add(vdev-cdev, MKDEV(VIDEO_MAJOR, vdev-minor), 1);/* 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;//设置video结点名称如果设备类型为VFL_TYPE_GRABBER名称为videoXdev_set_name(vdev-dev, %s%d, name_base, vdev-num);//注册device文件生成设备文件/dev/videoXret device_register(vdev-dev);/* Register the release callback that will be called when the lastreference to the device goes away. *///设置销毁video设备的回调函数vdev-dev.release v4l2_device_release;/* Increase v4l2_device refcount */v4l2_device_get(vdev-v4l2_dev);这个函数主要做四件事 检查设备类型赋予设备名称寻找一个空闲的设备位置寻找合适的主设备号和次设号初始化字符设备使用v4l2_device的v4l2_fops初始化video_device的fops,release函数等注册字符设备并生成/dev/videoX结点注册subdev时也会调用这个接口 3. v4l2_subdev从设备 V4L2从设备使用struct v4l2_subdev结构体表示该结构体用于对子设备进行抽象。 几乎所有的设备都有多个 IC 模块 它们可能是实体的例如 USB 摄像头里面包含 ISP、sensor 等也可能是抽象的如 USB 设备里面的抽象拓扑结构它们在 /dev 目录下面生成了多个设备节点并且这些 IC 模块还创建了一些非 v4l2 设备DVB、ALSA、FB、I2C 和输入设备。 通常情况下这些IC模块通过一个或者多个 I2C 总线连接到主桥驱动上面同时其它的总线仍然可用这些 IC 就称为 ‘sub-devices’。 一个V4L2主设备可能对应多个V4L2从设备所有主设备对应的从设备都挂到v4l2_device结构体的subdevs链表中。 对于视频设备从设备就是摄像头通常情况下是I2C设备主设备可通过I2C总线控制从设备 例如控制摄像头的焦距、闪光灯等同时使用 MIPI 或者 LVDS 等接口进行图像数据传输。 struct v4l2_subdev中包含的struct v4l2_subdev_ops是一个完备的操作函数集用于对接各种不同的子设备比如video、audio、sensor等 同时还有一个核心的函数集struct v4l2_subdev_core_ops提供更通用的功能。 子设备驱动根据设备特点实现该函数集中的某些函数即可。 [include/media/v4l2-subdev.h]#define V4L2_SUBDEV_FL_IS_I2C (1U 0) // 从设备是I2C设备#define V4L2_SUBDEV_FL_IS_SPI (1U 1) // 从设备是SPI设备#define V4L2_SUBDEV_FL_HAS_DEVNODE (1U 2) // 从设备需要设备节点#define V4L2_SUBDEV_FL_HAS_EVENTS (1U 3) // 从设备会产生事件struct v4l2_subdev {#if defined(CONFIG_MEDIA_CONTROLLER) // 多媒体配置选项struct media_entity entity;#endifstruct list_head list; // 子设备串联链表struct module *owner; // 属于那个模块一般指向i2c_lient驱动模块bool owner_v4l2_dev;// 标志位确定该设备属于那种设备由V4L2_SUBDEV_FL_IS_XX宏确定u32 flags;// 指向主设备的v4l2_device结构体struct v4l2_device *v4l2_dev;// v4l2子设备的操作函数集合const struct v4l2_subdev_ops *ops;// 提供给v4l2框架的操作函数只有v4l2框架会调用驱动不使用const struct v4l2_subdev_internal_ops *internal_ops;// 从设备的控制接口struct v4l2_ctrl_handler *ctrl_handler;// 从设备的名称必须独一无二char name[V4L2_SUBDEV_NAME_SIZE];// 从设备组的ID由驱动定义相似的从设备可以编为一组u32 grp_id;// 从设备私有数据指针一般指向i2c_client的设备结构体devvoid *dev_priv;// 主设备私有数据指针一般指向v4l2_device嵌入的结构体void *host_priv;// 指向video设备结构体struct video_device *devnode;// 指向物理设备struct device *dev;// 将所有从设备连接到全局subdev_list链表或notifier-done链表struct list_head async_list;// 指向struct v4l2_async_subdev用于异步事件struct v4l2_async_subdev *asd;// 指向管理的notifier用于主设备和从设备的异步关联struct v4l2_async_notifier *notifier;/* common part of subdevice platform data */struct v4l2_subdev_platform_data *pdata;};// 提供给v4l2框架的操作函数只有v4l2框架会调用驱动不使用struct v4l2_subdev_internal_ops {// v4l2_subdev注册时回调此函数使v4l2_dev指向主设备的v4l2_device结构体int (*registered)(struct v4l2_subdev *sd);// v4l2_subdev卸载时回调此函数void (*unregistered)(struct v4l2_subdev *sd);// 应用调用open打开从设备节点时调用此函数int (*open)(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh);// 应用调用close时调用此函数int (*close)(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh);};使用v4l2_subdev_init初始化v4l2_subdev结构体。然后必须用一个唯一的名字初始化subdev-name同时初始化模块的owner域。 若从设备是I2C设备则可使用v4l2_i2c_subdev_init函数进行初始化该函数内部会调用v4l2_subdev_init同时设置flags、owner、dev、name等成员。 [include/media/v4l2-subdev.h]// 初始化v4l2_subdev结构体// ops-v4l2子设备的操作函数集合指针保存到v4l2_subdev结构体的ops成员中void v4l2_subdev_init(struct v4l2_subdev *sd,const struct v4l2_subdev_ops *ops);[include/media/v4l2-common.h]// 初始化V4L2从设备为I2C设备的v4l2_subdev结构体// sd-v4l2_subdev结构体指针// client-i2c_client结构体指针// ops-v4l2子设备的操作函数集合指针保存到v4l2_subdev结构体的ops成员中void v4l2_i2c_subdev_init(struct v4l2_subdev *sd, struct i2c_client *client,const struct v4l2_subdev_ops *ops);从设备必须向V4L2子系统注册v4l2_subdev结构体使用v4l2_device_register_subdev注册使用v4l2_device_unregister_subdev注销。 [include/media/v4l2-device.h]// 向V4L2子系统注册v4l2_subdev结构体// v4l2_dev-主设备v4l2_device结构体指针// sd-从设备v4l2_subdev结构体指针// 返回值 0-成功小于0-失败int v4l2_device_register_subdev(struct v4l2_device *v4l2_dev,struct v4l2_subdev *sd)// 从V4L2子系统注销v4l2_subdev结构体// sd-从设备v4l2_subdev结构体指针 void v4l2_device_unregister_subdev(struct v4l2_subdev *sd);V4L2从设备驱动都必须有一个v4l2_subdev结构体。 这个结构体可以单独代表一个简单的从设备也可以嵌入到一个更大的结构体中与更多设备状态信息保存在一起。通常有一个下级设备结构体比如i2c_client包含了内核创建的设备数据。 建议使用v4l2_set_subdevdata()将这个结构体的指针保存在v4l2_subdev的私有数据域(dev_priv)中。可以更方便的通过v4l2_subdev找到实际的低层总线特定设备数据。 对于常用的i2c_client结构体i2c_set_clientdata函数可用于保存一个v4l2_subdev指针i2c_get_clientdata可以获取一个v4l2_subdev指针对于其他总线可能需要使用其他相关函数。 [include/media/v4l2-subdev.h]// 将i2c_client的指针保存到v4l2_subdev结构体的dev_priv成员中static inline void v4l2_set_subdevdata(struct v4l2_subdev *sd, void *p){sd-dev_priv p;}[include/linux/i2c.h]// 可以将v4l2_subdev结构体指针保存到i2c_client中dev成员的driver_data中static inline void i2c_set_clientdata(struct i2c_client *dev, void *data){dev_set_drvdata(dev-dev, data);}// 获取i2c_client结构体中dev成员的driver_data一般指向v4l2_subdevstatic inline void *i2c_get_clientdata(const struct i2c_client *dev){return dev_get_drvdata(dev-dev);}主设备驱动中也应保存每个子设备的私有数据比如一个指向特定主设备的各设备私有数据的指针。为此v4l2_subdev结构体提供主设备私有数据域(host_priv)并可通过v4l2_get_subdev_hostdata和 v4l2_set_subdev_hostdata访问。 [include/media/v4l2-subdev.h]static inline void *v4l2_get_subdev_hostdata(const struct v4l2_subdev *sd){return sd-host_priv;}static inline void v4l2_set_subdev_hostdata(struct v4l2_subdev *sd, void *p){sd-host_priv p;}每个v4l2_subdev都包含子设备驱动需要实现的函数指针如果对此设备不适用可为NULL具体在v4l2_subdev_ops结构体当中。 由于子设备可完成许多不同的工作而在一个庞大的函数指针结构体中通常仅有少数有用的函数实现其功能肯定不合适。 所以函数指针根据其实现的功能被分类每一类都有自己的函数指针结构体如v4l2_subdev_core_ops、v4l2_subdev_audio_ops、v4l2_subdev_video_ops等等。 顶层函数指针结构体包含了指向各类函数指针结构体的指针如果子设备驱动不支持该类函数中的任何一个功能则指向该类结构体的指针为NULL。 [include/media/v4l2-subdev.h]/* v4l2从设备的操作函数集合从设备根据自身设备类型选择实现其中core函数集通常可用于所有子设备其他类别的实现依赖于子设备。如视频设备可能不支持音频操作函数反之亦然。这样的设置在限制了函数指针数量的同时还使增加新的操作函数和分类变得较为容易。 */struct v4l2_subdev_ops {// 从设备的通用操作函数集合进行初始化、reset、控制等操作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; // VBI设备const struct v4l2_subdev_ir_ops *ir;const struct v4l2_subdev_sensor_ops *sensor;const struct v4l2_subdev_pad_ops *pad;};// 适用于所有v4l2从设备的操作函数集合struct v4l2_subdev_core_ops {// IO引脚复用配置int (*s_io_pin_config)(struct v4l2_subdev *sd, size_t n,struct v4l2_subdev_io_pin_config *pincfg);// 初始化从设备的某些寄存器使其恢复默认int (*init)(struct v4l2_subdev *sd, u32 val);// 加载固件int (*load_fw)(struct v4l2_subdev *sd);// 复位int (*reset)(struct v4l2_subdev *sd, u32 val);// 设置GPIO引脚输出值int (*s_gpio)(struct v4l2_subdev *sd, u32 val);// 设置从设备的电源状态0-省电模式1-正常操作模式int (*s_power)(struct v4l2_subdev *sd, int on);// 中断函数被主设备的中断函数调用int (*interrupt_service_routine)(struct v4l2_subdev *sd,u32 status, bool *handled);......};使用v4l2_device_register_subdev注册从设备后就可以调用v4l2_subdev_ops中的方法了。 可以通过v4l2_subdev直接调用也可以使用内核提供的宏定义v4l2_subdev_call间接调用某一个方法。 若要调用多个从设备的同一个方法则可使用v4l2_device_call_all宏定义。 // 直接调用err sd-ops-video-g_std(sd, norm);// 使用宏定义调用这个宏将会做NULL指针检查如果su为NULL则返回-ENODEV// 如果sd-ops-video或sd-ops-video-g_std为NULL则返回-ENOIOCTLCMD// 否则将返回sd-ops-video-g_std的调用的实际结果err v4l2_subdev_call(sd, video, g_std, norm);[include/media/v4l2-subdev.h]#define v4l2_subdev_call(sd, o, f, args...) \(!(sd) ? -ENODEV : (((sd)-ops-o (sd)-ops-o-f) ? \(sd)-ops-o-f((sd) , ##args) : -ENOIOCTLCMD))v4l2_device_call_all(v4l2_dev, 0, video, g_std, norm);[include/media/v4l2-device.h]#define v4l2_device_call_all(v4l2_dev, grpid, o, f, args...) \do { \struct v4l2_subdev *__sd; \__v4l2_device_call_subdevs_p(v4l2_dev, __sd, \!(grpid) || __sd-grp_id (grpid), o, f , \##args); \} while (0)如果子设备需要通知它的v4l2_device主设备一个事件可以调用v4l2_subdev_notify(sd,notification, arg)。 这个宏检查是否有一个notify回调被注册如果没有返回-ENODEV。否则返回 notify调用结果。notify回调函数由主设备提供。 [include/media/v4l2-device.h]// 从设备通知主设备最终回调到v4l2_device的notify函数static inline void v4l2_subdev_notify(struct v4l2_subdev *sd,unsigned int notification, void *arg){if (sd sd-v4l2_dev sd-v4l2_dev-notify)sd-v4l2_dev-notify(sd, notification, arg);}使用v4l2_subdev的好处在于它是一个通用结构体且不包含任何底层硬件信息。 所有驱动可以包含多个I2C总线的从设备但也有从设备是通过GPIO控制。这个区别仅在配置设备时有关系一旦子设备注册完成对于v4l2子系统来说就完全透明了。 4. v4l2_fh 文件访问控制 5. v4l2_ctrl_handler 控制模块提供子设备主要是 video 和 ISP 设备在用户空间的特效操作接口 6. media_device 用于运行时数据流的管理嵌入在 V4L2 device 内部 五、 video_device、v4l2_device和v4l2_subdev的关系举例 下面以我们手机的摄像头来举例 假定一款CMOS摄像头有两个接口一个是摄像头接口(数据)一个是I2C接口(控制命令) 摄像头接口负责传输图像数据I2C接口负责传输控制信息所以又可以将CMOS摄像头看作是一个I2C模块 在一款SoC芯片上面摄像头相关的有摄像头控制器、摄像头接口、I2C总线 SOC上可以有多个摄像头控制器多个摄像头接口多个I2C总线 摄像头控制器负责接收和处理摄像头数据摄像头接口负责传输图像数据I2C总线负责传输控制信息 对于手机而言一般都有两个摄像头一个前置摄像头一个后置摄像头 如下图所示 我们可以选择让控制器去操作哪一个摄像头可以使用某个gpio供电通过电平来选择摄像头这就做到了使用一个摄像头控制器来控制多个摄像头这就是多路复用 我们回到V4L2来再来谈v4l2_device和v4l2_subdev v4l2_device表示一个v4l2实例在V4L2驱动中使用v4l2_device来表示摄像头控制器使用v4l2_subdev来表示具体的某一个摄像头的I2C控制模块进而通过其控制摄像头v4l2_device里有一个v4l2_subdev链表可以选择v4l2_device去控制哪一个v4l2_subdev subdev的设计目的是为了多路复用就是用一个v4l2_device可以服务多个v4l2_subdev 然而某些驱动是没有v4l2_subdev只有video_device 我们用一张图来总结设备之间关系 video_device是一个字符设备video_device内含一个cdevv4l2_device是一个v4l2实例嵌入到video_device中v4l2_device维护者一个链表管理v4l2_subdevv4l2_subdev表示摄像头的I2C控制模块主设备可通过v4l2_subdev_call的宏调用从设备提供的方法反过来从设备可以调用主设备的notify方法通知主设备某些事件发生了。 核心层core负责注册字符设备然后提供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来操作硬件。 一口君再把各个结构体与各回调函数之间关系汇总到下面这个图里(rk3568) 主要架构部分Linux内核已经实现了Camera控制器驱动厂家一般都会实现对于一般驱动工程师来说我们只需要实现子设备驱动即可。 六、videobuf2 从数据流角度来分析V4L2框架可以分成两个部分看控制流数据流 控制流主要由v4l2_subdev的回调函数实现(一般由摄像头厂商提供)主要用于控制摄像数据流的部分就是video buffer驱动部分通常由SoC厂商提供比如瑞芯微rk3568平台,对应到rkisp_rawrd0_m、rkisp_rawrd2_s子模块。 V4L2的buffer管理是通过videobuf2来完成的它充当用户空间和驱动之间的中间层并提供low-level模块化的内存管理功能 获取摄像头视频流的主要步骤如下 要获取图像信息需要执行VIDIOC_DQBUF、VIDIOC_QBUF命令。 瑞芯微rk3568平台videobuf2相关结构体和ops回调函数关系如下 其中struct rkisp_device是瑞芯微3568平台用于管理Camera控制器的最重要的结构体 struct rkisp_capture_device 对应拓扑结构中的模块rkisp_rawrd0_m 、rkisp_rawrd2_s 。 该模块是一个video设备用于获取原始图像信息所以在struct rkisp_vdev_node vnode中包含了struct vb2_queue buf_queue、struct video_device vdev struct vb2_queue中的回调函数struct vb2_mem_ops *mem_ops、struct vb2_buf_ops *buf_ops、struct vb2_ops *ops就是videobuf2驱动。 videobuf2驱动部分相关结构体如下 上图大体包含了videobuf2的框架 vb2_queue 核心的数据结构用于描述buffer的队列其中struct vb2_buffer *bufs[]是存放buffer节点的数组该数组中的成员代表了vb2 buffer并将在queued_list和done_list两个队列中进行流转struct vb2_buf_ops buffer的操作函数集由驱动来实现并由框架通过call_bufop宏来对特定的函数进行调用struct vb2_mem_ops 内存buffer分配函数接口buffer类型分为三种 1虚拟地址和物理地址都分散可以通过dma-sg来完成 2物理地址分散虚拟地址连续可以通过vmalloc分配 3物理地址连续可以通过dma-contig来完成三种类型也vb2框架中都有实现框架可以通过call_memop来进行调用struct vb2_ops vb2队列操作函数集由驱动来实现对应的接口并在框架中通过call_vb_qop宏被调用 调用流程 通用接口 ----------isp ioctrl接口---------- 驱动 字符设备-v4l2_ioctl-v4l_qbuf-vb2_ioctl_qbuf-vb2_qbuf-vb2_core_qbuf-rkisp_buf_queue下面是VIDIOC_DQBUF命令执行的 log【在函数vb2_core_dqbuf入口调用stack_dump()】 /* */ [ 105.813743] vb2_core_dqbuf0x54/0x5b8 [ 105.813753] vb2_dqbuf0x94/0xc8 [ 105.813763] vb2_ioctl_dqbuf0x50/0x60[ 105.813774] v4l_dqbuf0x44/0x58 [ 105.813785] __video_do_ioctl0x1a0/0x348 [ 105.813795] video_usercopy0x228/0x740 [ 105.813805] video_ioctl20x14/0x20 [ 105.813815] v4l2_ioctl0x44/0x68 [ 105.813825] v4l2_compat_ioctl320x1d0/0x3a48[ 105.813836] __arm64_compat_sys_ioctl0xbc/0x15b0 [ 105.813847] el0_svc_common.constprop.00x64/0x178 [ 105.813859] el0_svc_compat_handler0x18/0x20 [ 105.813869] el0_svc_compat0x8/0x34VIDIOC_QBUF命令执行的log [ 105.944858] vb2_core_qbuf0x28/0x338 [ 105.944883] vb2_qbuf0x6c/0x90 [ 105.944904] vb2_ioctl_qbuf0x48/0x58 [ 105.944928] v4l_qbuf0x44/0x58 [ 105.944951] __video_do_ioctl0x1a0/0x348 [ 105.944972] video_usercopy0x228/0x740 [ 105.944993] video_ioctl20x14/0x20 [ 105.945013] v4l2_ioctl0x44/0x68 [ 105.945036] v4l2_compat_ioctl320x1d0/0x3a48 [ 105.945058] __arm64_compat_sys_ioctl0xbc/0x15b0 [ 105.945082] el0_svc_common.constprop.00x64/0x178 [ 105.945105] el0_svc_compat_handler0x18/0x20 [ 105.945125] el0_svc_compat0x8/0x34七、v4l2拓扑结构 关于如何使用设备树节点描述拓扑结构后续文章会详细讲解。 文中各种mipi技术文档后台回复关键字mipi 后面还会继续更新几篇Camera文章 建议大家订阅本专题 也可以后台留言加一口君好友yikoupeng 拉你进高质量技术交流群。
http://www.w-s-a.com/news/756847/

相关文章:

  • 网站建设前的规划网站建设公司六安
  • 公司注册网站开发的行业表述南宁在百度上建网站
  • 创建企业网站国内网站用django做的
  • 云主机网站的空间在哪制作微网站的平台
  • 长沙做网站 青创互联wordpress4.4.1
  • 宜昌哪里有专业做网站的网站开发做什么的
  • 3小说网站开发东莞网站公司哪家好
  • 做网站安全联盟解ps网站设计概述
  • 聊城公司做网站wordpress连接域名
  • 宣传网站建设的意义台州行app官网下载
  • 温州 网站优化网站开发公司前置审批
  • 网站开发具体的工作内容网站下载app免费
  • seo网站建设时文章频率昆山网站建设ikelv
  • 中天建设中瑞物资网站优化建立生育支持政策体系
  • 网站页面的宽度大网站怎样选域名
  • icp网站备案流程wordpress post 405
  • 网站怎样上传到空间重庆有多少网站
  • 用模板建商城购物网站嘉定专业网站建设
  • 网站开发与应用 论文dede手机医院网站模板
  • 织梦 网站栏目管理 很慢自学网页设计难吗
  • 茶文化建设网站的意义平顶山网站建设服务公司
  • 建设网站详细流程南京宣传片制作公司
  • 合肥网站排名什么网站做电气自动化兼职
  • 如何用api做网站交通建设门户网站
  • 阳西住房和城乡规划建设局网站长沙网站seo技巧
  • 长沙知名网站推广手机画设计图软件
  • 顺德公司做网站自己有网站怎么优化
  • 南京网站开发南京乐识专业外贸流程知乎
  • 盐田区住房和建设局网站分类网站有哪些
  • 建一个团购网站WordPress文章字号设置