3. V4L2核心数据结构

V4L2(Video for Linux 2)框架通过四个核心数据结构将摄像头设备、子设备、视频节点和缓冲区管理串联起来。理解它们的初始化顺序与注册机制,是编写稳定摄像头驱动的基石。本章逐一剖析 struct video_device、struct v4l2_device、struct v4l2_subdev 和 struct vb2_queue 的生命周期与操作要点。

核心原则: 注册顺序必须遵循“自底向上”原则——先注册 v4l2_device,再注册 subdev,最后注册 video_device;vb2_queue 在 video_device 注册前完成初始化并与 video_device 关联。

3.1 struct v4l2_device:顶层容器

struct v4l2_device 是整个 V4L2 设备的顶层抽象,代表一个物理或逻辑摄像头设备。它负责管理所有子设备(subdev)和视频节点(video_device),并提供统一的 sysfs 接口。

3.1.1 核心字段

字段类型说明
namechar[32]设备名称,用于 sysfs 和日志
devstruct device *指向父设备(通常是 platform_device 或 i2c_client)
subdevsstruct list_head已注册子设备链表头
ctrl_handlerstruct v4l2_ctrl_handler *全局控制处理器(可选)

3.1.2 初始化与注册

#include <media/v4l2-device.h>

struct v4l2_device v4l2_dev;

/* 1. 初始化 v4l2_device */
v4l2_device_init(&v4l2_dev, parent_dev);

/* 2. 注册(通常在 probe 函数末尾) */
ret = v4l2_device_register(parent_dev, &v4l2_dev);
if (ret < 0) {
    dev_err(parent_dev, "Failed to register v4l2_device\n");
    return ret;
}

/* 3. 注销(在 remove 函数中) */
v4l2_device_unregister(&v4l2_dev);
注意: v4l2_device_register() 不会自动创建 video 节点,它只建立设备层次结构。video 节点由 video_register_device() 单独创建。

3.2 struct v4l2_subdev:子设备抽象

摄像头传感器、ISP、AF 控制器等硬件模块都抽象为 struct v4l2_subdev。它通过 v4l2_subdev_ops 暴露操作接口,并挂载到 v4l2_device 的 subdev 链表上。

3.2.1 初始化流程

#include <media/v4l2-subdev.h>

struct v4l2_subdev sd;
struct v4l2_subdev_ops sensor_ops = {
    .core   = &sensor_core_ops,
    .video  = &sensor_video_ops,
    .pad    = &sensor_pad_ops,
};

/* 1. 初始化 subdev */
v4l2_subdev_init(&sd, &sensor_ops);
sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
snprintf(sd.name, sizeof(sd.name), "sensor_%s", dev_name(client));

/* 2. 设置私有数据(可选) */
sd.dev = &client->dev;
v4l2_set_subdevdata(&sd, sensor_priv);

/* 3. 注册到 v4l2_device */
ret = v4l2_device_register_subdev(&v4l2_dev, &sd);
if (ret < 0) {
    dev_err(&client->dev, "Failed to register subdev\n");
    return ret;
}

3.2.2 子设备操作集

操作集用途典型回调
core核心控制s_power, ioctl, log_status
video视频流控制s_stream, g_frame_interval
padPad 级操作get_fmt, set_fmt, enum_mbus_code
sensor传感器专用g_skip_frames, s_exposure

3.3 struct video_device:用户空间接口

struct video_device 是用户空间通过 /dev/videoX 访问的实体。它封装了 file_operations、ioctl 分发和 v4l2_ioctl_ops。

3.3.1 关键字段与初始化

#include <media/v4l2-dev.h>
#include <media/v4l2-ioctl.h>

struct video_device vdev;

/* 1. 设置 ioctl 操作集 */
vdev.ioctl_ops = &camera_ioctl_ops;
vdev.release  = video_device_release_empty;  /* 或自定义 release */

/* 2. 设置设备类型 */
vdev.device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
vdev.vfl_dir     = VFL_DIR_RX;  /* 捕获方向 */

/* 3. 关联 v4l2_device */
vdev.v4l2_dev = &v4l2_dev;

/* 4. 设置队列(见 3.4 节) */
vdev.queue = &vb2_q;

/* 5. 注册 video 节点 */
ret = video_register_device(&vdev, VFL_TYPE_VIDEO, -1);
if (ret < 0) {
    dev_err(v4l2_dev.dev, "Failed to register video device\n");
    return ret;
}

3.3.2 注册参数

参数说明
VFL_TYPE_VIDEO标准视频设备,生成 /dev/videoX
VFL_TYPE_VBIVBI 设备,生成 /dev/vbiX
VFL_TYPE_RADIO收音机设备,生成 /dev/radioX
-1(次设备号)由内核自动分配,推荐使用

3.4 struct vb2_queue:缓冲区管理引擎

struct vb2_queue 是 V4L2 内存管理的核心,负责 buffer 的分配、入队、出队和 DMA 操作。它通过 vb2_ops 回调与驱动交互。

3.4.1 初始化与回调

#include <media/videobuf2-v4l2.h>
#include <media/videobuf2-dma-contig.h>

struct vb2_queue vb2_q;

/* 1. 设置队列参数 */
vb2_q.type           = V4L2_BUF_TYPE_VIDEO_CAPTURE;
vb2_q.io_modes       = VB2_MMAP | VB2_USERPTR | VB2_DMABUF;
vb2_q.drv_priv       = &camera_priv;
vb2_q.buf_struct_size = sizeof(struct camera_buffer);
vb2_q.ops            = &camera_vb2_ops;
vb2_q.mem_ops        = &vb2_dma_contig_memops;
vb2_q.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;

/* 2. 初始化 vb2_queue */
ret = vb2_queue_init(&vb2_q);
if (ret < 0) {
    dev_err(dev, "vb2_queue_init failed\n");
    return ret;
}

/* 3. 在 video_device 注册前关联 */
vdev.queue = &vb2_q;

3.4.2 vb2_ops 回调表

回调触发时机驱动职责
queue_setup用户调用 REQBUFS计算 buffer 数量和大小,分配内存
buf_preparebuffer 入队前验证格式,准备 DMA 映射
buf_queuebuffer 入队将 buffer 加入硬件队列,启动传输
start_streaming流启动开启硬件时钟、MIPI 信号
stop_streaming流停止停止硬件,取消未完成 buffer

3.5 整体初始化顺序

以下 SVG 图展示了四个核心数据结构的初始化与注册时序,以及它们之间的依赖关系。

V4L2 核心数据结构初始化与注册时序 V4L2 核心数据结构初始化与注册时序 时间 1. v4l2_device_init() 初始化顶层容器 2. v4l2_device_register() 注册到 sysfs 层次 3. subdev_init() 初始化子设备 4. register_subdev() 挂载到 v4l2_device 链表 5. vb2_queue_init() 初始化缓冲区引擎 6. video_device 初始化 设置 ioctl_ops, queue 等 7. video_register_device() 创建 /dev/videoX 节点 8. 设备就绪,等待流 IO 用户空间可打开 /dev/videoX v4l2_device v4l2_subdev vb2_queue video_device 就绪状态

3.6 常见错误与避坑

⚠️ 常见错误:
  • 注册顺序颠倒: 先注册 video_device 再注册 v4l2_device,导致 video_device 无法找到父设备,sysfs 链接断裂。
  • vb2_queue 未关联: 忘记将 vdev.queue 指向已初始化的 vb2_queue,用户调用 REQBUFS 时内核崩溃。
  • release 回调未设置: vdev.release 为 NULL,设备注销时内核 panic。
  • subdev 重复注册: 同一个 subdev 被多次调用 v4l2_device_register_subdev(),导致链表损坏。

3.7 完整初始化示例(伪代码)

static int camera_probe(struct platform_device *pdev)
{
    struct camera_priv *priv;
    int ret;

    /* 1. 分配私有结构体 */
    priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;

    /* 2. 初始化 v4l2_device */
    ret = v4l2_device_register(&pdev->dev, &priv->v4l2_dev);
    if (ret)
        return ret;

    /* 3. 初始化并注册 subdev(传感器) */
    v4l2_subdev_init(&priv->sensor, &sensor_ops);
    priv->sensor.entity.function = MEDIA_ENT_F_CAM_SENSOR;
    ret = v4l2_device_register_subdev(&priv->v4l2_dev, &priv->sensor);
    if (ret)
        goto err_unregister_v4l2;

    /* 4. 初始化 vb2_queue */
    priv->vb2_q.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    priv->vb2_q.io_modes = VB2_MMAP;
    priv->vb2_q.ops = &camera_vb2_ops;
    priv->vb2_q.mem_ops = &vb2_dma_contig_memops;
    priv->vb2_q.drv_priv = priv;
    ret = vb2_queue_init(&priv->vb2_q);
    if (ret)
        goto err_unregister_subdev;

    /* 5. 初始化 video_device */
    priv->vdev.ioctl_ops = &camera_ioctl_ops;
    priv->vdev.release  = video_device_release_empty;
    priv->vdev.v4l2_dev = &priv->v4l2_dev;
    priv->vdev.queue    = &priv->vb2_q;
    priv->vdev.device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
    ret = video_register_device(&priv->vdev, VFL_TYPE_VIDEO, -1);
    if (ret)
        goto err_vb2_release;

    platform_set_drvdata(pdev, priv);
    return 0;

err_vb2_release:
    /* vb2_queue 不需要显式释放,随 priv 释放 */
err_unregister_subdev:
    v4l2_device_unregister_subdev(&priv->sensor);
err_unregister_v4l2:
    v4l2_device_unregister(&priv->v4l2_dev);
    return ret;
}
调试技巧: 注册完成后,检查 /sys/class/video4linux/videoX/device/ 是否指向正确的父设备。如果链接缺失,说明 v4l2_device 与 video_device 的关联未正确建立。