4. V4L2 Subdev ioctl 框架:v4l2_subdev_core_ops、v4l2_subdev_video_ops、v4l2_subdev_pad_ops

V4L2 (Video for Linux 2) 子设备驱动通过三个核心操作结构体来组织 ioctl 命令的实现:v4l2_subdev_core_ops、v4l2_subdev_video_ops 和 v4l2_subdev_pad_ops。这三个结构体分别管理通用控制、视频流控制和 pad(管脚/接口)级配置,构成了 subdev ioctl 命令分发的骨架。

核心概念: V4L2 框架将 subdev 的 ioctl 命令按功能域划分为三类操作集。应用层或桥驱动通过统一的 video_device 节点下发命令,V4L2 核心根据命令类型自动路由到对应的 ops 回调函数。
V4L2 Subdev ioctl 框架总览 V4L2 Subdev ioctl 框架总览 应用层 / 桥驱动 V4L2 核心 ioctl 分发 v4l2_subdev_core_ops 通用控制 / 电源 / 时钟 v4l2_subdev_video_ops 视频流 / 帧率 / 格式 v4l2_subdev_pad_ops Pad 格式 / 裁剪 / 选择器 核心回调示例 s_power / g_ext_ctrls ioctl / log_status 视频回调示例 s_stream / g_frame_interval s_parm / g_parm Pad 回调示例 get_fmt / set_fmt enum_mbus_code / get_selection ioctl 命令路由规则 VIDIOC_SUBDEV_* → pad_ops | VIDIOC_STREAMON/OFF → video_ops | 通用命令 → core_ops

4.1 v4l2_subdev_core_ops — 核心操作集

v4l2_subdev_core_ops 是 subdev 最基础的操作结构体,负责设备无关的通用控制。它包含电源管理、扩展控制、日志、ioctl 直通等回调函数。

回调函数 功能描述 对应 ioctl / 使用场景
s_power 控制子设备电源状态(on/off) 系统休眠 / 流开启前上电
ioctl 直通 ioctl 命令,处理私有命令 厂商自定义控制命令
g_ext_ctrls 批量获取扩展控制值 VIDIOC_G_EXT_CTRLS
s_ext_ctrls 批量设置扩展控制值 VIDIOC_S_EXT_CTRLS
log_status 打印子设备当前状态到内核日志 调试 / VIDIOC_LOG_STATUS
init 子设备初始化回调 设备探测阶段调用
load_fw 加载固件到子设备 需要固件的传感器/编解码器
实践建议: 大多数 sensor 驱动至少需要实现 s_power 和 ioctl 回调。s_power 通常配合 PM runtime 框架使用,在流开启前上电、流关闭后下电,以降低功耗。

4.2 v4l2_subdev_video_ops — 视频操作集

v4l2_subdev_video_ops 专注于视频流相关的控制,包括流启停、帧间隔、帧参数等。这是 sensor 和 decoder 驱动最常实现的操作集之一。

回调函数 功能描述 对应 ioctl / 使用场景
s_stream 开启或停止视频流(enable/disable) VIDIOC_STREAMON / STREAMOFF
g_frame_interval 获取当前帧间隔(帧率倒数) VIDIOC_SUBDEV_G_FRAME_INTERVAL
s_frame_interval 设置帧间隔 VIDIOC_SUBDEV_S_FRAME_INTERVAL
g_parm 获取视频流参数(帧率、模式等) VIDIOC_G_PARM
s_parm 设置视频流参数 VIDIOC_S_PARM
g_std 获取当前视频标准(如 NTSC/PAL) 模拟视频 decoder
s_std 设置视频标准 模拟视频 decoder
注意: s_stream 是 video_ops 中最重要的回调。在开启流时,驱动应配置硬件寄存器启动 MIPI 或并行接口输出;在关闭流时,应停止输出并进入低功耗模式。务必实现流状态机保护,防止重复调用导致硬件异常。
s_stream 调用流程 s_stream 回调调用流程 桥驱动调用 s_stream(1) V4L2 核心路由到 video_ops Subdev 驱动 s_stream 实现 配置 MIPI/CSI 接口 使能 sensor 输出 更新流状态标志

4.3 v4l2_subdev_pad_ops — Pad 操作集

v4l2_subdev_pad_ops 是 V4L2 子设备框架中最重要的操作集之一,负责 pad(管脚/接口)级别的媒体总线格式、裁剪、选择器配置。它直接对应 VIDIOC_SUBDEV_* 系列 ioctl 命令。

回调函数 功能描述 对应 ioctl 命令
enum_mbus_code 枚举 pad 支持的媒体总线格式码 VIDIOC_SUBDEV_ENUM_MBUS_CODE
get_fmt 获取 pad 当前格式 VIDIOC_SUBDEV_G_FMT
set_fmt 设置 pad 格式(包括分辨率、像素编码) VIDIOC_SUBDEV_S_FMT
enum_frame_size 枚举 pad 支持的帧尺寸 VIDIOC_SUBDEV_ENUM_FRAME_SIZE
enum_frame_interval 枚举 pad 支持的帧间隔 VIDIOC_SUBDEV_ENUM_FRAME_INTERVAL
get_selection 获取 pad 的裁剪/组合选择器 VIDIOC_SUBDEV_G_SELECTION
set_selection 设置 pad 的裁剪/组合选择器 VIDIOC_SUBDEV_S_SELECTION
link_validate 验证两个 pad 之间的链路配置是否有效 媒体框架链路验证
关键设计: pad_ops 中的 set_fmt 回调通常实现“协商-调整”模式:驱动接收应用层请求的格式,根据硬件能力调整到最接近的可用格式,并将实际配置写回 format 结构体。应用层读取返回的格式即可知道硬件实际采用的配置。

4.4 三种 ops 的协作关系

在实际驱动中,三个操作集并非孤立工作,而是通过 subdev 核心结构体 v4l2_subdev 组织在一起。桥驱动或应用层通过不同的 ioctl 命令触发对应的 ops 回调。

// subdev 驱动中注册三个 ops 的典型代码
static const struct v4l2_subdev_core_ops my_core_ops = {
    .s_power    = my_sensor_s_power,
    .ioctl      = my_sensor_ioctl,
    .log_status = my_sensor_log_status,
};

static const struct v4l2_subdev_video_ops my_video_ops = {
    .s_stream          = my_sensor_s_stream,
    .g_frame_interval  = my_sensor_g_frame_interval,
    .s_frame_interval  = my_sensor_s_frame_interval,
};

static const struct v4l2_subdev_pad_ops my_pad_ops = {
    .enum_mbus_code = my_sensor_enum_mbus_code,
    .get_fmt        = my_sensor_get_fmt,
    .set_fmt        = my_sensor_set_fmt,
    .get_selection  = my_sensor_get_selection,
};

// 组合成 v4l2_subdev_ops
static const struct v4l2_subdev_ops my_sensor_ops = {
    .core  = &my_core_ops,
    .video = &my_video_ops,
    .pad   = &my_pad_ops,
};
最佳实践: 在实现 set_fmt 时,建议同时更新内部状态并调用 get_selection 的默认值,确保裁剪窗口与格式分辨率保持一致。对于不支持裁剪的 sensor,get_selection 应返回与格式相同的尺寸。

4.5 ioctl 命令到 ops 的路由规则

V4L2 核心根据 ioctl 命令编号自动将调用路由到对应的 ops 结构体。理解路由规则对调试和开发至关重要。

ioctl 命令范围 路由目标 ops 典型命令示例
VIDIOC_SUBDEV_* (0x5600 ~ 0x56FF) pad_ops VIDIOC_SUBDEV_G_FMT, VIDIOC_SUBDEV_S_FMT
VIDIOC_STREAMON/OFF video_ops → s_stream VIDIOC_STREAMON, VIDIOC_STREAMOFF
VIDIOC_G/S_PARM video_ops → g_parm / s_parm VIDIOC_G_PARM, VIDIOC_S_PARM
VIDIOC_G/S_EXT_CTRLS core_ops → g_ext_ctrls / s_ext_ctrls VIDIOC_G_EXT_CTRLS, VIDIOC_S_EXT_CTRLS
VIDIOC_LOG_STATUS core_ops → log_status VIDIOC_LOG_STATUS
私有 / 厂商自定义命令 core_ops → ioctl 自定义命令码
常见错误: 初学者容易将 pad_ops 和 video_ops 混淆。记住:pad_ops 处理的是“格式/裁剪/选择器”等静态配置,而 video_ops 处理的是“流启停/帧率”等运行时控制。如果 sensor 不支持流控制(如纯静态配置设备),video_ops 可以为 NULL。
ioctl 路由决策树 ioctl 命令路由决策树 接收 ioctl 命令 VIDIOC_SUBDEV_* ? 格式/裁剪/选择器 VIDIOC_STREAMON/OFF ? 流控制 其他 / 私有命令 ? 控制/日志/自定义 → pad_ops 回调 → video_ops 回调 → core_ops 回调 路由优先级与回退机制 V4L2 核心优先匹配精确命令 → 若对应 ops 为 NULL 则返回 -ENOIOCTLCMD 私有命令通过 core_ops.ioctl 直通,驱动自行解析 cmd 和 arg

4.6 驱动实现要点总结

  • 最小实现集: 大多数 sensor 驱动至少需要实现 core_ops.s_power、video_ops.s_stream、pad_ops.set_fmt 和 pad_ops.get_fmt。
  • 格式协商: set_fmt 必须实现“请求-调整-返回”模式,不能直接拒绝应用层请求。
  • 流状态机: 使用原子变量或互斥锁保护 s_stream 的调用状态,防止重复开启/关闭。
  • 错误处理: 所有 ops 回调应返回标准的 Linux 错误码(如 -EINVAL、-ENOMEM、-EIO),避免返回未定义值。
  • 调试支持: 实现 log_status 回调,输出当前格式、流状态、电源状态等信息,便于调试。
总结: v4l2_subdev_core_ops、v4l2_subdev_video_ops 和 v4l2_subdev_pad_ops 构成了 V4L2 子设备 ioctl 命令分发的三大支柱。理解每个 ops 的职责边界和路由规则,是编写正确、高效 subdev 驱动的基础。在下一节中,我们将深入分析每个 ops 中关键回调的实现细节和常见陷阱。