linux V4L2子系统——v4l2架构(6)之videobuf2
备注:
1. Kernel版本:5.4
2. 使用工具:Source Insight 4.0
3. 参考博客:
(1)Linux V4L2子系统-videobuf2架构分析(三)
(2)V4L2架构-videobuf2
文章目录
概述
Video设备产生的数据较多,传统的缓冲机制已不能满足需求。为此,Linux内核抽象出了videobuf2机制,用于管理存放视频图像的帧缓冲。videobuf2抽象层像一座桥梁,将用户空间和V4L2 driver连接起来。videobuf2抽象层向用户空间提供了标准POSIX I/O系统调用,包括read、poll及mmap等,同时还提供了大量与流式I/O相关的V4L2 ioctl调用,包括缓冲区分配、缓冲区入队、缓冲区出队及流控制。虽然使用videobuf2会给驱动程序强加一些设计决策,但是使用它的收益是videobuf2可以减少驱动程序代码和保持V4L2子系统在用户空间API的一致性,显然使用videobuf2更为合理。
为什么要有videobuf2?
-
不完善的以及错误的内存管理
a. 不能停止streaming
(在streamoff
的时候,buffer
被释放,从而无法灵活地再次开启stream
);
b.VIDIOC_REQBUFS
(0) 不会释放内存,这个主要就是使用上的方便与否;
c. 不能够使用VIDIOC_REQBUFS
重复申请内存,道理同上;
d.video
内存在mmap
、qbuf
甚至page fault
中分配;在unmap
、streamoff
或者驱动自定义的地方被释放,也就是说不够标准化、统一化;
e. 每一个buffer都有一个等待队列,太过繁琐,会导致 buffer 轮转的时间较长; -
扩展性不足,尤其是嵌入式多媒体设备
a. 难以添加新的内存处理函数以及自定义的内存分配函数,比如现在 videobuf2 里面已有的三种-「dma-contig
、sg-dma
、vmalloc
」;
b. 对 cache 以及 IOMMU 的支持非常弱;
c. 不够灵活。只有一个通用的函数来处理 cache、sg-list 创建等等事情,这些应该分为不同的抽象方法类进行差异化管理; -
无用的成员,代码,变量
分类
不是所有的Video设备都使用同一种类型的videobuf2。实时上,Linux内核中有3中不同类型的videobuf2。
#path:drivers/media/common/videobuf2
├── vb2-trace.c
├── videobuf2-core.c
├── videobuf2-dma-contig.c
├── videobuf2-dma-sg.c
├── videobuf2-dvb.c
├── videobuf2-memops.c
├── videobuf2-v4l2.c
└── videobuf2-vmalloc.c
#path:drivers/media/v4l2-core
├── videobuf-core.c
├── videobuf-dma-contig.c
├── videobuf-dma-sg.c
└── videobuf-vmalloc.c
(1)缓冲区物理地址和虚拟地址不连续。
大多数用户空间缓冲区就属于这种情况,在可能的情况下,内核空间以这种方式分配缓冲区也是有意义的。然而在有些情况下,则不适用。对于不连续的缓冲区,需要硬件上支持scatter/gather DMA operations。使用该缓冲区,需要包含头文件 <include/media/videobuf-dma-sg.h>
(适用于V4L)或 <include/media/videobuf2-dma-sg.h>
(适用于V4L2)。
(2)缓冲区物理地址不连续但虚拟地址连续。
缓冲区分配使用vmalloc。这种缓冲区无法使用DMA进行传输。但在不使用DMA的情形下,虚拟地址连续的缓冲器很便利。使用该缓冲区,需要包含头文件 <include/media/videobuf-vmalloc.h>
(适用于V4L)或 <include/media/videobuf2-vmalloc.h>
(适用于V4L2)。
(3)缓冲区物理地址和虚拟地址都连续。
在页式内存管理系统中,分配物理地址和虚拟地址都连续的缓冲区是不可靠的,因为这种分配方式容易造成更多的内存碎片,某些情况下内存碎片过多会造成内存分配失败,从而导致系统无法正常功能工作。但是对于DMA传输来说却很方便。使用该缓冲区,需要包含头文件 <include/media/videobuf-dma-contig.h>
(适用于V4L)或 <include/media/videobuf2-dma-contig.h>
(适用于V4L2)。这中类型的videobuf2比较常用。
除此之外,还存在一种overlay缓冲区,其位于系统的显存中。目前overlay缓冲区已被弃用,但在一些片上系统的驱动中偶尔还能看到。
至于使用哪一种videobuf2,需要驱动工程师根据实际的使用环境进行评估。
数据结构
详见:linux V4L2子系统——v4l2的结构体(5)之videobuf2(vb2)
vb2_queue初始化
以sun6i-csi为例,示例说明如下:
static const struct vb2_ops sun6i_csi_vb2_ops = {
.queue_setup = sun6i_video_queue_setup,
.wait_prepare = vb2_ops_wait_prepare,
.wait_finish = vb2_ops_wait_finish,
.buf_prepare = sun6i_video_buffer_prepare,
.start_streaming = sun6i_video_start_streaming,
.stop_streaming = sun6i_video_stop_streaming,
.buf_queue = sun6i_video_buffer_queue,
};
int sun6i_video_init(struct sun6i_video *video, struct sun6i_csi *csi,
const char *name)
{
......
/* Initialize videobuf2 queue */
vidq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
vidq->io_modes = VB2_MMAP | VB2_DMABUF;
vidq->drv_priv = video;
vidq->buf_struct_size = sizeof(struct sun6i_csi_buffer);
vidq->ops = &sun6i_csi_vb2_ops;
vidq->mem_ops = &vb2_dma_contig_memops;
vidq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
vidq->lock = &video->lock;
/* Make sure non-dropped frame */
vidq->min_buffers_needed = 3;
vidq->dev = csi->dev;
ret = vb2_queue_init(vidq);
if (ret) {
v4l2_err(&csi->v4l2_dev, "vb2_queue_init failed: %d\n", ret);
goto clean_entity;
}
......
}
//源码: drivers/media/common/videobuf2/videobuf2-v4l2.c
int vb2_queue_init(struct vb2_queue *q)
{
/*
* Sanity check
*/
if (WARN_ON(!q) ||
WARN_ON(q->timestamp_flags &
~(V4L2_BUF_FLAG_TIMESTAMP_MASK |
V4L2_BUF_FLAG_TSTAMP_SRC_MASK)))
return -EINVAL;
/* Warn that the driver should choose an appropriate timestamp type */
// 驱动必须选择合适的时间戳,否则内核会发出警告
WARN_ON((q->timestamp_flags & V4L2_BUF_FLAG_TIMESTAMP_MASK) ==
V4L2_BUF_FLAG_TIMESTAMP_UNKNOWN);
/* Warn that vb2_memory should match with v4l2_memory */
// 检查 vb2_memory 与 v4l2_memory 的枚举变量值是否一致
if (WARN_ON(VB2_MEMORY_MMAP != (int)V4L2_MEMORY_MMAP)
|| WARN_ON(VB2_MEMORY_USERPTR != (int)V4L2_MEMORY_USERPTR)
|| WARN_ON(VB2_MEMORY_DMABUF != (int)V4L2_MEMORY_DMABUF))
return -EINVAL;
if (q->buf_struct_size == 0)
q->buf_struct_size = sizeof(struct vb2_v4l2_buffer);
q->buf_ops = &v4l2_buf_ops;
q->is_multiplanar = V4L2_TYPE_IS_MULTIPLANAR(q->type);
q->is_output = V4L2_TYPE_IS_OUTPUT(q->type);
q->copy_timestamp = (q->timestamp_flags & V4L2_BUF_FLAG_TIMESTAMP_MASK)
== V4L2_BUF_FLAG_TIMESTAMP_COPY;
/*
* For compatibility with vb1: if QBUF hasn't been called yet, then
* return EPOLLERR as well. This only affects capture queues, output
* queues will always initialize waiting_for_buffers to false.
*/
q->quirk_poll_must_check_waiting_for_buffers = true;
return vb2_core_queue_init(q);
}
EXPORT_SYMBOL_GPL(vb2_queue_init);
//源码: drivers/media/common/videobuf2/videobuf2-core.c
int vb2_core_queue_init(struct vb2_queue *q)
{
/*
* Sanity check
*/
// 必须设置下面的成员,否则返回-EINVAL的错误
if (WARN_ON(!q) ||
WARN_ON(!q->ops) ||
WARN_ON(!q->mem_ops) ||
WARN_ON(!q->type) ||
WARN_ON(!q->io_modes) ||
WARN_ON(!q->ops->queue_setup) ||
WARN_ON(!q->ops->buf_queue))
return -EINVAL;
if (WARN_ON(q->requires_requests && !q->supports_requests))
return -EINVAL;
INIT_LIST_HEAD(&q->queued_list); // 初始化queued_list链表节点
INIT_LIST_HEAD(&q->done_list); // 初始化done_list链表节点
spin_lock_init(&q->done_lock); // 初始化自旋锁
mutex_init(&q->mmap_lock); // 初始化互斥锁
init_waitqueue_head(&q->done_wq); // 初始化等待队列头
q->memory = VB2_MEMORY_UNKNOWN;
// 若缓冲区大小驱动没有设置,则内核使用默认值初始化
if (q->buf_struct_size == 0)
q->buf_struct_size = sizeof(struct vb2_buffer);
if (q->bidirectional)
q->dma_dir = DMA_BIDIRECTIONAL;
else
q->dma_dir = q->is_output ? DMA_TO_DEVICE : DMA_FROM_DEVICE;
return 0;
}
EXPORT_SYMBOL_GPL(vb2_core_queue_init);
使用方法探析
videobuf2的使用方法复杂,需要结合具体的驱动实例进行说明,这样比较好理解。应用可以通过调用 open
、close
、ioctl
、mmap
、read
系统调用访问Video设备,内核根据不同的系统调用采用相对应的方法访问 videobuf2。下面从这些系统调用入手,分析内核中videobuf2的使用方法。
open/close
应用调用 open/close 打开/关闭Video设备,获取设备的描述符。内核中首先调用 v4l2_open / v4l2_release,然后调用驱动提供的 sun6i_video_open / sun6i_video_close 函数。
ioctl
V4L2子系统定义了很多ioctl命令供应用程序使用。
VIDIOC_REQBUFS
命令用于向内核申请缓冲区VIDIOC_QUERYBUF
命令用于获取缓冲区信息VIDIOC_QBUF
命令将读取完数据的空缓存返还给驱动的缓存队列VIDIOC_DQBUF
命令将填充满数据的缓存从驱动中返回给应用VIDIOC_STREAMOFF
命令用于关闭流,即停止图像采集VIDIOC_STREAMON
命令用于开启流,即开启图像采集。
内核中的调用流程为 v4l2_ioctl
-> video_ioctl2
-> __video_do_ioctl
->根据不同的命令调用不同的驱动函数->调用对应的videobuf2处理函数,具体调用流程参考上图。
下面具体分析一下ioctl调用的videobuf2处理函数。
VIDIOC_REQBUFS
使用 VIDIOC_REQBUFS
命令调用ioctl,最终会调用到 vb2_reqbufs
函数,内核使用 vb2_reqbufs
函数创建缓冲区。调用流程如下:
v4l_reqbufs
|--->vb2_ioctl_reqbufs(sun6i_video_ioctl_ops.vidioc_reqbufs)
|--->vb2_core_reqbufs
|--->sun6i_video_queue_setup(sun6i_csi_vb2_ops.queue_setup)
|--->__vb2_queue_alloc
|--->__init_vb2_v4l2_buffer(v4l2_buf_ops.init_buffer)
|--->__vb2_buf_mem_alloc
|--->vb2_dc_alloc(vb2queue->mem_ops->alloc, vb2_dma_contig_memops.alloc)
(1)验证缓冲区的 memory type
和 buffer type
是否正确。
__verify_memory_type
函数用于校验缓冲区类型和缓冲区内存类型。vb2_queue
中的缓冲区类型 type
和 v4l2_requestbuffers
中的缓冲区类型 type 需一致。缓冲区内存类型必须是 V4L2_MEMORY_MMAP
、 V4L2_MEMORY_USERPTR
、 V4L2_MEMORY_DMABUF
其中之一。
若是 V4L2_MEMORY_MMAP
类型,则 q->io_modes
必须设置为 VB2_MMAP
,mem_ops->alloc
、q->mem_ops->put
和 q->mem_ops->mmap
的函数指针也必须设置。
若是 V4L2_MEMORY_USERPTR
类型,则 q->io_modes
必须设置为 VB2_USERPTR
,mem_ops->get_userptr
和 q->mem_ops->put_userptr
的函数指针也必须设置。
若是 V4L2_MEMORY_DMABUF
类型,则 q->io_modes
必须设置为 VB2_DMABUF
,mem_ops->attach_dmabuf
、q->mem_ops->detach_dmabuf
、q->mem_ops->map_dmabuf
和 q->mem_ops->unmap_dmabuf
的函数指针也必须设置。
(2)判断缓冲区参数是否正确,若不正确,则需要做一些处理。
申请的 缓冲区数量为0 或 缓冲区队列中缓冲区数量不为0 或 申请的缓冲区内存类型 和 缓冲区队列中缓冲区内存类型不一致 ,则进入额外的处理逻辑。若内存类型为 V4L2_MEMORY_MMAP
且缓冲区正在使用,则直接返回错误。清理处于 PREPARED
或 QUEUED
状态的缓冲区并释放缓冲区内存。
(3)计算需要分配的缓冲区数量。
(4)调用驱动实现的函数 queue_setup
,驱动函数需要设置 num_buffers
、num_buffers
、q->plane_sizes
和 q->alloc_ctx
。
(5)调用 __vb2_queue_alloc
分配缓冲区内存。此时缓冲区的状态为 VB2_BUF_STATE_DEQUEUED
。
(6)若分配的缓冲区数量小于需要分配的数量,需要再次调用驱动提供的 queue_setup
函数。
(7)设置分配的缓冲区数量并向应用返回分配的缓冲区数量。
vb2_ioctl_reqbufs() 函数
// 源码: drivers/media/common/videobuf2/videobuf2-v4l2.c
int vb2_ioctl_reqbufs(struct file *file, void *priv,
struct v4l2_requestbuffers *p)
{
struct video_device *vdev = video_devdata(file);
int res = vb2_verify_memory_type(vdev->queue, p->memory, p->type);
// 根据 io_mode 填充 capabilities
fill_buf_caps(vdev->queue, &p->capabilities);
if (res)
return res;
if (vb2_queue_is_busy(vdev, file))
return -EBUSY;
// 调用核心函数进行处理
res = vb2_core_reqbufs(vdev->queue, p->memory, &p->count);
/* If count == 0, then the owner has released all buffers and he
is no longer owner of the queue. Otherwise we have a new owner. */
if (res == 0)
vdev->queue->owner = p->count ? file->private_data : NULL;
return res;
}
EXPORT_SYMBOL_GPL(vb2_ioctl_reqbufs);
vb2_core_reqbufs() 函数
// 源码: drivers/media/common/videobuf2/videobuf2-core.c
int vb2_core_reqbufs(struct vb2_queue *q, enum vb2_memory memory,
unsigned int *count)
{
unsigned int num_buffers, allocated_buffers, num_planes = 0;
unsigned plane_sizes[VB2_MAX_PLANES] = { };
unsigned int i;
int ret;
// 已开启数据流,则不能再次申请buf
if (q->streaming) {
dprintk(1, "streaming active\n");
return -EBUSY;
}
if (q->waiting_in_dqbuf && *count) {
dprintk(1, "another dup()ped fd is waiting for a buffer\n");
return -EBUSY;
}
// 申请的缓冲区数量为0 或
// 缓冲区队列中缓冲区数量不为0 或
// 申请的缓冲区内存类型 和 缓冲区队列中缓冲区内存类型不一致,
// 则需要则额外的处理
if (*count == 0 || q->num_buffers != 0 ||
(q->memory != VB2_MEMORY_UNKNOWN && q->memory != memory)) {
/*
* We already have buffers allocated, so first check if they
* are not in use and can be freed.
*/
mutex_lock(&q->mmap_lock);
if (debug && q->memory == VB2_MEMORY_MMAP &&
__buffers_in_use(q))
dprintk(1, "memory in use, orphaning buffers\n");
/*
* Call queue_cancel to clean up any buffers in the
* QUEUED state which is possible if buffers were prepared or
* queued without ever calling STREAMON.
*/
// 若缓冲区处于PREPARED或QUEUED状态,则需要清理,
// 一般在申请了缓冲区,但没调用STREAMON,会出现这种情况
__vb2_queue_cancel(q);
// 释放已经分配内存的缓冲区
ret = __vb2_queue_free(q, q->num_buffers);
mutex_unlock(&q->mmap_lock);
if (ret)
return ret;
/*
* In case of REQBUFS(0) return immediately without calling
* driver's queue_setup() callback and allocating resources.
*/
if (*count == 0)
return 0;
}
/*
* Make sure the requested values and current defaults are sane.
*/
WARN_ON(q->min_buffers_needed > VB2_MAX_FRAME);
// 缓冲区数量取num_buffers和需要最少的缓冲区数量的较大值
num_buffers = max_t(unsigned int, *count, q->min_buffers_needed);
// 缓冲区数量取应用申请的数量和最大数量中的较小值,VIDEO_MAX_FRAME定义为32
num_buffers = min_t(unsigned int, num_buffers, VB2_MAX_FRAME);
memset(q->alloc_devs, 0, sizeof(q->alloc_devs));
// 设置缓冲区队列中缓冲区的内存模型,内存模型应该和应用申请的模型保持一致
q->memory = memory;
/*
* Ask the driver how many buffers and planes per buffer it requires.
* Driver also sets the size and allocator context for each plane.
*/
// 回调用驱动提供的queue_setup函数,sun6i-csi 提供的函数为 sun6i_video_queue_setup
// 驱动函数需要设置num_buffers、num_buffers、q->plane_sizes和q->alloc_ctx
ret = call_qop(q, queue_setup, q, &num_buffers, &num_planes,
plane_sizes, q->alloc_devs);
if (ret)
return ret;
/* Check that driver has set sane values */
if (WARN_ON(!num_planes))
return -EINVAL;
for (i = 0; i < num_planes; i++)
if (WARN_ON(!plane_sizes[i]))
return -EINVAL;
/* Finally, allocate buffers and video memory */
allocated_buffers =
__vb2_queue_alloc(q, memory, num_buffers, num_planes, plane_sizes);
if (allocated_buffers == 0) {
dprintk(1, "memory allocation failed\n");
return -ENOMEM;
}
......
mutex_lock(&q->mmap_lock);
q->num_buffers = allocated_buffers;
......
return 0;
}
queue_setup() 函数
queue_setup函数
需要驱动提供。sun6i-csi 提供的函数为 sun6i_video_queue_setup
,主要的作用是设置 __reqbufs函数
中的缓冲区数量 num_buffers
、plane的数量 num_planes
、plane的大小 q->plane_sizes
。上述设置的变量都是调用函数以指针的形式传入。
//sun6i-csi平台需由驱动自行实现,实为 sun6i_video_queue_setup
static int sun6i_video_queue_setup(struct vb2_queue *vq,
unsigned int *nbuffers,
unsigned int *nplanes,
unsigned int sizes[],
struct device *alloc_devs[])
{
struct sun6i_video *video = vb2_get_drv_priv(vq);
unsigned int size = video->fmt.fmt.pix.sizeimage;
if (*nplanes)
return sizes[0] < size ? -EINVAL : 0;
*nplanes = 1;
sizes[0] = size;
return 0;
}
__vb2_queue_alloc() 函数
缓冲区分配主要由 __vb2_queue_alloc
函数实现。分配 num_buffers
个缓冲区,即分配 num_buffers
个 struct vb2_buffer
结构体。所有的缓冲区内存地址都保存到 vb2_queue
结构体中的bufs数组中。
若是缓冲区内存是 V4L2_MEMORY_MMAP
类型,则还需要额外分配保存图像的缓冲区,一个缓冲区分配 num_planes
个保存图像的缓冲区。此缓冲区由 vb2_dc_alloc
分配。分配完后缓冲器的结构示意如下图所示。
V4L2_MEMORY_MMAP
类型的缓冲区需要分配额外的内存空间用于存储图像数据,如图中绿框所属,首选分配一个管理的结构体 struct vb2_dc_buf
,再分配真正存储图像数据的缓冲区,存储图像的缓冲区物理地址和虚拟地址一致,其虚拟地址保存到管理结构体的 vaddr
成员中,虚拟地址保存到管理结构体的 dma_addr
成员中,缓冲区大小保存到管理结构体的 size成员
中。
// 源码: drivers/media/common/videobuf2/videobuf2-core.c
/*
* __vb2_queue_alloc() - allocate videobuf buffer structures and (for MMAP type)
* video buffer memory for all buffers/planes on the queue and initializes the
* queue
*
* Returns the number of buffers successfully allocated.
*/
static int __vb2_queue_alloc(struct vb2_queue *q, enum vb2_memory memory,
unsigned int num_buffers, unsigned int num_planes,
const unsigned plane_sizes[VB2_MAX_PLANES])
{
unsigned int buffer, plane;
struct vb2_buffer *vb;
int ret;
/* Ensure that q->num_buffers+num_buffers is below VB2_MAX_FRAME */
num_buffers = min_t(unsigned int, num_buffers,
VB2_MAX_FRAME - q->num_buffers);
for (buffer = 0; buffer < num_buffers; ++buffer) {
/* Allocate videobuf buffer structures */
// 申请 vb2_buffer 内存,在初始化vb2_queue时会配置 buf_struct_size 大小
// 注意:驱动程序一般会有个大结构体包含此结构体
vb = kzalloc(q->buf_struct_size, GFP_KERNEL);
if (!vb) {
dprintk(1, "memory alloc for buffer struct failed\n");
break;
}
// 初始化 vb2_buffer
vb->state = VB2_BUF_STATE_DEQUEUED;
vb->vb2_queue = q;
vb->num_planes = num_planes;
vb->index = q->num_buffers + buffer;
vb->type = q->type;
vb->memory = memory;
for (plane = 0; plane < num_planes; ++plane) {
vb->planes[plane].length = plane_sizes[plane];
vb->planes[plane].min_length = plane_sizes[plane];
}
// 回调 vb2_queue 中 buf_ops 的 init_buffer 函数
// 此处实为:v4l2_buf_ops 中的 __init_vb2_v4l2_buffer
call_void_bufop(q, init_buffer, vb);
// 将新申请的 vb2_buffer 添加到 vb2_queue bufs 中
q->bufs[vb->index] = vb;
/* Allocate video buffer memory for the MMAP type */
// 对于V4L2_MEMORY_MMAP类型,则还需要分配额外的内存用于保存图像数据
// 然后映射到用户空间,用户可以直接读取额外内存中的数据
if (memory == VB2_MEMORY_MMAP) {
ret = __vb2_buf_mem_alloc(vb);
if (ret) {
dprintk(1, "failed allocating memory for buffer %d\n",
buffer);
q->bufs[vb->index] = NULL;
kfree(vb);
break;
}
__setup_offsets(vb);
/*
* Call the driver-provided buffer initialization
* callback, if given. An error in initialization
* results in queue setup failure.
*/
// 回调 vb2_queue 中 vb2_ops 的 buf_init 函数初始化内存,
// 此函数一般在驱动实现,也可不实现
// sun6i-csi 没有实现
ret = call_vb_qop(vb, buf_init, vb);
if (ret) {
dprintk(1, "buffer %d %p initialization failed\n",
buffer, vb);
__vb2_buf_mem_free(vb);
q->bufs[vb->index] = NULL;
kfree(vb);
break;
}
}
}
dprintk(1, "allocated %d buffers, %d plane(s) each\n",
buffer, num_planes);
return buffer;
}
__vb2_buf_mem_alloc() 函数
// 源码: drivers/media/common/videobuf2/videobuf2-core.c
/*
* __vb2_buf_mem_alloc() - allocate video memory for the given buffer
*/
static int __vb2_buf_mem_alloc(struct vb2_buffer *vb)
{
struct vb2_queue *q = vb->vb2_queue;
void *mem_priv;
int plane;
int ret = -ENOMEM;
/*
* Allocate memory for all planes in this buffer
* NOTE: mmapped areas should be page aligned
*/
for (plane = 0; plane < vb->num_planes; ++plane) {
/* Memops alloc requires size to be page aligned. */
// 分配的内存大小按页对齐
unsigned long size = PAGE_ALIGN(vb->planes[plane].length);
/* Did it wrap around? */
if (size < vb->planes[plane].length)
goto free;
// 回调 vb2_queue 中 mem_ops 的 alloc
// sun6i-csi平台实为:vb2_dma_contig_memops.alloc
// 即是:vb2_dc_alloc
mem_priv = call_ptr_memop(vb, alloc,
q->alloc_devs[plane] ? : q->dev,
q->dma_attrs, size, q->dma_dir, q->gfp_flags);
if (IS_ERR_OR_NULL(mem_priv)) {
if (mem_priv)
ret = PTR_ERR(mem_priv);
goto free;
}
/* Associate allocator private data with this plane */
// 将额外分配的内存保存到mem_priv成员中
vb->planes[plane].mem_priv = mem_priv;
}
return 0;
free:
/* Free already allocated memory if one of the allocations failed */
for (; plane > 0; --plane) {
call_void_memop(vb, put, vb->planes[plane - 1].mem_priv);
vb->planes[plane - 1].mem_priv = NULL;
}
return ret;
}
mem_ops->alloc()函数
此函数依据场景不同,有三种,sun6i-csi平台使用的的事 缓冲区虚拟地址和物理地址都连续,属于第三种类型的 videobuf2
,故该函数为 vb2_dc_alloc
。
// 源码:drivers/media/common/videobuf2/videobuf2-dma-contig.c
struct vb2_dc_buf {
struct device *dev;
void *vaddr; // 内存虚拟地址
unsigned long size; // 内存大小
void *cookie;
dma_addr_t dma_addr; // 内存物理地址
unsigned long attrs; // dma 内存属性
enum dma_data_direction dma_dir;//dma传输方向
struct sg_table *dma_sgt; // SG DMA相关
struct frame_vector *vec;
/* MMAP related */
struct vb2_vmarea_handler handler;
refcount_t refcount;
struct sg_table *sgt_base;
/* DMABUF related */
struct dma_buf_attachment *db_attach;// DMABUF相关变量
};
static void *vb2_dc_alloc(struct device *dev, unsigned long attrs,
unsigned long size, enum dma_data_direction dma_dir,
gfp_t gfp_flags)
{
struct vb2_dc_buf *buf;
if (WARN_ON(!dev))
return ERR_PTR(-EINVAL);
// 首先分配一个struct vb2_dc_buf结构体
buf = kzalloc(sizeof *buf, GFP_KERNEL);
if (!buf)
return ERR_PTR(-ENOMEM);
if (attrs)
buf->attrs = attrs;
// 然后分配存储图像数据的缓冲区,此缓冲区的物理地址和虚拟地址都连续
buf->cookie = dma_alloc_attrs(dev, size, &buf->dma_addr,
GFP_KERNEL | gfp_flags, buf->attrs);
if (!buf->cookie) {
dev_err(dev, "dma_alloc_coherent of size %ld failed\n", size);
kfree(buf);
return ERR_PTR(-ENOMEM);
}
if ((buf->attrs & DMA_ATTR_NO_KERNEL_MAPPING) == 0)
buf->vaddr = buf->cookie;
/* Prevent the device from being released while the buffer is used */
buf->dev = get_device(dev); // 设置父设备指针
buf->size = size; // 保存图像数据缓冲区的大小
buf->dma_dir = dma_dir; // 记录DMA传输方向
// 设置struct vb2_vmarea_handler结构体
buf->handler.refcount = &buf->refcount;
buf->handler.put = vb2_dc_put; // 回调函数
buf->handler.arg = buf; // 回调函数参数
refcount_set(&buf->refcount, 1); // 增加引用计数,引用计数为0时释放缓冲区
return buf; // 返回分配的 vb2_dc_buf 地址
}
VIDIOC_QUERYBUF
使用 VIDIOC_QUERYBUF
命令调用ioctl,最终会调用到 vb2_querybuf
函数,内核使用 vb2_querybuf
函数将缓冲区信息拷贝到用户空间,主要有时间戳 timestamp
、标志 flags
、缓冲区长度 length
、缓冲区偏移 offset
等信息。
函数调用流程如下:
v4l_querybuf
|--->vb2_ioctl_querybuf(sun6i_video_ioctl_ops.vidioc_querybuf)
|--->vb2_querybuf
|--->vb2_core_querybuf
|--->__fill_v4l2_buffer(v4l2_buf_ops.fill_user_buffer)
// 源码: drivers/media/common/videobuf2/videobuf2-v4l2.c
/*
* __fill_v4l2_buffer() - fill in a struct v4l2_buffer with information to be
* returned to userspace
*/
static void __fill_v4l2_buffer(struct vb2_buffer *vb, void *pb)
{
struct v4l2_buffer *b = pb;
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
struct vb2_queue *q = vb->vb2_queue;
unsigned int plane;
/* Copy back data such as timestamp, flags, etc. */
b->index = vb->index;
b->type = vb->type;
b->memory = vb->memory;
b->bytesused = 0;
b->flags = vbuf->flags;
b->field = vbuf->field;
b->timestamp = ns_to_timeval(vb->timestamp);
b->timecode = vbuf->timecode;
b->sequence = vbuf->sequence;
b->reserved2 = 0;
b->request_fd = 0;
if (q->is_multiplanar) {
/*
* Fill in plane-related data if userspace provided an array
* for it. The caller has already verified memory and size.
*/
b->length = vb->num_planes;
for (plane = 0; plane < vb->num_planes; ++plane) {
struct v4l2_plane *pdst = &b->m.planes[plane];
struct vb2_plane *psrc = &vb->planes[plane];
pdst->bytesused = psrc->bytesused;
pdst->length = psrc->length;
if (q->memory == VB2_MEMORY_MMAP)
pdst->m.mem_offset = psrc->m.offset;
else if (q->memory == VB2_MEMORY_USERPTR)
pdst->m.userptr = psrc->m.userptr;
else if (q->memory == VB2_MEMORY_DMABUF)
pdst->m.fd = psrc->m.fd;
pdst->data_offset = psrc->data_offset;
memset(pdst->reserved, 0, sizeof(pdst->reserved));
}
} else {
/*
* We use length and offset in v4l2_planes array even for
* single-planar buffers, but userspace does not.
*/
b->length = vb->planes[0].length;
b->bytesused = vb->planes[0].bytesused;
if (q->memory == VB2_MEMORY_MMAP)
b->m.offset = vb->planes[0].m.offset;
else if (q->memory == VB2_MEMORY_USERPTR)
b->m.userptr = vb->planes[0].m.userptr;
else if (q->memory == VB2_MEMORY_DMABUF)
b->m.fd = vb->planes[0].m.fd;
}
/*
* Clear any buffer state related flags.
*/
b->flags &= ~V4L2_BUFFER_MASK_FLAGS;
b->flags |= q->timestamp_flags & V4L2_BUF_FLAG_TIMESTAMP_MASK;
if (!q->copy_timestamp) {
/*
* For non-COPY timestamps, drop timestamp source bits
* and obtain the timestamp source from the queue.
*/
b->flags &= ~V4L2_BUF_FLAG_TSTAMP_SRC_MASK;
b->flags |= q->timestamp_flags & V4L2_BUF_FLAG_TSTAMP_SRC_MASK;
}
switch (vb->state) {
case VB2_BUF_STATE_QUEUED:
case VB2_BUF_STATE_ACTIVE:
b->flags |= V4L2_BUF_FLAG_QUEUED;
break;
case VB2_BUF_STATE_IN_REQUEST:
b->flags |= V4L2_BUF_FLAG_IN_REQUEST;
break;
case VB2_BUF_STATE_ERROR:
b->flags |= V4L2_BUF_FLAG_ERROR;
/* fall through */
case VB2_BUF_STATE_DONE:
b->flags |= V4L2_BUF_FLAG_DONE;
break;
case VB2_BUF_STATE_PREPARING:
case VB2_BUF_STATE_DEQUEUED:
/* nothing */
break;
}
if ((vb->state == VB2_BUF_STATE_DEQUEUED ||
vb->state == VB2_BUF_STATE_IN_REQUEST) &&
vb->synced && vb->prepared)
b->flags |= V4L2_BUF_FLAG_PREPARED;
if (vb2_buffer_in_use(q, vb))
b->flags |= V4L2_BUF_FLAG_MAPPED;
if (vbuf->request_fd >= 0) {
b->flags |= V4L2_BUF_FLAG_REQUEST_FD;
b->request_fd = vbuf->request_fd;
}
}
VIDIOC_QBUF
使用 VIDIOC_QBUF
命令调用ioctl,最终会调用到 vb2_qbuf函数
,内核使用 vb2_qbuf函数
将读取完数据的空缓存返还给驱动的缓存队列。调用流程如下:
v4l_qbuf
|--->vb2_ioctl_qbuf(sun6i_video_ioctl_ops.vidioc_qbuf)
|--->vb2_qbuf
|--->vb2_core_qbuf
vb2_qbuf
的主要工作如下:
(1)检查当前 v4l2_buffer
参数是否合法——vb2_queue_or_prepare_buf
;
(2)根据 v4l2_buffer index
获取 bufs数组 获取对应编号的缓冲区地址。
(3)根据缓冲区的不同状态做不同的处理。
VB2_BUF_STATE_DEQUEUED
/ VB2_BUF_STATE_IN_REQUEST
状态的缓冲区需要调用 __buf_prepare函数
执行一些准备工作。
首先将缓冲区状态设置为 VB2_BUF_STATE_PREPARING
,V4L2_MEMORY_MMAP
调用__prepare_mmap
,sun6i-csi 平台调用 sun6i_video_buffer_prepare
。其他状态的缓冲区直接返回错误,不能进行 VIDIOC_QBUF
操作。
(4)将缓冲区挂到 queued_list
链表中。
(5)设置缓冲区状态为 VB2_BUF_STATE_QUEUED
。
(6)若 VIDIOC_STREAMON
已经被调用,start_streaming
未成功调用,当前 buf 数量 多余 最小,此时应再次调用 start_streaming
。
vb2_qbuf函数:
// 源码: drivers/media/common/videobuf2/videobuf2-v4l2.c
int vb2_qbuf(struct vb2_queue *q, struct media_device *mdev,
struct v4l2_buffer *b)
{
struct media_request *req = NULL;
int ret;
if (vb2_fileio_is_active(q)) {
dprintk(1, "file io in progress\n");
return -EBUSY;
}
// 参数合法性检查,主要有以下几个:
// 1. v4l2_buffer type 与 vb2_queue type
// 2. v4l2_buffer index 是否越界
// 3. vb2_queue bufs 所对应的 vb2_buffer 是否存在
// 4. v4l2_buffer memory 与 vb2_queue memory 类型是否一致
ret = vb2_queue_or_prepare_buf(q, mdev, b, false, &req);
if (ret)
return ret;
ret = vb2_core_qbuf(q, b->index, b, req);
if (req)
media_request_put(req);
return ret;
}
EXPORT_SYMBOL_GPL(vb2_qbuf);
vb2_core_qbuf函数:
// 源码: drivers/media/common/videobuf2/videobuf2-core.c
int vb2_core_qbuf(struct vb2_queue *q, unsigned int index, void *pb,
struct media_request *req)
{
struct vb2_buffer *vb;
int ret;
if (q->error) {
dprintk(1, "fatal error occurred on queue\n");
return -EIO;
}
// 获取对应编号的缓冲区地址
vb = q->bufs[index];
if (!req && vb->state != VB2_BUF_STATE_IN_REQUEST &&
q->requires_requests) {
dprintk(1, "qbuf requires a request\n");
return -EBADR;
}
if ((req && q->uses_qbuf) ||
(!req && vb->state != VB2_BUF_STATE_IN_REQUEST &&
q->uses_requests)) {
dprintk(1, "queue in wrong mode (qbuf vs requests)\n");
return -EBUSY;
}
if (req) {
int ret;
q->uses_requests = 1;
// 验证 vb 的状态是否为缓冲区出队
if (vb->state != VB2_BUF_STATE_DEQUEUED) {
dprintk(1, "buffer %d not in dequeued state\n",
vb->index);
return -EINVAL;
}
if (q->is_output && !vb->prepared) {
ret = call_vb_qop(vb, buf_out_validate, vb);
if (ret) {
dprintk(1, "buffer validation failed\n");
return ret;
}
}
media_request_object_init(&vb->req_obj);
......
// 更改 vb 的状态为缓冲区排队中
vb->state = VB2_BUF_STATE_IN_REQUEST;
/*
* Increment the refcount and store the request.
* The request refcount is decremented again when the
* buffer is dequeued. This is to prevent vb2_buffer_done()
* from freeing the request from interrupt context, which can
* happen if the application closed the request fd after
* queueing the request.
*/
media_request_get(req);
vb->request = req;
/* Fill buffer information for the userspace */
if (pb) {
// 此pb,实为v4l2_buffer,copy 时间戳
call_void_bufop(q, copy_timestamp, vb, pb);
// 此pb,实为v4l2_buffer,flag、memory类型等
call_void_bufop(q, fill_user_buffer, vb, pb);
}
dprintk(2, "qbuf of buffer %d succeeded\n", vb->index);
return 0;
}
if (vb->state != VB2_BUF_STATE_IN_REQUEST)
q->uses_qbuf = 1;
// 检查缓冲区状态,只有 VB2_BUF_STATE_DEQUEUED 和
// VB2_BUF_STATE_IN_REQUEST 状态才能被加入到驱动的缓存队列
switch (vb->state) {
case VB2_BUF_STATE_DEQUEUED:
case VB2_BUF_STATE_IN_REQUEST:
if (!vb->prepared) {
ret = __buf_prepare(vb);
if (ret)
return ret;
}
break;
case VB2_BUF_STATE_PREPARING:
dprintk(1, "buffer still being prepared\n");
return -EINVAL;
default:
dprintk(1, "invalid buffer state %d\n", vb->state);
return -EINVAL;
}
/*
* Add to the queued buffers list, a buffer will stay on it until
* dequeued in dqbuf.
*/
// 将应用传入的缓冲区挂到queued_list链表中
list_add_tail(&vb->queued_entry, &q->queued_list);
// queued_list链表中的缓冲区数量加1
q->queued_count++;
q->waiting_for_buffers = false;
// 将该 vb 的状态 缓冲区入队中
vb->state = VB2_BUF_STATE_QUEUED;
if (pb)
call_void_bufop(q, copy_timestamp, vb, pb);
trace_vb2_qbuf(q, vb);
/*
* If already streaming, give the buffer to driver for processing.
* If not, the buffer will be given to driver on next streamon.
*/
if (q->start_streaming_called)
__enqueue_in_driver(vb);
/* Fill buffer information for the userspace */
if (pb) // 向用户空间填充信息
call_void_bufop(q, fill_user_buffer, vb, pb);
/*
* If streamon has been called, and we haven't yet called
* start_streaming() since not enough buffers were queued, and
* we now have reached the minimum number of queued buffers,
* then we can finally call start_streaming().
*/
// 若 VIDIOC_STREAMON已经被调用,start_streaming未成功调用,
// 当前 buf 数量 多余 最小,此时应再次调用 start_streaming
if (q->streaming && !q->start_streaming_called &&
q->queued_count >= q->min_buffers_needed) {
ret = vb2_start_streaming(q);
if (ret)
return ret;
}
dprintk(2, "qbuf of buffer %d succeeded\n", vb->index);
return 0;
}
// 源码: drivers/media/common/videobuf2/videobuf2-core.c
static int __buf_prepare(struct vb2_buffer *vb)
{
struct vb2_queue *q = vb->vb2_queue;
enum vb2_buffer_state orig_state = vb->state;
unsigned int plane;
int ret;
......
// 将该 vb 的状态改为 正在准备缓存中
vb->state = VB2_BUF_STATE_PREPARING;
// 根据 vb2_memory memory 类型初始化内存
switch (q->memory) {
case VB2_MEMORY_MMAP:
ret = __prepare_mmap(vb);
break;
case VB2_MEMORY_USERPTR:
ret = __prepare_userptr(vb);
break;
case VB2_MEMORY_DMABUF:
ret = __prepare_dmabuf(vb);
break;
default:
WARN(1, "Invalid queue type\n");
ret = -EINVAL;
break;
}
......
return 0;
}
// 源码: drivers/media/common/videobuf2/videobuf2-core.c
/*
* __prepare_mmap() - prepare an MMAP buffer
*/
static int __prepare_mmap(struct vb2_buffer *vb)
{
int ret = 0;
// 回调 vb2_queue 的buf_ops 中的 fill_vb2_buffer,
// 实为 __fill_vb2_buffer, 即:v4l2_buf_ops 的 __fill_vb2_buffer
ret = call_bufop(vb->vb2_queue, fill_vb2_buffer,
vb, vb->planes);
// 上面正常,则继续回调 vb2_queue 中的 vb2_ops 的 buf_prepare
// sun6i-csi平台实为:sun6i_video_buffer_prepare
return ret ? ret : call_vb_qop(vb, buf_prepare, vb);
}
sun6i-csi 平台 buf_prepare
的回调函数为 sun6i_video_buffer_prepare
,主要的作用是 检查buf大小
,设置 payload
,获取 dma_addr
并设置 struct sun6i_csi_buffer
。
static int sun6i_video_buffer_prepare(struct vb2_buffer *vb)
{
struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
struct sun6i_csi_buffer *buf =
container_of(vbuf, struct sun6i_csi_buffer, vb);
struct sun6i_video *video = vb2_get_drv_priv(vb->vb2_queue);
unsigned long size = video->fmt.fmt.pix.sizeimage;
if (vb2_plane_size(vb, 0) < size) {
v4l2_err(video->vdev.v4l2_dev, "buffer too small (%lu < %lu)\n",
vb2_plane_size(vb, 0), size);
return -EINVAL;
}
vb2_set_plane_payload(vb, 0, size);
buf->dma_addr = vb2_dma_contig_plane_dma_addr(vb, 0);
vbuf->field = video->fmt.fmt.pix.field;
return 0;
}
VIDIOC_STREAMON
使用 VIDIOC_STREAMON
命令调用ioctl,最终会调用到 vb2_streamon
函数,内核使用 vb2_streamon
函数将开启视频流,对于图像采集设备而言,则设备开始采集图像,并将图像数据保存到缓冲区中。
v4l_streamon
|--->vb2_ioctl_streamon(sun6i_video_ioctl_ops.vidioc_streamon)
|--->vb2_streamon
|--->vb2_core_streamon
|--->vb2_start_streaming
|--->sun6i_video_start_streaming(sun6i_csi_vb2_ops.start_streaming)
|--->v4l2_subdev_call(subdev, video, s_stream, 1);
vb2_streamon
主要的工作如下:
(1)遍历 queued_list链表
,首先将缓冲区状态设置为 VB2_BUF_STATE_ACTIVE
,然后将入队的缓冲区都添加到驱动的队列中。sun6i-csi平台 调用 sun6i_video_buffer_queue
将缓冲区添加到 dma_queue
链表中。
(2)调用 start_streaming
开始视频流,sun6i-csi平台
调用 sun6i_video_start_streaming
函数使能设备,开始图像采集。
(3)流开启标志设置 streaming
设置为1。
// 源码: drivers/media/common/videobuf2/videobuf2-core.c
/*
* vb2_start_streaming() - Attempt to start streaming.
* @q: videobuf2 queue
*
* Attempt to start streaming. When this function is called there must be
* at least q->min_buffers_needed buffers queued up (i.e. the minimum
* number of buffers required for the DMA engine to function). If the
* @start_streaming op fails it is supposed to return all the driver-owned
* buffers back to vb2 in state QUEUED. Check if that happened and if
* not warn and reclaim them forcefully.
*/
static int vb2_start_streaming(struct vb2_queue *q)
{
struct vb2_buffer *vb;
int ret;
/*
* If any buffers were queued before streamon,
* we can now pass them to driver for processing.
*/
// 将queued_list上的所有缓冲区添加到驱动中
// 1. 设置缓冲区状态
// 2. 增加驱动使用缓冲区的计数
// 3. 回调驱动 vb2_queue 中 vb2_ops的 buf_queue,实为 sun6i_video_buffer_queue
list_for_each_entry(vb, &q->queued_list, queued_entry)
__enqueue_in_driver(vb);
/* Tell the driver to start streaming */
q->start_streaming_called = 1;
// 回调驱动中的start_streaming函数,视频设备开始工作
// 实为 sun6i_video_start_streaming
ret = call_qop(q, start_streaming, q,
atomic_read(&q->owned_by_drv_count));
if (!ret)
return 0;
q->start_streaming_called = 0;
dprintk(1, "driver refused to start streaming\n");
/*
* If you see this warning, then the driver isn't cleaning up properly
* after a failed start_streaming(). See the start_streaming()
* documentation in videobuf2-core.h for more information how buffers
* should be returned to vb2 in start_streaming().
*/
if (WARN_ON(atomic_read(&q->owned_by_drv_count))) {
unsigned i;
/*
* Forcefully reclaim buffers if the driver did not
* correctly return them to vb2.
*/
for (i = 0; i < q->num_buffers; ++i) {
vb = q->bufs[i];
if (vb->state == VB2_BUF_STATE_ACTIVE)
vb2_buffer_done(vb, VB2_BUF_STATE_QUEUED);
}
/* Must be zero now */
WARN_ON(atomic_read(&q->owned_by_drv_count));
}
/*
* If done_list is not empty, then start_streaming() didn't call
* vb2_buffer_done(vb, VB2_BUF_STATE_QUEUED) but STATE_ERROR or
* STATE_DONE.
*/
WARN_ON(!list_empty(&q->done_list));
return ret;
}
VIDIOC_DQBUF
使用 VIDIOC_DQBUF
命令调用ioctl,最终会调用到 vb2_dqbuf函数
,内核使用 vb2_dqbuf函数
将填充满数据的缓存从驱动中返回给应用。
v4l_dqbuf
|--->vb2_ioctl_dqbuf(sun6i_video_ioctl_ops.vidioc_dqbuf)
|--->vidioc_dqbuf
|--->vb2_core_dqbuf
vb2_dqbuf
主要的工作如下:
(1)检查缓冲区是否可用。
图像采集模式下,需要等待图像数据填充到缓冲区中才能被使用。非阻塞且无缓冲区可用,则直接返回 -EAGAIN
错误。阻塞且无缓冲区可用,睡眠等待。
(2)缓冲区可用,则获取一个可用的缓冲区并将其从 done_list链表
中删除。
(3)将可用的缓冲区信息拷贝到用户空间。
(4)将可用的缓冲区从 queued_list链表
中删除。
(5)设置缓冲区状态为 VB2_BUF_STATE_DEQUEUED
。
// 源码: drivers/media/common/videobuf2/videobuf2-core.c
int vb2_core_dqbuf(struct vb2_queue *q, unsigned int *pindex, void *pb,
bool nonblocking)
{
struct vb2_buffer *vb = NULL;
int ret;
// 获取一个 done 的 vb2_buffer
ret = __vb2_get_done_vb(q, &vb, pb, nonblocking);
if (ret < 0)
return ret;
switch (vb->state) {
case VB2_BUF_STATE_DONE:
dprintk(3, "returning done buffer\n");
break;
case VB2_BUF_STATE_ERROR:
dprintk(3, "returning done buffer with errors\n");
break;
default:
dprintk(1, "invalid buffer state\n");
return -EINVAL;
}
call_void_vb_qop(vb, buf_finish, vb);
vb->prepared = 0;
if (pindex)
*pindex = vb->index;
/* Fill buffer information for the userspace */
if (pb)
call_void_bufop(q, fill_user_buffer, vb, pb);
/* Remove from videobuf queue */
list_del(&vb->queued_entry);
q->queued_count--; // 入队缓冲区减1
trace_vb2_dqbuf(q, vb);
/* go back to dequeued state */
// 设置缓冲器为VB2_BUF_STATE_DEQUEUED状态
__vb2_dqbuf(vb);
......
return 0;
}
// 源码: drivers/media/common/videobuf2/videobuf2-core.c
/*
* __vb2_get_done_vb() - get a buffer ready for dequeuing
*
* Will sleep if required for nonblocking == false.
*/
static int __vb2_get_done_vb(struct vb2_queue *q, struct vb2_buffer **vb,
void *pb, int nonblocking)
{
unsigned long flags;
int ret = 0;
/*
* Wait for at least one buffer to become available on the done_list.
*/
// 等待缓冲器是否可用,缓冲区可用,返回0
// 非阻塞且无缓冲区可用,则直接返回-EAGAIN错误
// 阻塞且无缓冲区可用,睡眠等待
// wait_prepare 最终调用 vb2_ops_wait_prepare 函数
ret = __vb2_wait_for_done_vb(q, nonblocking);
if (ret)
return ret;
/*
* Driver's lock has been held since we last verified that done_list
* is not empty, so no need for another list_empty(done_list) check.
*/
spin_lock_irqsave(&q->done_lock, flags);
// 获取done_list链表中一个可用的缓冲区
*vb = list_first_entry(&q->done_list, struct vb2_buffer, done_entry);
/*
* Only remove the buffer from done_list if all planes can be
* handled. Some cases such as V4L2 file I/O and DVB have pb
* == NULL; skip the check then as there's nothing to verify.
*/
// 确保取出的缓冲区所有planes可用
if (pb)
ret = call_bufop(q, verify_planes_array, *vb, pb);
// 缓冲区所有planes可用,则将缓冲区从done_list链表移除
if (!ret)
list_del(&(*vb)->done_entry);
spin_unlock_irqrestore(&q->done_lock, flags);
return ret;
}
VIDIOC_STREAMOFF
使用 VIDIOC_STREAMOFF
命令调用ioctl,最终会调用到 vb2_streamoff函数
,内核使用 vb2_streamoff函数
关闭流。调用流程如下:
v4l_streamoff
|--->vb2_ioctl_streamoff(sun6i_video_ioctl_ops.vidioc_streamoff)
|--->vb2_streamoff
|--->vb2_core_streamoff
|--->__vb2_queue_cancel(核心处理函数)
|--->sun6i_video_stop_streaming(sun6i_csi_vb2_ops.stop_streaming)
|--->v4l2_subdev_call(subdev, video, s_stream, 0);
vb2_streamoff
的主要工作如下:
(1)调用驱动提供的函数 stop_streaming
停止流,sun6i-csi平台 调用 sun6i_video_stop_streaming
函数关闭视频采集设备。
(2)清空入队链表和清空完成链表。
(3)将所有缓冲区出队到用户空间并设置 VB2_BUF_STATE_DEQUEUED
状态。
// 源码: drivers/media/common/videobuf2/videobuf2-core.c
/*
* __vb2_queue_cancel() - cancel and stop (pause) streaming
*
* Removes all queued buffers from driver's queue and all buffers queued by
* userspace from videobuf's queue. Returns to state after reqbufs.
*/
static void __vb2_queue_cancel(struct vb2_queue *q)
{
unsigned int i;
/*
* Tell driver to stop all transactions and release all queued
* buffers.
*/
// 调用驱动提供的停止流的函数 sun6i_video_stop_streaming
if (q->start_streaming_called)
call_void_qop(q, stop_streaming, q);
/*
* If you see this warning, then the driver isn't cleaning up properly
* in stop_streaming(). See the stop_streaming() documentation in
* videobuf2-core.h for more information how buffers should be returned
* to vb2 in stop_streaming().
*/
if (WARN_ON(atomic_read(&q->owned_by_drv_count))) {
for (i = 0; i < q->num_buffers; ++i)
if (q->bufs[i]->state == VB2_BUF_STATE_ACTIVE) {
pr_warn("driver bug: stop_streaming operation is leaving buf %p in active state\n",
q->bufs[i]);
vb2_buffer_done(q->bufs[i], VB2_BUF_STATE_ERROR);
}
/* Must be zero now */
WARN_ON(atomic_read(&q->owned_by_drv_count));
}
q->streaming = 0; // 清除流开启标志
q->start_streaming_called = 0; // 清除VIDIOC_STREAMON被调用标志
q->queued_count = 0; // 清除入队缓冲区计数
q->error = 0; // 清楚错误计数
q->uses_requests = 0;
q->uses_qbuf = 0;
/*
* Remove all buffers from videobuf's list...
*/
INIT_LIST_HEAD(&q->queued_list); // 清空入队链表
/*
* ...and done list; userspace will not receive any buffers it
* has not already dequeued before initiating cancel.
*/
INIT_LIST_HEAD(&q->done_list); // 清空完成链表
atomic_set(&q->owned_by_drv_count, 0); // 设置驱动引用计数为0
wake_up_all(&q->done_wq); // 唤醒等待在缓冲区队列的线程
......
}
缓冲区状态变化
通过分析关于缓冲区的ioctl命令执行流程,可以总结出缓冲区的状态变化过程,如下图所示。黄色表示缓冲区属于用户空间,绿色表示缓冲区属于 videobuf2
,青色表示缓冲区属于驱动。
(1)通过 VIDIOC_REQBUFS
命令申请缓冲区后,缓冲区的状态为 VB2_BUF_STATE_DEQUEUED
,缓冲区属于用户空间。
(2)通过 VIDIOC_QBUF
命令将缓冲区添加到内核空间,缓冲区的状态先变为 VB2_BUF_STATE_PREPARING
,然后再变为 VB2_BUF_STATE_QUEUED
,缓冲区属于 videobuf2
。若 VIDIOC_STREAMON
被调用,则 start_streaming_called=1
,则 VIDIOC_QBUF
还会把缓冲区添加到驱动缓冲区队列中,此时缓冲区属于驱动。
(3)通过 VIDIOC_STREAMON
命令开启流后,缓冲区被添加到驱动缓冲区队列中,缓冲区的状态为 VB2_BUF_STATE_ACTIVE
,此时缓冲区属于驱动。
(4)若DMA数据传输完成,则缓冲区中已被填充数据,则驱动调用 vb2_buffer_done
将缓冲区状态更改为 VB2_BUF_STATE_DONE
,此时缓冲区属于 videobuf2
。
mmap
使用 mmap系统调用
映射Video设备,最终会调用到 vb2_mmap
,内核使用 vb2_mmap
函数将Video设备的缓冲映射到用户空间。要使用 mmap
,则缓冲区内存类型必须为 V4L2_MEMORY_MMAP
,虚拟内存区域必须是共享的 VM_SHARED
,图像输出模式时虚拟内存区域必须是可写的 VM_WRITE
,图像采集模式时虚拟内存区域必须是可读的 VM_READ
。调用流程如下:
v4l2_mmap
|--->vb2_fop_mmap(sun6i_video_fops.mmap)
|--->vb2_mmap(核心函数)
|--->vb2_dc_mmap(vb2_queue->mem_ops->mmap)
// 源码: drivers/media/common/videobuf2/videobuf2-core.c
int vb2_mmap(struct vb2_queue *q, struct vm_area_struct *vma)
{
unsigned long off = vma->vm_pgoff << PAGE_SHIFT;
struct vb2_buffer *vb;
unsigned int buffer = 0, plane = 0;
int ret;
unsigned long length;
// 判断 vb2_memory_type 是否为 VB2_MEMORY_MMAP
if (q->memory != VB2_MEMORY_MMAP) {
dprintk(1, "queue is not currently set up for mmap\n");
return -EINVAL;
}
/*
* Check memory area access mode.
*/
// 根据类型 检查 vm_flags标志
if (!(vma->vm_flags & VM_SHARED)) {
dprintk(1, "invalid vma flags, VM_SHARED needed\n");
return -EINVAL;
}
if (q->is_output) {
if (!(vma->vm_flags & VM_WRITE)) {
dprintk(1, "invalid vma flags, VM_WRITE needed\n");
return -EINVAL;
}
} else {
if (!(vma->vm_flags & VM_READ)) {
dprintk(1, "invalid vma flags, VM_READ needed\n");
return -EINVAL;
}
}
mutex_lock(&q->mmap_lock);
if (vb2_fileio_is_active(q)) {
dprintk(1, "mmap: file io in progress\n");
ret = -EBUSY;
goto unlock;
}
/*
* Find the plane corresponding to the offset passed by userspace.
*/
ret = __find_plane_by_offset(q, off, &buffer, &plane);
if (ret)
goto unlock;
vb = q->bufs[buffer];
/*
* MMAP requires page_aligned buffers.
* The buffer length was page_aligned at __vb2_buf_mem_alloc(),
* so, we need to do the same here.
*/
// mmap要求内存按页对齐
length = PAGE_ALIGN(vb->planes[plane].length);
if (length < (vma->vm_end - vma->vm_start)) {
dprintk(1,
"MMAP invalid, as it would overflow buffer length\n");
ret = -EINVAL;
goto unlock;
}
/*
* vm_pgoff is treated in V4L2 API as a 'cookie' to select a buffer,
* not as a in-buffer offset. We always want to mmap a whole buffer
* from its beginning.
*/
vma->vm_pgoff = 0;
// 调用驱动提供的mmap函数,实为 vb2_dc_mmap(vb2_dma_contig_memops.mmap)
ret = call_memop(vb, mmap, vb->planes[plane].mem_priv, vma);
unlock:
mutex_unlock(&q->mmap_lock);
if (ret)
return ret;
dprintk(3, "buffer %d, plane %d successfully mapped\n", buffer, plane);
return 0;
}
//源码:drivers/media/common/videobuf2/videobuf2-dma-contig.c
static int vb2_dc_mmap(void *buf_priv, struct vm_area_struct *vma)
{
struct vb2_dc_buf *buf = buf_priv;
int ret;
if (!buf) {
printk(KERN_ERR "No buffer to map\n");
return -EINVAL;
}
// DMA内存一致性映射
ret = dma_mmap_attrs(buf->dev, vma, buf->cookie,
buf->dma_addr, buf->size, buf->attrs);
if (ret) {
pr_err("Remapping memory failed, error: %d\n", ret);
return ret;
}
// 设置虚拟内存不能扩大和core dump标志
vma->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP;
// 设置私有数据
vma->vm_private_data = &buf->handler;
// 设置虚拟内存的操作函数
vma->vm_ops = &vb2_common_vm_ops;
// 打开虚拟内存
vma->vm_ops->open(vma);
pr_debug("%s: mapped dma addr 0x%08lx at 0x%08lx, size %ld\n",
__func__, (unsigned long)buf->dma_addr, vma->vm_start,
buf->size);
return 0;
}
close
使用close系统关闭Video设备,最终会调用到 vb2_queue_release,内核使用 vb2_queue_release 关闭Video设备、清理缓冲区队列和释放缓冲区占用的内存。调用流程如下:
v4l2_release
|--->sun6i_video_close(sun6i_video_fops.release)
|--->_vb2_fop_release
|--->vb2_queue_release
|--->vb2_core_queue_release
|--->__vb2_cleanup_fileio
|--->__vb2_queue_cancel
|--->__vb2_queue_free
vb2_core_queue_release 的主要工作如下:
(1)释放文件I/O模拟器占用的资源
(2)停止流,关闭Video设备,清空入队链表和清空完成链表,将所有缓冲区出队到用户空间并设置 VB2_BUF_STATE_DEQUEUED 状态。
(3)释放缓冲区内存。
// 源码: drivers/media/common/videobuf2/videobuf2-core.c
void vb2_core_queue_release(struct vb2_queue *q)
{
__vb2_cleanup_fileio(q); // 释放文件I/O模拟器占用的资源
__vb2_queue_cancel(q); // 退出和停止流
mutex_lock(&q->mmap_lock);
__vb2_queue_free(q, q->num_buffers);// 释放缓冲区内存
mutex_unlock(&q->mmap_lock);
}
EXPORT_SYMBOL_GPL(vb2_core_queue_release);