V4L2 subdev驱动开发:sensor与ISP协同 - 第2节

2.1 Subdev 在 V4L2 框架中的定位与作用

在嵌入式摄像头系统中,一个完整的视频采集链路通常由 Sensor(图像传感器)、ISP(图像信号处理器)、CSI 接收器 等多个硬件模块串联而成。V4L2(Video for Linux 2)框架引入 Subdev(Sub-device) 概念,正是为了将这条链路上的每个独立硬件单元抽象为可独立控制、可动态连接的软件实体。

核心观点:Subdev 是 V4L2 框架中用于表示非 Video 节点的硬件单元的抽象层。它将复杂的图像处理管线拆解为多个可组合、可复用的驱动模块。

2.1.1 Subdev 的主要作用

作用维度 具体说明 典型场景
硬件抽象 将 Sensor、ISP、MIPI 桥接器等硬件封装为统一的 subdev 对象 Sensor 驱动注册为 subdev,提供曝光、增益控制接口
链路管理 通过 media controller 框架实现 subdev 之间的动态连接与路由 ISP subdev 从 Sensor subdev 接收图像数据
控制分发 提供标准化的控制接口(格式、帧率、曝光、白平衡等) 应用层通过 subdev 节点直接调节 Sensor 参数
事件通知 支持 subdev 间的事件传递(如帧同步、错误上报) Sensor 完成曝光后通知 ISP 开始处理

2.1.2 Subdev 与 Video 节点的关系

V4L2 框架中存在两类核心设备节点:Video 节点(/dev/videoX) 和 Subdev 节点(/dev/v4l-subdevX)。它们的分工与协作关系如下:

  • Video 节点:面向应用层,负责图像数据的采集、输出和流 I/O 控制(如 VIDIOC_QBUF、VIDIOC_DQBUF)。它是数据流的终点或起点。
  • Subdev 节点:面向驱动层,负责硬件单元的配置与控制(如格式协商、曝光设置、增益调节)。它不直接处理图像数据流。
类比理解:Video 节点相当于“摄像头应用”的接口,而 Subdev 节点相当于“摄像头硬件寄存器”的接口。应用通过 Video 节点获取图像,通过 Subdev 节点调节图像参数。

下图展示了典型的 Sensor + ISP 系统中 Subdev 与 Video 节点的协作关系:

Subdev 与 Video 节点协作关系图 Sensor 硬件 (图像传感器) ISP 硬件 (图像处理器) CSI 接收器 (MIPI 接口) Sensor Subdev (/dev/v4l-subdev0) ISP Subdev (/dev/v4l-subdev1) CSI Subdev (/dev/v4l-subdev2) Video 节点 Subdev:控制接口(格式/曝光/增益) Video 节点:数据流接口(QBUF/DQBUF)
常见误区:不要将 Subdev 节点视为 Video 节点的替代品。Video 节点处理图像数据流,Subdev 节点处理控制参数流。两者通过 media controller 框架协同工作,缺一不可。

2.2 Subdev 内部架构:ops、pad、state

每个 V4L2 subdev 驱动内部由三个核心组件构成:操作集(ops)、端口(pad) 和 状态(state)。理解这三者的关系是编写 subdev 驱动的基础。

2.2.1 操作集(ops)

struct v4l2_subdev_ops 是 subdev 驱动的核心接口集合,它定义了 subdev 对外提供的所有操作能力。ops 按功能分为多个子集:

ops 子集 主要回调函数 功能描述
core init(), s_power(), ioctl() 核心控制:初始化、电源管理、自定义 ioctl
video s_stream(), g_frame_interval() 视频流控制:启动/停止流、帧率查询
pad enum_mbus_code(), set_fmt(), get_fmt() Pad 操作:枚举格式、设置/获取格式
sensor g_skip_frames(), s_exposure() Sensor 专用:跳过帧数、曝光控制
ctrl s_ctrl(), g_ctrl() 控制接口:V4L2 控制类的 get/set

驱动注册时,通过 v4l2_subdev_init() 或 v4l2_i2c_subdev_init() 将 ops 绑定到 subdev 实例:

static const struct v4l2_subdev_ops sensor_subdev_ops = {
    .core  = &sensor_core_ops,
    .video = &sensor_video_ops,
    .pad   = &sensor_pad_ops,
};

static int sensor_probe(struct i2c_client *client) {
    struct v4l2_subdev *sd;
    // ... 分配 subdev 内存 ...
    v4l2_i2c_subdev_init(sd, client, &sensor_subdev_ops);
    // ... 其他初始化 ...
}

2.2.2 端口(pad)

Pad 是 subdev 的数据输入/输出端口,用于描述 subdev 与其他 subdev 或 video 节点之间的连接关系。每个 subdev 可以有多个 pad,分为 输入 pad 和 输出 pad。

  • 输入 pad(SINK):接收来自上游 subdev 的图像数据。
  • 输出 pad(SOURCE):向下游 subdev 或 video 节点发送图像数据。

Pad 的定义通过 struct v4l2_subdev_pad_ops 中的回调实现,核心是格式协商:

static struct v4l2_subdev_pad_ops sensor_pad_ops = {
    .enum_mbus_code = sensor_enum_mbus_code,  // 枚举支持的媒体总线格式
    .get_fmt        = sensor_get_fmt,         // 获取当前格式
    .set_fmt        = sensor_set_fmt,         // 设置格式(核心协商函数)
};

// pad 数组定义(Sensor 通常只有 1 个输出 pad)
static const struct media_entity_operations sensor_media_ops = {
    .link_validate = v4l2_subdev_link_validate,
};

static struct v4l2_subdev_pad_config sensor_pad_cfg[] = {
    {
        .pad = 0,  // 输出 pad
        .flags = MEDIA_PAD_FL_SOURCE,
    },
};
格式协商流程:当应用层通过 Video 节点设置格式时,V4L2 框架会沿着 media controller 链路,依次调用每个 subdev 的 set_fmt() 回调,确保整条链路的格式一致。

2.2.3 状态(state)

Subdev 的状态管理通过 struct v4l2_subdev_state 实现,它保存了 subdev 当前的工作参数,包括:

  • Pad 格式:每个 pad 当前的媒体总线格式(struct v4l2_mbus_framefmt)。
  • 裁剪/缩放:每个 pad 的裁剪矩形(struct v4l2_rect)和缩放比例。
  • 帧间隔:当前帧率设置。
  • 其他自定义状态:驱动开发者可扩展的私有数据。

状态管理在 v4l2_subdev_state 中通过 try 和 active 两套机制实现:

状态类型 用途 生命周期
try 状态 格式协商时的试探性设置,用于验证格式是否支持 仅在 set_fmt() 调用期间有效
active 状态 当前生效的工作参数,驱动实际硬件时使用 持续有效,直到被新的设置覆盖

下图展示了 subdev 内部 ops、pad、state 三者的关系:

Subdev 内部架构:ops、pad、state 关系图 struct v4l2_subdev v4l2_subdev_ops ├─ core_ops (init, s_power) ├─ video_ops (s_stream) ├─ pad_ops (set_fmt, get_fmt) └─ ctrl_ops (s_ctrl, g_ctrl) Pad 数组 [SINK, SOURCE] State (try / active) 应用层 ioctl 硬件寄存器 ops 定义 subdev 的能力,pad 定义连接拓扑,state 保存当前工作参数。三者共同构成 subdev 的完整行为模型。

2.2.4 三者的协同工作流程

以 Sensor subdev 设置分辨率为例,展示 ops、pad、state 的协同过程:

  1. 应用层调用:通过 Video 节点或 subdev 节点调用 VIDIOC_SUBDEV_S_FMT。
  2. 框架路由:V4L2 框架找到目标 subdev,调用其 pad_ops->set_fmt() 回调。
  3. 格式协商:set_fmt() 内部检查请求的格式是否在 enum_mbus_code() 支持的列表中。
  4. 状态更新:如果格式有效,更新 state 中的 pad 格式信息(try 或 active)。
  5. 硬件配置:通过 core_ops->s_power() 或自定义函数,将格式写入 Sensor 硬件寄存器。
  6. 链路传播:如果格式变化影响下游,通过 media controller 通知相邻 subdev 重新协商。
关键设计原则:Subdev 驱动应尽量将硬件无关的逻辑(格式验证、状态管理)与硬件相关的操作(寄存器读写)分离。ops 中的回调负责调度,state 负责记录,pad 负责连接——各司其职,降低耦合。

2.3 本章小结

核心概念 一句话总结
Subdev 作用 将 Sensor、ISP 等硬件单元抽象为可独立控制的驱动模块,实现管线化协作
Subdev vs Video 节点 Subdev 负责控制参数,Video 节点负责图像数据流,两者通过 media controller 协同
ops 定义 subdev 的操作能力(core/video/pad/ctrl 等子集)
pad 定义 subdev 的数据输入/输出端口,支持格式协商与链路连接
state 保存 subdev 当前工作参数(格式、裁剪、帧率等),支持 try/active 双状态机制