3. 缓冲区队列初始化:vb2_queue_init 参数详解与 queue_setup 回调实现
缓冲区队列初始化是 videobuf2 框架中驱动与核心交互的第一个关键步骤。驱动通过 vb2_queue_init() 向 V4L2 核心注册一个 vb2_queue 实例,并绑定一系列回调函数。其中 queue_setup 回调决定了缓冲区分配的内存类型、数量与大小,直接影响后续映射与 DMA 操作的效率。
3.1 vb2_queue 结构体核心参数
struct vb2_queue 是 videobuf2 框架中管理缓冲区队列的核心数据结构。驱动在初始化时必须正确填充以下关键字段:
| 参数 | 类型 | 说明 | 典型值 / 注意事项 |
|---|---|---|---|
| type | enum v4l2_buf_type | 缓冲区类型,决定数据流向 | V4L2_BUF_TYPE_VIDEO_CAPTURE 或 V4L2_BUF_TYPE_VIDEO_OUTPUT |
| io_modes | u32 | 支持的 I/O 模式位掩码 | VB2_MMAP | VB2_USERPTR | VB2_DMABUF |
| drv_priv | void * | 驱动私有数据指针,通常指向设备结构体 | 在回调中通过 vb2_get_drv_priv(vq) 获取 |
| buf_struct_size | unsigned int | 每个缓冲区私有结构体的大小(字节) | 若驱动需要扩展缓冲区元数据,设为 sizeof(struct my_buffer) |
| ops | struct vb2_ops * | 回调函数集合指针 | 必须实现 queue_setup、buf_prepare、buf_queue 等 |
| mem_ops | struct vb2_mem_ops * | 内存分配器操作集 | 通常使用 vb2_dma_contig_memops 或 vb2_vmalloc_memops |
| timestamp_flags | u32 | 时间戳标志 | V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC |
| min_buffers_needed | unsigned int | 驱动正常工作所需的最小缓冲区数 | 对于流水线型驱动,通常设为 2 或 3 |
调优要点:
buf_struct_size 如果设置过小,驱动无法存储每个缓冲区的 DMA 地址或帧描述符;设置过大则浪费内存。建议在驱动私有结构中包含 struct vb2_v4l2_buffer v4l2_buf 作为第一个成员,然后扩展自定义字段。
3.2 queue_setup 回调:缓冲区分配决策中心
queue_setup 回调在 REQBUFS 或 CREATE_BUFS 时被调用,驱动必须在此回调中告知核心:
- 需要分配多少个缓冲区(
num_planes和plane_sizes) - 每个平面的字节大小
- 是否允许分配请求数量的缓冲区(返回 0 表示同意,负值表示拒绝)
回调原型如下:
static int my_queue_setup(struct vb2_queue *vq,
unsigned int *num_buffers,
unsigned int *num_planes,
unsigned int sizes[],
struct device *alloc_devs[])
{
struct my_device *dev = vb2_get_drv_priv(vq);
// 1. 检查缓冲区数量是否在合理范围内
if (*num_buffers < dev->min_buffers)
*num_buffers = dev->min_buffers;
if (*num_buffers > MY_MAX_BUFFERS)
*num_buffers = MY_MAX_BUFFERS;
// 2. 设置平面数量(单平面或双平面)
*num_planes = 1;
// 3. 设置每个平面的大小(对齐到 PAGE_SIZE)
sizes[0] = PAGE_ALIGN(dev->width * dev->height * 2); // YUYV 格式
// 4. 指定分配设备(用于 DMA 一致性内存)
alloc_devs[0] = dev->dev;
return 0;
}
常见错误: 在
queue_setup 中直接分配内存或申请 DMA 缓冲区。该回调只负责描述缓冲区需求,实际内存分配由 videobuf2 核心根据 mem_ops 在后续步骤完成。驱动不应在此处调用 dma_alloc_coherent。
3.3 内存分配策略对比
videobuf2 支持三种内存分配后端,驱动通过 mem_ops 选择:
| 内存类型 | mem_ops 实现 | 适用场景 | DMA 支持 | 性能特征 |
|---|---|---|---|---|
| vmalloc | vb2_vmalloc_memops |
USB 摄像头、模拟采集卡(非 DMA) | 不支持硬件 DMA | 低吞吐,CPU 拷贝开销大 |
| DMA 连续 | vb2_dma_contig_memops |
大多数嵌入式摄像头、ISP、DMA 引擎 | 支持,物理地址连续 | 高吞吐,适合大块传输 |
| DMA 分散/聚集 | vb2_dma_sg_memops |
支持 SG 的 DMA 引擎、高性能场景 | 支持,物理地址不连续 | 高吞吐,减少内存碎片 |
调优建议: 对于嵌入式平台,优先使用
vb2_dma_contig_memops。如果硬件支持 scatter-gather DMA,且系统内存碎片严重,切换到 vb2_dma_sg_memops 可以避免连续物理内存分配失败。
3.4 缓冲区队列初始化流程 SVG
下图展示了从驱动 probe 到 vb2_queue_init 完成,再到 REQBUFS 触发 queue_setup 的完整流程:
3.5 多平面与格式协商
对于 YUV 422 或 NV12 等格式,驱动可能需要使用多平面缓冲区。在 queue_setup 中设置 *num_planes = 2 并分别指定 sizes[0] 和 sizes[1]:
static int my_queue_setup_mplane(struct vb2_queue *vq,
unsigned int *num_buffers,
unsigned int *num_planes,
unsigned int sizes[],
struct device *alloc_devs[])
{
struct my_device *dev = vb2_get_drv_priv(vq);
*num_planes = 2;
sizes[0] = PAGE_ALIGN(dev->width * dev->height); // Y 平面
sizes[1] = PAGE_ALIGN(dev->width * dev->height / 2); // UV 平面
alloc_devs[0] = dev->dev;
alloc_devs[1] = dev->dev;
return 0;
}
调优提示: 多平面场景下,确保
alloc_devs[] 指向正确的 DMA 设备。如果两个平面使用不同的内存控制器(例如 ISP 的 Y 通道和 UV 通道分离),可以指向不同的 struct device *。
3.6 初始化参数检查清单
在调用 vb2_queue_init 之前,建议驱动按以下清单逐项确认:
- type 是否与设备能力匹配(CAPTURE vs OUTPUT)
- io_modes 是否包含应用层可能请求的模式(至少支持 MMAP)
- mem_ops 是否与硬件 DMA 能力匹配(连续 vs SG)
- ops 是否实现了所有必需回调(
queue_setup、buf_prepare、buf_queue、start_streaming、stop_streaming) - buf_struct_size 是否足够容纳驱动私有数据
- min_buffers_needed 是否满足流水线深度需求
- drv_priv 是否指向有效的设备结构体
完成上述参数填充后,调用 vb2_queue_init(vq) 返回 0 表示成功。此时队列已注册到 V4L2 核心,等待应用层通过 REQBUFS 触发缓冲区分配。