2. Subdev 核心数据结构:struct v4l2_subdev 结构体详解、ops 回调函数集、内部状态管理

在 V4L2 框架中,struct v4l2_subdev 是每一个子设备驱动实例的核心抽象。它承载了设备标识、操作函数指针、内部状态、控制句柄以及媒体拓扑连接信息。理解该结构体的布局与生命周期,是编写正确、可维护的 subdev 驱动的前提。

核心要点:struct v4l2_subdev 是 subdev 驱动的“身份证”与“控制中心”,所有与内核 V4L2 框架的交互都通过该结构体及其内嵌的 ops 回调集完成。

2.1 struct v4l2_subdev 结构体全景

该结构体定义在 include/media/v4l2-subdev.h 中,是 subdev 驱动的核心数据结构。下面列出其关键成员及其作用:

成员名称 类型 作用描述
name char[32] 子设备名称,用于调试和用户空间识别
entity struct media_entity 媒体实体,用于构建媒体拓扑图(MC 框架)
ctrl_handler struct v4l2_ctrl_handler * 控制句柄,管理所有 V4L2 控制(如亮度、对比度)
internal_ops const struct v4l2_subdev_internal_ops * 内部操作集,用于注册/注销/打开/关闭等框架级回调
ops const struct v4l2_subdev_ops * 核心操作集,包含视频、音频、传感器、pad 等回调
dev_priv void * 指向驱动私有数据的指针,通常指向自定义结构体
host_priv void * 指向宿主(bridge)私有数据的指针,由 bridge 驱动设置
flags u32 标志位,如 V4L2_SUBDEV_FL_HAS_DEVNODE 等
owner struct module * 所属模块指针,用于引用计数
list struct list_head 用于挂接到全局 subdev 列表的链表节点
工程实践提示:在驱动 probe 函数中,通常使用 v4l2_subdev_init() 或 v4l2_i2c_subdev_init() 初始化该结构体,然后填充 ops、ctrl_handler 和 dev_priv。不要直接 memset 整个结构体,以免破坏内核链表或引用计数。
struct v4l2_subdev 核心成员关系 struct v4l2_subdev name / flags / owner entity (media) ctrl_handler dev_priv / host_priv ops : const struct v4l2_subdev_ops * 工程要点 • dev_priv 指向驱动私有数据,通常包含 i2c_client、电源状态、寄存器缓存等 • ctrl_handler 必须在注册 subdev 之前初始化,否则控制操作会失败 • entity 用于媒体控制器拓扑,非 MC 驱动可忽略,但建议初始化

2.2 ops 回调函数集:v4l2_subdev_ops 体系

struct v4l2_subdev_ops 是 subdev 驱动的“功能接口表”,它聚合了多个子操作集,每个子集对应一类硬件功能。驱动开发者根据设备能力选择实现相应的回调。

2.2.1 操作集层级结构

子操作集 类型 典型回调 适用场景
core struct v4l2_subdev_core_ops s_power, ioctl, log_status 电源管理、通用 ioctl、调试
video struct v4l2_subdev_video_ops s_stream, g_frame_interval 视频流启停、帧率控制
pad struct v4l2_subdev_pad_ops get_fmt, set_fmt, enum_mbus_code 媒体总线格式协商(MC 核心)
sensor struct v4l2_subdev_sensor_ops g_skip_frames, s_stream 图像传感器专用控制
audio struct v4l2_subdev_audio_ops s_clock_freq, s_routing 音频编解码器控制
ir struct v4l2_subdev_ir_ops rx_read, tx_write 红外遥控接收/发送
常见错误:驱动开发者经常只实现 video 和 pad 操作集,却忘记实现 core 操作集中的 s_power 回调。这会导致 bridge 驱动在调用 v4l2_subdev_call(sd, core, s_power, 1) 时返回 -ENOIOCTLCMD,从而无法正确管理设备电源状态。

2.2.2 核心回调详解:s_stream 与 set_fmt

在视频流管道中,s_stream 和 set_fmt 是最常被调用的两个回调,它们直接决定了数据流能否正确建立。

// s_stream 回调典型实现模式
static int sensor_s_stream(struct v4l2_subdev *sd, int enable)
{
    struct sensor_priv *priv = v4l2_get_subdevdata(sd);
    int ret;

    if (enable) {
        // 1. 配置硬件寄存器启动输出
        ret = sensor_write_reg(priv, REG_STREAM, 1);
        if (ret)
            return ret;
        // 2. 设置 MIPI CSI-2 或并行接口时序
        ret = sensor_set_interface(priv);
    } else {
        // 停止输出,进入低功耗模式
        ret = sensor_write_reg(priv, REG_STREAM, 0);
        // 可选:关闭时钟、降低功耗
    }

    priv->streaming = enable ? 1 : 0;
    return ret;
}

// set_fmt 回调典型实现模式
static int sensor_set_fmt(struct v4l2_subdev *sd,
                          struct v4l2_subdev_state *state,
                          struct v4l2_subdev_format *format)
{
    struct sensor_priv *priv = v4l2_get_subdevdata(sd);
    struct v4l2_mbus_framefmt *fmt = &format->format;

    // 1. 验证并调整格式参数(对齐到硬件支持范围)
    fmt->width = clamp(fmt->width, MIN_WIDTH, MAX_WIDTH);
    fmt->height = clamp(fmt->height, MIN_HEIGHT, MAX_HEIGHT);
    fmt->code = MEDIA_BUS_FMT_SBGGR10_1X10; // 固定或协商

    // 2. 更新内部状态(不立即写硬件,s_stream 时生效)
    priv->fmt = *fmt;

    // 3. 如果是 TRY 操作,只返回调整后的格式
    if (format->which == V4L2_SUBDEV_FORMAT_TRY)
        return 0;

    // 4. ACTIVE 操作:更新硬件寄存器
    return sensor_update_parm(priv);
}
ops 回调函数调用流程 Bridge 驱动 v4l2_subdev_call() 查找 sd->ops->video 查找 s_stream 函数指针 调用回调函数 返回 errno 或 0

2.3 内部状态管理:state 与 active_state

从 Linux 内核 5.x 开始,V4L2 subdev 框架引入了 struct v4l2_subdev_state 用于管理 subdev 的 pad 配置状态。该机制解决了传统驱动中格式参数分散在私有结构体中的问题,提供了统一的 try/active 状态管理。

2.3.1 v4l2_subdev_state 结构

struct v4l2_subdev_state {
    struct v4l2_subdev *sd;          // 所属 subdev
    struct v4l2_mbus_framefmt *pads; // pad 格式数组
    unsigned int *streams;           // 流标志位(多流场景)
    struct v4l2_rect *crops;         // 裁剪矩形数组
    struct v4l2_rect *comps;         // 合成矩形数组
    struct v4l2_subdev_stream_configs *stream_configs;
    struct list_head list;           // 用于挂接到 subdev 的 state 链表
};
状态类型 存储位置 生命周期 使用场景
TRY 状态 栈上或临时分配 单次 ioctl 调用期间 格式协商、预览效果
ACTIVE 状态 sd->active_state 驱动运行期间持续存在 当前生效的硬件配置
状态管理最佳实践:在 set_fmt 回调中,始终先操作 state 中的 pads 数组,再决定是否写入硬件。对于 TRY 操作,只修改 state 不触碰硬件;对于 ACTIVE 操作,修改 state 后同步硬件寄存器。这种“先软件后硬件”的模式可以有效避免硬件状态不一致。

2.3.2 状态初始化与切换流程

// 驱动 probe 时初始化 active_state
static int sensor_probe(struct i2c_client *client)
{
    struct sensor_priv *priv;
    struct v4l2_subdev *sd;
    int ret;

    priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL);
    sd = &priv->sd;

    v4l2_i2c_subdev_init(sd, client, &sensor_ops);

    // 分配并初始化 active_state
    ret = v4l2_subdev_init_finalize(sd);
    if (ret)
        return ret;

    // 设置默认格式
    sd->active_state->pads[0] = default_fmt;

    return 0;
}

// 状态切换示例:s_stream 时应用 active_state
static int sensor_s_stream(struct v4l2_subdev *sd, int enable)
{
    struct sensor_priv *priv = v4l2_get_subdevdata(sd);
    struct v4l2_mbus_framefmt *fmt;

    if (enable) {
        // 从 active_state 读取当前格式
        fmt = v4l2_subdev_get_pad_format(sd, sd->active_state, 0);
        // 根据 fmt 配置硬件
        sensor_apply_format(priv, fmt);
        // 启动流
        sensor_start_hw(priv);
    } else {
        sensor_stop_hw(priv);
    }

    return 0;
}
subdev 内部状态管理流程 用户空间 ioctl V4L2 框架层 which? TRY: 修改 state 临时副本 ACTIVE: 修改 active_state 同步硬件寄存器

2.4 工程案例:IMX219 传感器 subdev 状态管理

以 Sony IMX219 传感器驱动为例,展示 struct v4l2_subdev 及其状态管理的实际应用:

struct imx219_priv {
    struct v4l2_subdev sd;
    struct media_pad pad;
    struct v4l2_ctrl_handler ctrl_handler;
    struct v4l2_mbus_framefmt fmt;
    struct regmap *regmap;
    struct clk *xvclk;
    struct gpio_desc *reset_gpio;
    bool streaming;
    struct mutex lock; // 保护并发访问
};

// 初始化 subdev 核心结构
static int imx219_probe(struct i2c_client *client)
{
    struct imx219_priv *priv;
    struct v4l2_subdev *sd;
    int ret;

    priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL);
    sd = &priv->sd;

    mutex_init(&priv->lock);

    // 1. 初始化 subdev
    v4l2_i2c_subdev_init(sd, client, &imx219_ops);
    sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;

    // 2. 初始化控制句柄
    v4l2_ctrl_handler_init(&priv->ctrl_handler, 5);
    v4l2_ctrl_new_std(&priv->ctrl_handler, &imx219_ctrl_ops,
                      V4L2_CID_GAIN, 0, 255, 1, 0);
    sd->ctrl_handler = &priv->ctrl_handler;

    // 3. 初始化 active_state
    ret = v4l2_subdev_init_finalize(sd);
    if (ret)
        goto error_free_ctrl;

    // 4. 设置默认格式
    priv->fmt = imx219_default_fmt;
    *v4l2_subdev_get_pad_format(sd, sd->active_state, 0) = imx219_default_fmt;

    // 5. 注册 subdev
    ret = v4l2_async_register_subdev(sd);
    if (ret)
        goto error_free_state;

    return 0;

error_free_state:
    v4l2_subdev_cleanup(sd);
error_free_ctrl:
    v4l2_ctrl_handler_free(&priv->ctrl_handler);
    return ret;
}
调试技巧:使用 v4l2_subdev_call(sd, core, log_status, NULL) 可以触发 subdev 驱动打印当前内部状态。在 log_status 回调中输出 active_state 的格式、streaming 标志、控制值等信息,是调试格式协商和流控制问题的有效手段。

2.5 本章小结

  • struct v4l2_subdev 是 subdev 驱动的核心,包含标识、操作集、控制句柄和私有数据指针。
  • ops 回调函数集 采用分层设计,core/video/pad/sensor 等子集各司其职,s_stream 和 set_fmt 是最常用的回调。
  • 内部状态管理 通过 v4l2_subdev_state 实现 try/active 分离,避免格式协商时直接操作硬件。
  • 工程实践 中应始终使用 v4l2_subdev_init_finalize() 初始化状态,并在所有回调中优先读取 active_state。