2. V4L2核心数据结构:struct video_device、struct v4l2_device、struct v4l2_subdev的职责与关系

V4L2框架围绕三个核心数据结构构建:video_device、v4l2_device和v4l2_subdev。理解它们的职责与协作关系,是掌握摄像头驱动开发的基础。本章从设计意图、数据结构、注册流程和交互关系四个维度展开。

2.1 三大核心数据结构总览

下表从职责、生命周期、所属层次和典型实例四个角度对比三者:

数据结构 核心职责 生命周期 所属层次 典型实例
struct video_device 向用户空间暴露/dev/videoX设备节点,处理open/close/ioctl/poll/mmap 随设备节点创建与销毁 V4L2核心层 → 用户接口 UVC摄像头主设备节点
struct v4l2_device 作为顶层容器,管理所有子设备,提供注册/注销/通知/调试基础设施 随物理设备探测与移除 V4L2核心层 → 设备管理 USB摄像头控制器实例
struct v4l2_subdev 封装传感器、ISP、解码器等硬件子模块,提供标准化控制接口 随子设备探测与移除 V4L2子设备层 OV5640传感器、TVP5150解码器
设计原则提示:三者遵循“容器-节点-子模块”的分层模式。v4l2_device是根容器,video_device是用户可见的接口节点,v4l2_subdev是功能模块抽象。这种分层使得驱动可以灵活组合不同传感器和ISP,而不影响用户空间接口。

2.2 struct video_device:用户空间接口

struct video_device是V4L2框架中最接近用户空间的结构体。它负责创建和管理/dev/videoX字符设备节点,并将用户空间的系统调用转换为驱动层的回调函数。

2.2.1 关键字段

struct video_device {
    const struct v4l2_file_operations *fops;  // 文件操作回调
    struct v4l2_ioctl_ops *ioctl_ops;         // ioctl命令处理
    struct device dev;                        // 内核设备结构
    struct v4l2_device *v4l2_dev;             // 所属顶层设备
    struct v4l2_ctrl_handler *ctrl_handler;   // 控制处理器
    unsigned long flags;                      // 设备标志
    int minor;                                // 次设备号
    char name[32];                            // 设备名称
    // ... 其他字段
};

2.2.2 注册流程

  1. 分配与初始化:使用video_device_alloc()分配,或静态定义后手动初始化。
  2. 设置回调:填充fops和ioctl_ops,实现vidioc_querycap、vidioc_s_fmt等核心ioctl。
  3. 关联容器:将v4l2_dev指针指向所属的v4l2_device实例。
  4. 注册:调用video_register_device(),V4L2核心自动分配次设备号并创建设备节点。
关键约束:一个v4l2_device可以注册多个video_device(例如同时提供视频采集和视频输出节点),但每个video_device只能属于一个v4l2_device。

2.3 struct v4l2_device:顶层容器

struct v4l2_device是V4L2驱动的“根对象”。它不直接与用户空间交互,而是作为所有子设备和视频设备节点的管理者,提供注册、注销、通知链和调试文件系统等基础设施。

2.3.1 关键字段

struct v4l2_device {
    struct device *dev;                       // 物理设备指针
    struct list_head subdevs;                 // 子设备链表头
    spinlock_t lock;                          // 内部锁
    char name[V4L2_DEVICE_NAME_SIZE];         // 设备名称
    struct v4l2_ctrl_handler *ctrl_handler;   // 顶层控制处理器
    struct v4l2_prio_state prio;              // 优先级管理
    // ... 其他字段
};

2.3.2 核心职责

  • 子设备管理:通过v4l2_device_register_subdev()注册子设备,维护subdevs链表。
  • 通知机制:提供v4l2_subdev_notify(),允许子设备向顶层设备发送异步事件。
  • 调试支持:在/sys/kernel/debug/v4l2/下创建调试节点。
  • 生命周期管理:在驱动探测时初始化,在移除时注销所有子设备和视频节点。
常见错误:驱动开发者常忘记在v4l2_device的dev字段中设置正确的物理设备指针。这会导致dev_*日志函数无法正确输出设备信息,且影响电源管理回调的传递。

2.4 struct v4l2_subdev:硬件子模块抽象

struct v4l2_subdev是V4L2框架中最重要的可复用组件。它将摄像头传感器、ISP、视频解码器、HDMI接收器等硬件模块抽象为标准化接口,使得主控驱动可以透明地操作不同厂商的硬件。

2.4.1 关键字段

struct v4l2_subdev {
    struct v4l2_device *v4l2_dev;             // 所属顶层设备
    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 list_head list;                    // 挂入v4l2_device的链表节点
    char name[V4L2_SUBDEV_NAME_SIZE];        // 子设备名称
    unsigned long flags;                      // 子设备标志
    // ... 其他字段
};

2.4.2 操作集分类

操作集 包含回调 典型用途
v4l2_subdev_core_ops init, load_fw, reset, ioctl, g_ctrl, s_ctrl 固件加载、复位、通用控制
v4l2_subdev_video_ops s_stream, s_parm, g_parm, querystd, g_input_status 视频流启停、参数查询、标准检测
v4l2_subdev_pad_ops enum_mbus_code, set_fmt, get_fmt, get_selection 媒体总线格式协商、裁剪/缩放配置
v4l2_subdev_sensor_ops g_skip_frames, set_skip_frames 传感器跳帧控制

2.5 三者关系:SVG架构图

下图展示了v4l2_device、video_device和v4l2_subdev之间的层次关系与数据流向:

V4L2核心数据结构关系图 struct v4l2_device 顶层容器:管理子设备与视频节点 struct video_device 用户空间接口:/dev/videoX 设备节点 struct v4l2_subdev 传感器:OV5640 struct v4l2_subdev ISP:图像信号处理器 s_stream/s_fmt set_fmt/enum_mbus 用户空间应用程序 open/ioctl/mmap 关键交互说明: 1. v4l2_device 作为根容器,持有 subdevs 链表和 video_device 引用。 2. video_device 通过 v4l2_dev 指针找到所属容器,通过 ioctl_ops 处理用户请求。 3. v4l2_subdev 通过 v4l2_dev 指针向顶层注册,通过 ops 回调被主控驱动调用。 4. 数据流:用户空间 → video_device → 主控驱动 → v4l2_subdev → 硬件寄存器。

2.6 典型注册流程:三者协作

以下代码片段展示了一个典型的摄像头驱动中,三者如何协作完成初始化:

static int camera_probe(struct platform_device *pdev)
{
    struct v4l2_device *v4l2_dev;
    struct video_device *vdev;
    struct v4l2_subdev *sensor_subdev;
    int ret;

    // 1. 分配并初始化顶层容器
    v4l2_dev = devm_kzalloc(&pdev->dev, sizeof(*v4l2_dev), GFP_KERNEL);
    v4l2_dev->dev = &pdev->dev;
    ret = v4l2_device_register(&pdev->dev, v4l2_dev);
    if (ret < 0)
        return ret;

    // 2. 注册传感器子设备(假设使用I2C)
    sensor_subdev = devm_kzalloc(&pdev->dev, sizeof(*sensor_subdev), GFP_KERNEL);
    v4l2_i2c_subdev_init(sensor_subdev, client, &sensor_ops);
    ret = v4l2_device_register_subdev(v4l2_dev, sensor_subdev);
    if (ret < 0)
        goto err_unregister;

    // 3. 分配并注册视频设备节点
    vdev = video_device_alloc();
    vdev->v4l2_dev = v4l2_dev;
    vdev->fops = &camera_fops;
    vdev->ioctl_ops = &camera_ioctl_ops;
    vdev->release = video_device_release;
    ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
    if (ret < 0)
        goto err_unregister_subdev;

    // 4. 保存私有数据
    platform_set_drvdata(pdev, v4l2_dev);
    return 0;

err_unregister_subdev:
    v4l2_device_unregister_subdev(sensor_subdev);
err_unregister:
    v4l2_device_unregister(v4l2_dev);
    return ret;
}
注册顺序要点:必须先注册v4l2_device,再注册子设备,最后注册video_device。注销时顺序相反:先注销video_device,再注销子设备,最后注销v4l2_device。违反此顺序可能导致内核崩溃或设备节点残留。

2.7 三者关系总结

  • 包含关系:v4l2_device包含v4l2_subdev链表和video_device引用;video_device和v4l2_subdev都持有指向v4l2_device的指针。
  • 职责分离:v4l2_device管生命周期和基础设施;video_device管用户接口;v4l2_subdev管硬件控制。
  • 通信路径:用户空间通过video_device的ioctl进入驱动,驱动通过v4l2_subdev的操作集控制硬件,v4l2_device提供通知和调试通道。
  • 复用性:v4l2_subdev是V4L2框架实现“即插即用”传感器支持的关键,同一子设备驱动可被不同主控驱动复用。