2. V4L2 框架基础:Video for Linux 2 核心机制
V4L2(Video for Linux 2)是 Linux 内核中视频设备驱动的标准框架,为摄像头、视频采集卡、ISP 等设备提供统一的用户空间接口。理解 V4L2 的核心数据结构、ioctl 接口和 buffer 管理机制,是编写 ISP Pipeline 驱动的基石。
video_device、v4l2_device 和 v4l2_subdev 三个核心结构体的协作关系。
2.1 核心数据结构体系
V4L2 框架采用三层结构来管理视频设备:顶层 video_device 负责与用户空间交互,中间层 v4l2_device 作为设备实例的容器,底层 v4l2_subdev 管理子设备(如传感器、ISP 模块)。
2.1.1 video_device:用户空间接口
video_device 是 V4L2 框架中最上层的结构体,负责将视频设备注册为字符设备(如 /dev/video0),并实现 file_operations 和 v4l2_ioctl_ops 回调。
// include/media/v4l2-dev.h
struct video_device {
const struct v4l2_file_operations *fops; // 文件操作回调
const struct v4l2_ioctl_ops *ioctl_ops; // ioctl 命令处理
struct device dev; // 内核设备结构
struct v4l2_device *v4l2_dev; // 指向所属 v4l2_device
struct v4l2_ctrl_handler *ctrl_handler; // 控制处理器
unsigned long flags; // 设备标志
int index; // 设备节点编号
// ... 其他字段
};
- fops:实现
open/release/read/write/poll/mmap等标准文件操作。 - ioctl_ops:包含
vidioc_querycap/vidioc_s_fmt/vidioc_reqbufs/vidioc_qbuf/vidioc_dqbuf等回调。 - v4l2_dev:反向链接到所属的
v4l2_device,用于访问子设备列表。 - ctrl_handler:管理曝光、增益、白平衡等 V4L2 控制参数。
2.1.2 v4l2_device:设备实例容器
v4l2_device 作为顶层设备实例,负责管理所有关联的 v4l2_subdev 子设备,并提供统一的注册/注销接口。
// include/media/v4l2-device.h
struct v4l2_device {
struct device *dev; // 父设备指针
struct list_head subdevs; // 子设备链表头
spinlock_t lock; // 自旋锁
char name[V4L2_DEVICE_NAME_SIZE]; // 设备名称
// ... 其他字段
};
| 方法 | 功能描述 |
|---|---|
v4l2_device_register() | 注册 v4l2_device,初始化子设备链表 |
v4l2_device_unregister() | 注销 v4l2_device,自动注销所有子设备 |
v4l2_device_register_subdev() | 将 subdev 添加到设备链表 |
v4l2_device_for_each_subdev() | 遍历所有已注册的子设备 |
2.1.3 v4l2_subdev:子设备抽象
v4l2_subdev 是 V4L2 框架中最重要的抽象层,用于表示传感器、ISP 模块、CSI 接收器等硬件组件。每个 subdev 可以有自己的 v4l2_subdev_ops 回调集合。
// include/media/v4l2-subdev.h
struct v4l2_subdev {
struct v4l2_device *v4l2_dev; // 所属 v4l2_device
struct module *owner; // 模块所有者
const struct v4l2_subdev_ops *ops; // 子设备操作回调
const struct v4l2_subdev_internal_ops *internal_ops; // 内部操作
struct v4l2_ctrl_handler *ctrl_handler; // 子设备控制处理器
struct device *dev; // 子设备对应的 struct device
// ... 其他字段
};
v4l2_subdev。通过 v4l2_subdev_call() 宏在 subdev 之间传递控制命令,实现 Pipeline 的流水线控制。
2.2 ioctl 接口:用户态与内核态的桥梁
V4L2 通过 ioctl 系统调用实现用户态与内核驱动的交互。所有 ioctl 命令以 VIDIOC_ 为前缀,分为查询、设置、流控制、buffer 管理四大类。
2.2.1 核心 ioctl 命令分类
| 类别 | 命令 | 功能 |
|---|---|---|
| 能力查询 | VIDIOC_QUERYCAP | 查询设备能力(capture/output/m2m 等) |
| 格式协商 | VIDIOC_ENUM_FMT | 枚举支持的像素格式 |
VIDIOC_S_FMT / VIDIOC_G_FMT | 设置/获取当前格式 | |
VIDIOC_TRY_FMT | 尝试格式是否支持(不实际设置) | |
| 参数控制 | VIDIOC_S_CTRL / VIDIOC_G_CTRL | 设置/获取单个控制参数 |
VIDIOC_QUERYCTRL | 查询控制参数属性 | |
| 流控制 | VIDIOC_STREAMON / VIDIOC_STREAMOFF | 启动/停止视频流 |
| Buffer 管理 | VIDIOC_REQBUFS | 请求分配 buffer |
VIDIOC_QBUF / VIDIOC_DQBUF | 入队/出队 buffer | |
VIDIOC_QUERYBUF | 查询 buffer 状态和地址 | |
| 事件机制 | VIDIOC_SUBSCRIBE_EVENT | 订阅事件(如帧完成、错误) |
VIDIOC_DQEVENT | 出队事件 |
2.2.2 ioctl_ops 回调注册
驱动开发者需要实现 v4l2_ioctl_ops 结构体中的回调函数,并通过 video_device 的 ioctl_ops 字段注册。
static const struct v4l2_ioctl_ops isp_ioctl_ops = {
.vidioc_querycap = isp_vidioc_querycap,
.vidioc_enum_fmt_vid_cap = isp_vidioc_enum_fmt,
.vidioc_s_fmt_vid_cap = isp_vidioc_s_fmt,
.vidioc_g_fmt_vid_cap = isp_vidioc_g_fmt,
.vidioc_try_fmt_vid_cap = isp_vidioc_try_fmt,
.vidioc_reqbufs = isp_vidioc_reqbufs,
.vidioc_querybuf = isp_vidioc_querybuf,
.vidioc_qbuf = isp_vidioc_qbuf,
.vidioc_dqbuf = isp_vidioc_dqbuf,
.vidioc_streamon = isp_vidioc_streamon,
.vidioc_streamoff = isp_vidioc_streamoff,
};
2.3 Buffer 管理机制
V4L2 的 buffer 管理是视频采集性能的核心。框架支持三种 buffer 类型:V4L2_MEMORY_MMAP(内存映射)、V4L2_MEMORY_USERPTR(用户指针)和 V4L2_MEMORY_DMABUF(DMA buffer 共享)。ISP Pipeline 驱动通常使用 MMAP 或 DMABUF 模式。
2.3.1 buffer 类型对比
| 类型 | 内存来源 | 用户态访问 | ISP 适用场景 | 性能特点 |
|---|---|---|---|---|
| MMAP | 内核分配 DMA 内存 | mmap 映射到用户空间 | 标准 ISP 采集 | 零拷贝,性能最优 |
| USERPTR | 用户态分配内存 | 直接访问用户指针 | 较少使用 | 需内核锁定页面,有拷贝开销 |
| DMABUF | 其他驱动分配 | 通过 dma-buf 共享 | ISP + GPU/显示协同 | 跨设备零拷贝,适合复杂 Pipeline |
2.3.2 videobuf2 框架
现代 V4L2 驱动推荐使用 videobuf2 (VB2) 框架管理 buffer。VB2 封装了 buffer 分配、队列管理、内存映射等复杂逻辑,驱动只需实现 vb2_ops 回调。
// include/media/videobuf2-core.h
struct vb2_ops {
int (*queue_setup)(struct vb2_queue *q,
unsigned int *num_buffers,
unsigned int *num_planes,
unsigned int sizes[],
struct device *alloc_devs[]);
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);
void (*stop_streaming)(struct vb2_queue *q);
void (*buf_queue)(struct vb2_buffer *vb); // 核心回调:buffer 入硬件队列
};
- queue_setup:根据当前格式设置 buffer 数量和大小,ISP 通常需要 4~6 个 buffer。
- buf_prepare:准备 buffer 供硬件使用,如配置 DMA 地址、缓存同步。
- start_streaming:启动 ISP Pipeline 硬件,开始采集。
- buf_queue:将 buffer 添加到硬件 DMA 队列,ISP 驱动需维护一个环形链表。
- stop_streaming:停止硬件,等待所有正在处理的 buffer 完成。
2.3.3 ISP Pipeline 中的 buffer 管理策略
在 ISP Pipeline 驱动中,buffer 管理需要特别关注以下问题:
- 流水线深度:至少保持 3 个 buffer 在硬件队列中,避免 DMA 引擎空转。
- 帧同步:使用硬件 VSYNC 中断触发 DQBUF 完成事件,保证帧序正确。
- 缓存一致性:对于非 coherent DMA,在
buf_prepare和buf_finish中调用dma_sync_single_for_device/for_cpu。 - 错误恢复:当硬件出错时,在
stop_streaming中清空所有 buffer 并返回错误状态。
// ISP 驱动 VB2 buf_queue 回调示例
static void isp_buf_queue(struct vb2_buffer *vb)
{
struct isp_device *isp = vb2_get_drv_priv(vb->vb2_queue);
struct isp_buffer *buf = to_isp_buffer(vb);
unsigned long flags;
spin_lock_irqsave(&isp->dma_lock, flags);
// 将 buffer 添加到硬件 DMA 环形队列尾部
list_add_tail(&buf->list, &isp->dma_queue);
// 如果 DMA 引擎空闲,立即启动传输
if (isp->dma_active == NULL)
isp_start_dma(isp);
spin_unlock_irqrestore(&isp->dma_lock, flags);
}
// DMA 完成中断处理
static irqreturn_t isp_dma_done_irq(int irq, void *data)
{
struct isp_device *isp = data;
struct isp_buffer *done_buf;
spin_lock(&isp->dma_lock);
done_buf = isp->dma_active;
isp->dma_active = NULL;
// 从队列取出下一个 buffer 启动 DMA
if (!list_empty(&isp->dma_queue)) {
isp->dma_active = list_first_entry(&isp->dma_queue,
struct isp_buffer, list);
list_del(&isp->dma_active->list);
isp_start_dma(isp);
}
spin_unlock(&isp->dma_lock);
// 通知 VB2 框架 buffer 已完成
vb2_buffer_done(&done_buf->vb, VB2_BUF_STATE_DONE);
return IRQ_HANDLED;
}
vb2_buffer_done() 应在中断上下文调用,但要注意中断处理时间不宜过长。对于高帧率场景(如 60fps),建议使用 threaded IRQ 或 workqueue 将耗时操作(如统计信息处理)推迟到进程上下文。
2.4 本章小结
V4L2 框架为 ISP Pipeline 驱动提供了完整的基础设施:
- 三层数据结构:
video_device(用户接口)、v4l2_device(设备容器)、v4l2_subdev(硬件抽象)形成清晰的层次结构。 - ioctl 接口:标准化的命令集覆盖了从能力查询到流控制的全部需求,驱动通过
v4l2_ioctl_ops实现具体逻辑。 - Buffer 管理:videobuf2 框架简化了 buffer 生命周期管理,ISP 驱动只需关注硬件 DMA 队列的维护和中断处理。