RK平台V4L2框架:从数据结构到ISP子设备注册

好,我们直接进入正题。V4L2 是 Linux 下视频设备驱动的标准框架,瑞芯微平台也不例外。你想想看,一个摄像头从物理 sensor 到应用层看到图像,中间要经过多少层?V4L2 就是把这些层次串起来的骨架。

我个人习惯把 V4L2 理解成「视频设备的文件系统」——每个视频设备都是一个文件,你用 open/read/write/ioctl 就能操作它。但 sensor 驱动和普通字符驱动不一样,它要处理格式、帧率、缓冲区、控制参数……所以 V4L2 定义了一套核心数据结构来承载这些信息。

V4L2 核心数据结构

先看几个你一定会打交道的结构体。我当年刚接触时,光看头文件就晕了,后来发现其实就几个关键角色。

数据结构 作用 我常用的场景
struct video_device V4L2 设备核心,注册到系统后生成 /dev/videoX 分配、设置 fops、注册到 v4l2_device
struct v4l2_device 顶层 V4L2 设备实例,管理所有子设备 作为 parent 挂载 video_device 和 subdev
struct v4l2_subdev 子设备抽象,sensor、ISP、MIPI 桥片都用它 sensor 驱动中必须实现的核心结构
struct v4l2_pix_format 图像格式描述:宽、高、像素格式、bytesperline 设置 sensor 输出分辨率时必填
struct v4l2_buffer 缓冲区描述,用于视频数据流传输 DMA 缓冲管理,mmap 映射

关键点:在 RK 平台上,sensor 驱动通常只注册 v4l2_subdev,而 video_device 由 ISP 驱动创建。sensor 本身不直接暴露 /dev/videoX 给用户空间。

举个例子,sensor 驱动中你会看到这样的初始化:

static 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
    sd = devm_kzalloc(dev, sizeof(*sd), GFP_KERNEL);
    v4l2_i2c_subdev_init(sd, client, &sensor_subdev_ops);
    // 设置 pads
    sd->entity.function = MEDIA_ENT_F_CAM_SENSOR;
    // 注册异步 subdev
    v4l2_async_register_subdev(sd);
}

嗯,这里要注意 v4l2_async_register_subdev。RK 平台用异步注册机制,sensor 驱动先注册,等 ISP 驱动就绪后再完成绑定。我曾经踩过一个坑——sensor 的 i2c 地址写错了,导致异步注册一直不触发回调,查了两天才发现是硬件问题。

Media Controller 框架

V4L2 本身是扁平的,但现代 ISP pipeline 需要描述复杂的拓扑关系——sensor 连到 MIPI 桥片,桥片连到 ISP,ISP 输出到 DMA 引擎。Media Controller 就是干这个的。

说白了,Media Controller 把每个硬件模块抽象成一个 media_entity,实体之间通过 media_pad 连接,连接关系用 media_link 表示。用户空间通过 /dev/media0 就能看到整个 pipeline 的拓扑图。

我的建议:调试 pipeline 连接问题时,先用 media-ctl -p 打印拓扑,看看 entity 和 link 状态。这个命令比看代码快多了。

RK ISP pipeline 的典型拓扑长这样:

# media-ctl -p 的典型输出(简化)
- entity 1: rkisp-isp (1 pad)
    pad0: Source [fmt:YUYV/1920x1080]
- entity 2: rkisp-resizer (1 pad)
    pad0: Sink
    pad1: Source
- entity 3: m00_f_ov5640 1-003c (sensor)
    pad0: Source [fmt:SBGGR10/2592x1944]

你看到没?sensor 的 pad0 输出 raw 数据,ISP 的 pad0 接收,然后 resizer 做缩放。这些连接关系就是通过 Media Controller 的 link 来描述的。

RK ISP 子设备注册流程

这部分是实战重点。RK ISP 驱动(通常是 rkisp1 或 rkisp)在 probe 时会做以下几件事:

  1. 创建 v4l2_device:作为整个 ISP 设备的顶层容器。
  2. 注册 media_device:初始化 media controller 拓扑。
  3. 创建子设备实体:ISP 内部有多个子模块——ISP 主处理、resizer、统计信息采集等,每个都是一个 v4l2_subdev。
  4. 设置 pads 和 links:把 ISP 内部子模块连接起来,并暴露外部 pad 给 sensor。
  5. 注册 video_device:创建 /dev/videoX 节点,用于用户空间获取图像数据。
  6. 等待 sensor 异步绑定:sensor 注册后,ISP 驱动通过异步通知完成 link 建立。

注意:RK 平台的 ISP 驱动通常把 sensor 绑定在 rkisp-isp-subdev 的 sink pad 上。如果绑定失败,检查 sensor 的 mbus_code 是否与 ISP 支持的格式匹配。

下面这张图展示了整个注册流程和拓扑关系:

RK ISP 子设备注册与拓扑流程图 RK ISP 子设备注册与 Media Controller 拓扑 ISP 驱动注册流程 1. v4l2_device_register() 2. media_device_register() 3. 创建 ISP 子设备实体 4. 设置 pads & links 5. video_device_register() 6. 等待 sensor 异步绑定 Media Controller 拓扑 Sensor Subdev m00_f_ov5640 S ISP Main Subdev rkisp-isp S S Resizer Subdev rkisp-resizer S S Video Device /dev/video0 说明:sensor 通过异步注册绑定到 ISP ISP 内部子设备通过 media link 连接

从图上你能看到,sensor 的 source pad 连接到 ISP 的 sink pad,ISP 处理后传给 resizer,resizer 再输出到 video device。这个拓扑就是通过 Media Controller 的 media_create_pad_link() 建立的。

调试技巧:如果图像出不来,先用 media-ctl -e "rkisp-isp" 查看 ISP 实体状态,再用 v4l2-ctl --list-formats 确认格式协商是否成功。我遇到过好几次格式不匹配导致 pipeline 无法启动的情况。

最后总结一下,V4L2 框架在 RK 平台上的核心思路就是:sensor 作为 subdev 异步注册,ISP 驱动作为主控创建 media topology,用户空间通过 media-ctl 配置链路,通过 v4l2-ctl 或应用程序获取图像。理解了这个流程,你就能在遇到问题时快速定位是 sensor 驱动的问题、ISP 配置的问题,还是链路连接的问题。