struct v4l2\_device \*v4l2_dev; //指向父设备
const struct v4l2\_subdev\_ops \*ops; //提供一些控制v4l2设备的接口
const struct v4l2\_subdev\_internal\_ops \*internal_ops; //向V4L2框架提供的接口函数
struct v4l2\_ctrl\_handler \*ctrl_handler; //subdev控制接口
char name[V4L2_SUBDEV_NAME_SIZE]; //子设备名
u32 grp_id;
void \*dev_priv;
void \*host_priv;
struct video\_device \*devnode; //video\_device 设备节点
struct device \*dev;
struct fwnode\_handle \*fwnode;
struct list\_head async_list;
struct v4l2\_async\_subdev \*asd;
struct v4l2\_async\_notifier \*notifier;
struct v4l2\_async\_notifier \*subdev_notifier;
struct v4l2\_subdev\_platform\_data \*pdata;
};
下面两个结构体是
const struct v4l2_subdev_ops *ops; //提供一些控制v4l2设备的接口
const struct v4l2_subdev_internal_ops *internal_ops; //向V4L2框架提供的接口函数
这两个属性的结构体:
struct v4l2_subdev_ops {
const struct v4l2_subdev_core_ops *core; //视频设备通用的操作:初始化、加载FW、上电和RESET等
const struct v4l2_subdev_tuner_ops *tuner; //tuner特有的操作
const struct v4l2_subdev_audio_ops *audio; //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;
const struct v4l2_subdev_pad_ops *pad;
};
/**
* struct v4l2_subdev_internal_ops - V4L2 subdev internal ops
*
* @registered: called when this subdev is registered. When called the v4l2_dev
* field is set to the correct v4l2_device.
*
* @unregistered: called when this subdev is unregistered. When called the
* v4l2_dev field is still set to the correct v4l2_device.
*
* @open: called when the subdev device node is opened by an application.
*
* @close: called when the subdev device node is closed. Please note that
* it is possible for @close to be called after @unregistered!
*
* @release: called when the last user of the subdev device is gone. This
* happens after the @unregistered callback and when the last open
* filehandle to the v4l-subdevX device node was closed. If no device
* node was created for this sub-device, then the @release callback
* is called right after the @unregistered callback.
* The @release callback is typically used to free the memory containing
* the v4l2_subdev structure. It is almost certainly required for any
* sub-device that sets the V4L2_SUBDEV_FL_HAS_DEVNODE flag.
*
* … note::
* Never call this from drivers, only the v4l2 framework can call
* these ops.
*/
struct v4l2_subdev_internal_ops {
/* 当subdev注册时被调用,读取IC的ID来进行识别 */
int (*registered)(struct v4l2_subdev *sd);
void (*unregistered)(struct v4l2_subdev *sd);
/* 当设备节点被打开时调用,通常会给设备上电和设置视频捕捉FMT */
int (*open)(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh);
int (*close)(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh);
/* 释放对应的subdev */
void (*release)(struct v4l2_subdev *sd);
};
调用 video\_device 结构体进行设备的注册
// kernel_imx/include/media/v4l2-dev.h
struct video_device
{
#if defined(CONFIG_MEDIA_CONTROLLER)
struct media_entity entity;
struct media_intf_devnode *intf_devnode;
struct media_pipeline pipe;
#endif
const struct v4l2_file_operations *fops; // 设备描述符号
u32 device_caps;
/\* sysfs \*/
struct device dev; // v4l 设备
struct cdev \*cdev; // 字符设备
struct v4l2\_device \*v4l2_dev; // v4l2\_device 父设备
struct device \*dev_parent; // 父设备
struct v4l2\_ctrl\_handler \*ctrl_handler; // 设备节点的控制处理结构,可能为NULL
struct vb2\_queue \*queue;
struct v4l2\_prio\_state \*prio;
/\* device info \*/
char name[32];
enum vfl\_devnode\_type vfl_type;
enum vfl\_devnode\_direction vfl_dir;
int minor;
u16 num;
unsigned long flags;
int index;
/\* V4L2 file handles \*/
spinlock\_t fh_lock;
struct list\_head fh_list;
int dev_debug;
v4l2_std_id tvnorms;
/\* callbacks \*/
/\* ioctl回调函数集,提供file\_operations中的ioctl调用 \*/
void (\*release)(struct video\_device \*vdev); // 释放函数
const struct v4l2\_ioctl\_ops \*ioctl_ops;
DECLARE\_BITMAP(valid_ioctls, BASE_VIDIOC_PRIVATE);
struct mutex \*lock;
ANDROID\_KABI\_RESERVE(1);
ANDROID\_KABI\_RESERVE(2);
};
**v4l2\_fh**是用来保存子设备的特有操作方法,也就是下面要分析到的v4l2\_ctrl\_handler,内核提供一组v4l2\_fh的操作方法,通常在打开设备节点时进行v4l2\_fh注册。
// kernel_imx/include/media/v4l2-fh.h
/**
* struct v4l2_fh - Describes a V4L2 file handler
*
* @list: list of file handlers
* @vdev: pointer to &struct video_device
* @ctrl_handler: pointer to &struct v4l2_ctrl_handler
* @prio: priority of the file handler, as defined by &enum v4l2_priority
*
* @wait: event’ s wait queue
* @subscribe_lock: serialise changes to the subscribed list; guarantee that
* the add and del event callbacks are orderly called
* @subscribed: list of subscribed events
* @available: list of events waiting to be dequeued
* @navailable: number of available events at @available list
* @sequence: event sequence number
*
* @m2m_ctx: pointer to &struct v4l2_m2m_ctx
*/
struct v4l2_fh {
struct list_head list;
struct video_device *vdev;
struct v4l2_ctrl_handler *ctrl_handler;
enum v4l2_priority prio;
/\* Events \*/
wait\_queue\_head\_t wait;
struct mutex subscribe_lock;
struct list\_head subscribed;
struct list\_head available;
unsigned int navailable;
u32 sequence;
struct v4l2\_m2m\_ctx \*m2m_ctx;
};
/*这个头文件中还包含了如何去初始化子设备等、添加子设备、删除子设备*/
void v4l2_fh_init(struct v4l2_fh *fh, struct video_device *vdev);
/**
* v4l2_fh_add - Add the fh to the list of file handles on a video_device.
*
* @fh: pointer to &struct v4l2_fh
*
* … note::
* The @fh file handle must be initialised first.
*/
void v4l2_fh_add(struct v4l2_fh *fh);
/**
* v4l2_fh_open - Ancillary routine that can be used as the open() op
* of v4l2_file_operations.
*
* @filp: pointer to struct file
*
* It allocates a v4l2_fh and inits and adds it to the &struct video_device
* associated with the file pointer.
*/
int v4l2_fh_open(struct file *filp);
/**
* v4l2_fh_del - Remove file handle from the list of file handles.
*
* @fh: pointer to &struct v4l2_fh
*
* On error filp->private_data will be %NULL, otherwise it will point to
* the &struct v4l2_fh.
*
* … note::
**v4l2\_ctrl\_handler**是用于保存子设备控制方法集的结构体,对于视频设备这些ctrls包括设置亮度、饱和度、对比度和清晰度等,用链表的方式来保存ctrls,可以通过v4l2\_ctrl\_new\_std函数向链表添加ctrls。
其实下面的注释已经有对应的说明了
// kernel_imx/include/media/v4l2-ctrls.h
struct v4l2_ctrl *v4l2_ctrl_new_std(struct v4l2_ctrl_handler *hdl,
const struct v4l2_ctrl_ops *ops,
u32 id, s64 min, s64 max, u64 step,
s64 def);
/**
* v4l2_ctrl_new_std_menu() - Allocate and initialize a new standard V4L2
* menu control.
*
* @hdl: The control handler. 已经初始化的v4l2_ctrl_handler的结构体
* @ops: The control ops. 已经设置好的v4l2_ctrl_ops结构体
* @id: The control ID. 对应的ID通过;ioctl传递的参数,也就是序列号
* @max: The control’s maximum value. 操作范围的最大值和最小值
* @mask: The control’s skip mask for menu controls. This makes it
* easy to skip menu items that are not valid. If bit X is set,
* then menu item X is skipped. Of course, this only works for
* menus with <= 64 menu items. There are no menus that come
* close to that number, so this is OK. Should we ever need more,
* then this will have to be extended to a bit array.
* @def: The control’s default value.
*
* Same as v4l2_ctrl_new_std(), but @min is set to 0 and the @mask value
* determines which menu items are to be skipped.
*
* If @id refers to a non-menu control, then this function will return NULL.
*/
上面基本就是V4L2的框架图的一些源码解释,至于在代码中如何实现,还是需要根据代码去查看逻辑。主要是从注册设备去跟踪代码吧,因为工作中暂时没这些方面的需求,所以我这里也知识做一个简单的介绍。
## 四、V4l2的buf管理
因为我们可以通过v4l2的相关命令进行摄像头的数据流的抓取,在这过程中就涉及到一些文件的读写。
v4l2有三种:使用read/write方式;内存映射方式(mmap)和用户指针模式(USERPTR)。
**read和write** 是基本帧IO访问方式,通过read读取每一帧数据,数据需要在内核和用户之间拷贝,这种方式访问速度可能会非常慢;
**内存映射缓冲区 (V4L2\_MEMORY\_MMAP):** 是在内核空间开辟缓冲区,应用通过mmap()系统调用映射到用户地址空间。这些缓冲区可以是大而连续DMA缓冲区、通过vmalloc()创建的虚拟缓冲区,或者直接在设备的IO内存中开辟的缓冲区(如果硬件支持);
**用户空间缓冲区(V4L2\_MEMORY\_USERPTR)** 是用户空间的应用中开辟缓冲区,用户与内核空间之间交换缓冲区指针。很明显,在这种情况下是不需要mmap()调用的,但驱动为有效的支持用户空间缓冲区,其工作将也会更困难。
![在这里插入图片描述](https://img-blog.csdnimg.cn/720b45486f7042e8acf296919bf4a36a.png) Camera sensor捕捉到图像数据通过并口或MIPI传输到CAMIF(camera interface),CAMIF可以对图像数据进行调整(翻转、裁剪和格式转换等)。然后DMA控制器设置DMA通道请求AHB将图像数据传到分配好的DMA缓冲区。待图像数据传输到DMA缓冲区之后,mmap操作把缓冲区映射到用户空间,应用就可以直接访问缓冲区的数据。而为了使设备支持流IO这种方式,v4l2需要实现对video buffer的管理,即实现:
/* vb2_queue代表一个videobuffer队列,vb2_buffer是这个队列中的成员,vb2_mem_ops是缓冲内存的操作函数集,vb2_ops用来管理队列 */
struct vb2_queue {
enum v4l2_buf_type type; //buffer类型
unsigned int io_modes; //访问IO的方式:mmap、userptr etc
const struct vb2_ops *ops; //buffer队列操作函数集合
const struct vb2_mem_ops *mem_ops; //buffer memory操作集合
struct vb2_buffer *bufs[VIDEO_MAX_FRAME]; //代表每个frame buffer
unsignedint num_buffers; //分配的buffer个数
…
};
/* vb2_mem_ops包含了内存映射缓冲区、用户空间缓冲区的内存操作方法 */
struct vb2_mem_ops {
void *(*alloc)(void *alloc_ctx, unsignedlong size); //分配视频缓存
void (*put)(void *buf_priv); //释放视频缓存
/\* 获取用户空间视频缓冲区指针 \*/
void \*(\*get_userptr)(void \*alloc_ctx, unsigned long vaddr, unsignedlong size, int write);
void (\*put_userptr)(void \*buf_priv); //释放用户空间视频缓冲区指针
/\* 用于缓存同步 \*/
void (\*prepare)(void \*buf_priv);
void (\*finish)(void \*buf_priv);
/\* 缓存虚拟地址 & 物理地址 \*/
void \*(\*vaddr)(void \*buf_priv);
void \*(\*cookie)(void \*buf_priv);
unsignedint (\*num_users)(void \*buf_priv); //返回当期在用户空间的buffer数
int (\*mmap)(void \*buf_priv, structvm_area_struct \*vma); //把缓冲区映射到用户空间
..............
};
/* mem_ops由kernel自身实现并提供了三种类型的视频缓存区操作方法:连续的DMA缓冲区、集散的DMA缓冲区以及vmalloc创建的缓冲区,分别由videobuf2-dma-contig.c、videobuf2-dma-sg.c和videobuf-vmalloc.c文件实现,可以根据实际情况来使用。*/
/* vb2_ops是用来管理buffer队列的函数集合,包括队列和缓冲区初始化等 */
struct vb2_ops {
//队列初始化
int(*queue_setup)(struct vb2_queue *q, const struct v4l2_format *fmt,
unsigned int *num_buffers, unsigned int*num_planes,
unsigned int sizes[], void *alloc_ctxs[]);
//释放和获取设备操作锁
void(\*wait_prepare)(struct vb2\_queue \*q);
void(\*wait_finish)(struct vb2\_queue \*q);
//对buffer的操作
int(\*buf_init)(struct vb2\_buffer \*vb);
int(\*buf_prepare)(struct vb2\_buffer \*vb);
int(\*buf_finish)(struct vb2\_buffer \*vb);
void(\*buf_cleanup)(struct vb2\_buffer \*vb);
//开始/停止视频流
int(\*start_streaming)(struct vb2\_queue \*q, unsigned int count);
int(\*stop_streaming)(struct vb2\_queue \*q);
//把VB传递给驱动,以填充frame数据
void(\*buf_queue)(struct vb2\_buffer \*vb);
};
//kernel_imx/drivers/media/v4l2-core/v4l2-compat-ioctl32.c
struct v4l2_buffer32 {
__u32 index; // buffer的序号
__u32 type; /* enum v4l2_buf_type 类型*/
__u32 bytesused; // 已经使用的byte数
__u32 flags;
__u32 field; /* enum v4l2_field */
struct {
compat_s64 tv_sec;
compat_s64 tv_usec;
} timestamp; // 时间戳,代表帧捕获的时间
struct v4l2_timecode timecode;
__u32 sequence;
/\* memory location \*/
__u32 memory; /\* enum v4l2\_memory \*/
union {
__u32 offset;
compat\_long\_t userptr;
compat\_caddr\_t planes;
__s32 fd;
} m;
__u32 length; // 缓冲区大小,单位byte
__u32 reserved2;
__s32 request_fd;
};
## 五、V4L2的整体流程
因为我这里自己跟踪流程的时候我已经跟蒙了,所以这里我就后续学会了再补充,这里我是根据设备注册开始跟踪的
// kernel_imx/drivers/media/v4l2-core/v4l2-dev.c
int __video_register_device(struct video_device *vdev,
enum vfl_devnode_type type,
int nr, int warn_if_nr_in_use,
struct module *owner)
{
int i = 0;
int ret;
int minor_offset = 0;
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;
/\* the release callback MUST be present \*/
if (WARN\_ON(!vdev->release))
return -EINVAL;
/\* the v4l2\_dev pointer MUST be present \*/
if (WARN\_ON(!vdev->v4l2_dev))
return -EINVAL;
/\* the device\_caps field MUST be set for all but subdevs \*/
if (WARN\_ON(type != VFL_TYPE_SUBDEV && !vdev->device_caps))
return -EINVAL;
/\* v4l2\_fh support \*/
spin\_lock\_init(&vdev->fh_lock);
INIT\_LIST\_HEAD(&vdev->fh_list);
/\* Part 1: check device type \*/
switch (type) {
case VFL_TYPE_VIDEO:
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:
/\* Use device name 'swradio' because 'sdr' was already taken. \*/
name_base = "swradio";
break;
case VFL_TYPE_TOUCH:
name_base = "v4l-touch";
break;
default:
pr\_err("%s called with unknown type: %d\n",
\_\_func\_\_, type);
return -EINVAL;
}
vdev->vfl_type = type;
vdev->cdev = NULL;
if (vdev->dev_parent == NULL)
vdev->dev_parent = vdev->v4l2_dev->dev;
if (vdev->ctrl_handler == NULL)
vdev->ctrl_handler = vdev->v4l2_dev->ctrl_handler;
/\* If the prio state pointer is NULL, then use the v4l2\_device
prio state. */
if (vdev->prio == NULL)
vdev->prio = &vdev->v4l2_dev->prio;
/\* Part 2: find a free minor, device node number and device index. \*/
#ifdef CONFIG_VIDEO_FIXED_MINOR_RANGES
/* 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_VIDEO:
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;
}
#endif
/\* 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) {
pr\_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, so
we just find the first free minor number. */
for (i = 0; i < VIDEO_NUM_DEVICES; i++)
if (video_devices[i] == NULL)
break;
if (i == VIDEO_NUM_DEVICES) {
mutex_unlock(&videodev_lock);
pr_err(“could not get a free minor\n”);
return -ENFILE;
}
#endif