系列文章:
Linux V4L2驱动框架分析之(一):架构介绍
Linux V4L2驱动框架分析之(二):平台v4l2设备驱动
Linux V4L2驱动框架分析之(三):v4l2设备的缓存管理
Linux V4L2驱动框架分析之(四):sensor驱动
v4l2设备读取数据的方式有两种,一种是read方式,一种是streaming方式。read方式很容易理解,就是通过read函数读取,而streaming方式是在内核空间中维护一个缓存队列,然后将内存映射到用户空间,应用读取图像数据就是一个不断地出队列和入队列的过程,如下图所示:
使用streaming方式,需要管理多块缓冲,内核通过vb2_queue来管理,vb2_queue即缓冲队列。
应用程序查询设备功能,判断设备是否支持streaming方式:
if (ioctl(fd, VIDIOC_QUERYCAP, cap) < 0)
{
printf("ERR(%s):VIDIOC_QUERYCAP failed\n", __func__);
return -1;
}
传入的cap参数是一个truct v4l2_capability结构体的指针,该结构体的定义如下:
/* include/uapi/linux/videodev2.h */
struct v4l2_capability {
__u8 driver[16];
__u8 card[32];
__u8 bus_info[32];
__u32 version;
__u32 capabilities;
__u32 device_caps;
__u32 reserved[3];
};
其中最重要的是capabilities字段,这个字段标记着v4l2设备的功能,capabilities有以下部分标记位:
struct video_device结构体有一成员queue:
struct video_device
{
......
struct vb2_queue *queue;
......
};
采用streaming方式时,平台v4l2设备驱动需要设置与初始化该成员。
struct vb2_queue结构体定义如下:
struct vb2_queue {
......
const struct vb2_ops *ops;
const struct vb2_mem_ops *mem_ops;
const struct vb2_buf_ops *buf_ops;
......
struct vb2_buffer *bufs[VB2_MAX_FRAME];
unsigned int num_buffers;
struct list_head queued_list;
unsigned int queued_count;
struct list_head done_list;
wait_queue_head_t done_wq;
......
};
一般在v4l2_file_operations的open回调里设置并调用vb2_queue_init函数初始化vb2_queue,最关健的要设置vb2_queue的ops、mem_ops成员。
vb2_queue的mem_ops成员里有申请、释放和映射缓存的等回调函数,数据类型为struct vb2_mem_ops,定义如下:
struct vb2_mem_ops {
//申请缓存
void *(*alloc)(struct device *dev, unsigned long attrs,
unsigned long size,
enum dma_data_direction dma_dir,
gfp_t gfp_flags);
void (*put)(void *buf_priv);
struct dma_buf *(*get_dmabuf)(void *buf_priv, unsigned long flags);
void *(*get_userptr)(struct device *dev, unsigned long vaddr,
unsigned long size,
enum dma_data_direction dma_dir);
void (*put_userptr)(void *buf_priv);
void (*prepare)(void *buf_priv);
void (*finish)(void *buf_priv);
void *(*attach_dmabuf)(struct device *dev,
struct dma_buf *dbuf,
unsigned long size,
enum dma_data_direction dma_dir);
void (*detach_dmabuf)(void *buf_priv);
int (*map_dmabuf)(void *buf_priv);
void (*unmap_dmabuf)(void *buf_priv);
void *(*vaddr)(void *buf_priv);
void *(*cookie)(void *buf_priv);
unsigned int (*num_users)(void *buf_priv);
//映射缓存
int (*mmap)(void *buf_priv, struct vm_area_struct *vma);
};
vb2_queue支持这三种形式缓存,内核提供了三种vb2_mem_ops操作函数集:
/* drivers/media/v4l2-core/videobuf2-vmalloc.c
分配的缓冲区,虚拟地址连续,物理地址不一定连续
*/
const struct vb2_mem_ops vb2_vmalloc_memops = {
.alloc = vb2_vmalloc_alloc,
.put = vb2_vmalloc_put,
.get_userptr = vb2_vmalloc_get_userptr,
.put_userptr = vb2_vmalloc_put_userptr,
#ifdef CONFIG_HAS_DMA
.get_dmabuf = vb2_vmalloc_get_dmabuf,
#endif
.map_dmabuf = vb2_vmalloc_map_dmabuf,
.unmap_dmabuf = vb2_vmalloc_unmap_dmabuf,
.attach_dmabuf = vb2_vmalloc_attach_dmabuf,
.detach_dmabuf = vb2_vmalloc_detach_dmabuf,
.vaddr = vb2_vmalloc_vaddr,
.mmap = vb2_vmalloc_mmap,
.num_users = vb2_vmalloc_num_users,
};
/* drivers/media/v4l2-core/videobuf2-dma-sg.c
对于支持Scatter/Gather的DMA,可使用vb2_dma_sg_memops
*/
const struct vb2_mem_ops vb2_dma_sg_memops = {
.alloc = vb2_dma_sg_alloc,
.put = vb2_dma_sg_put,
.get_userptr = vb2_dma_sg_get_userptr,
.put_userptr = vb2_dma_sg_put_userptr,
.prepare = vb2_dma_sg_prepare,
.finish = vb2_dma_sg_finish,
.vaddr = vb2_dma_sg_vaddr,
.mmap = vb2_dma_sg_mmap,
.num_users = vb2_dma_sg_num_users,
.get_dmabuf = vb2_dma_sg_get_dmabuf,
.map_dmabuf = vb2_dma_sg_map_dmabuf,
.unmap_dmabuf = vb2_dma_sg_unmap_dmabuf,
.attach_dmabuf = vb2_dma_sg_attach_dmabuf,
.detach_dmabuf = vb2_dma_sg_detach_dmabuf,
.cookie = vb2_dma_sg_cookie,
};
/* drivers/media/v4l2-core/videobuf2-dma-contig.c */
const struct vb2_mem_ops vb2_dma_contig_memops = {
.alloc = vb2_dc_alloc,
.put = vb2_dc_put,
.get_dmabuf = vb2_dc_get_dmabuf,
.cookie = vb2_dc_cookie,
.vaddr = vb2_dc_vaddr,
.mmap = vb2_dc_mmap,
.get_userptr = vb2_dc_get_userptr,
.put_userptr = vb2_dc_put_userptr,
.prepare = vb2_dc_prepare,
.finish = vb2_dc_finish,
.map_dmabuf = vb2_dc_map_dmabuf,
.unmap_dmabuf = vb2_dc_unmap_dmabuf,
.attach_dmabuf = vb2_dc_attach_dmabuf,
.detach_dmabuf = vb2_dc_detach_dmabuf,
.num_users = vb2_dc_num_users,
};
根据具体设备使用缓存区的方式,选择对应的vb2_mem_ops。
应用程序申请缓存:
struct v4l2_requestbuffers req;
req.count = nr_bufs; //缓存数量
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0)
{
printf("ERR(%s):VIDIOC_REQBUFS failed\n", __func__);
return -1;
}
struct v4l2_buffer v4l2_buffer;
for(i = 0; i < nr_bufs; i++)
{
memset(&v4l2_buffer, 0, sizeof(struct v4l2_buffer));
v4l2_buffer.index = i; //想要放入队列的缓存
v4l2_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4l2_buffer.memory = V4L2_MEMORY_MMAP;
ret = ioctl(fd, VIDIOC_QBUF, &v4l2_buffer);
if(ret < 0)
{
printf("Unable to queue buffer.\n");
return -1;
}
}
到内核态的执行流程:
把缓冲放入队列之后就是映射缓存了。因为如果使用read方式读取的话,图像数据是从内核空间拷贝会应用空间,而一副图像的数据一般来讲是比较大的,所以效率会比较低。而如果使用映射的方式,讲内核空间的内存应用到用户空间,那么用户空间读取数据就想在操作内存一样,不需要经过内核空间到用户空间的拷贝,大大提高效率。
struct v4l2_buffer v4l2_buffer;
void* addr;
memset(&v4l2_buffer, 0, sizeof(struct v4l2_buffer));
v4l2_buffer.index = i; //想要查询的缓存
v4l2_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4l2_buffer.memory = V4L2_MEMORY_MMAP;
/* 查询缓存信息 */
ret = ioctl(fd, VIDIOC_QUERYBUF, &v4l2_buffer);
if(ret < 0)
{
printf("Unable to query buffer.\n");
return -1;
}
/* 映射 */
addr = mmap(NULL /* start anywhere */ ,
v4l2_buffer.length, PROT_READ | PROT_WRITE, MAP_SHARED,
fd, v4l2_buffer.m.offset);
获取图像数据其实就是一个不断地入队列和出队列地过程,在出队列前要调用poll等待数据准备完成,应用程序poll操作到内核态:
具体的驱动程序在获得到图像数据后,需要从struct vb2_buffer的queued_list链表得到一个struct vb2_buffer,把数据填入vb2_buffer,之后将该vb2_buffer挂入到struct vb2_buffer的done_list链表,最终唤醒进程。
数据准备完成,进缓存出队:
如果是映射缓存,出队后,应用程序可直接操作缓存里的图像数据,处理完数据之后再次把缓存放入队列。