4、Sensor驱动框架:Linux V4L2框架剖析、Video设备节点、Subdev架构、Media Controller拓扑

做嵌入式视觉Sensor驱动,说白了就是跟Linux内核里的V4L2框架打交道。V4L2全称Video for Linux 2,是Linux下视频设备驱动的标准。我刚开始接触时也觉得这东西挺绕,什么Video节点、Subdev、Media Controller,一堆概念。但摸熟了就会发现,它其实就三板斧。

今天咱们把这框架拆开揉碎了讲。你跟着我走一遍,保证能理清。

4.1 V4L2框架:三层的世界

V4L2框架从上到下分三层。我习惯这么记:

  • 最上层:字符设备驱动层。用户空间看到的/dev/video0、/dev/video1就在这层。
  • 中间层:V4L2核心层。内核帮你把通用的ioctl、mmap、poll这些操作都实现了。你不需要自己写。
  • 最下层:Sensor驱动层。这才是咱们真正要干活的地方。初始化Sensor、配寄存器、调格式,全在这。

核心思路:V4L2框架把通用逻辑和硬件逻辑分开了。你只管写最下面那层,上面两层内核帮你搞定。

我当年第一次调Sensor时,以为要从头写一个完整的字符设备驱动。后来发现V4L2已经封装好了,我只需要填充几个回调函数就行。嗯,省了不少事。

4.2 Video设备节点:用户空间的入口

用户程序怎么跟Sensor通信?就是通过/dev/videoX这个节点。它本质上是一个字符设备,主设备号81,次设备号从0开始。

常见的操作就这几个:

  • open():打开设备
  • close():关闭设备
  • ioctl():控制设备,比如设置分辨率、帧率、曝光时间
  • mmap():把内核的buffer映射到用户空间,直接读图像数据
  • poll():等待新的一帧数据到来

举个例子,用户程序设置分辨率的流程:

int fd = open("/dev/video0", O_RDWR);
struct v4l2_format fmt = {0};
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = 1920;
fmt.fmt.pix.height = 1080;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
ioctl(fd, VIDIOC_S_FMT, &fmt);
close(fd);

你看,用户程序根本不知道底层是什么Sensor。它只认/dev/video0。这就是V4L2的抽象能力。

小技巧:调试时可以用v4l2-ctl工具直接操作Video节点,不用写代码就能验证Sensor是否正常工作。比如v4l2-ctl --list-formats查看支持的格式。

4.3 Subdev架构:把Sensor拆开看

一个摄像头模组里不只有Sensor芯片。还有ISP、MIPI桥接芯片、镜头控制器等等。如果把它们全塞到一个驱动里,代码会乱成一锅粥。

V4L2的Subdev架构就是来解决这个问题的。它把每个硬件单元抽象成一个Subdev,每个Subdev只负责自己的事。

举个例子,一个典型的摄像头Pipeline:

  • Sensor Subdev:负责曝光、增益、帧率
  • MIPI CSI-2 Subdev:负责MIPI协议解析
  • ISP Subdev:负责图像处理
  • Video Subdev:负责输出给Video节点

每个Subdev都有自己的struct v4l2_subdev结构体,里面包含pad、ops、ctrl等成员。它们通过pad连接起来,形成一条数据流。

注意:Subdev的初始化顺序很重要。必须先初始化Sensor,再初始化MIPI桥接,最后初始化ISP。顺序错了,数据流就断了。我曾经因为初始化顺序搞反,调了整整两天。

4.4 Media Controller拓扑:画一张连接图

有了多个Subdev,它们之间怎么连接?Media Controller就是干这个的。它用图论的方式描述设备内部的连接关系。

核心概念就三个:

  • Entity:一个硬件单元,对应一个Subdev
  • Pad:Entity上的接口,分输入和输出
  • Link:连接两个Pad的边

我习惯把Media Controller想象成一张地铁图。每个站点是一个Entity,站台上的出入口是Pad,地铁线路是Link。数据就像乘客,从起点站坐到终点站。

下面这张图展示了典型的Sensor数据流拓扑:

Media Controller 拓扑示例 Sensor pad:0 (输出) MIPI CSI-2 pad:0 (输入) pad:1 (输出) ISP pad:0 (输入) pad:1 (输出) Video Node pad:0 (输入) 说明: 数据从Sensor输出,经过MIPI CSI-2桥接,进入ISP处理,最终送到Video节点。 每个Entity通过Link连接,形成完整的Pipeline。

在代码里,Media Controller的拓扑是通过media_entity和media_link结构体描述的。驱动初始化时会调用media_create_pad_link()来建立连接。

// 伪代码示例:建立Sensor到MIPI的Link
media_create_pad_link(
    &sensor_subdev->entity,   // 源Entity
    0,                         // 源Pad编号
    &mipi_subdev->entity,      // 目标Entity
    0,                         // 目标Pad编号
    0                          // 标志位
);

调试技巧:用media-ctl工具可以查看和修改拓扑。比如media-ctl -p打印整个Pipeline,media-ctl -l列出所有Link。这在排查数据流不通时特别好用。

4.5 三者如何协同工作

Video节点、Subdev、Media Controller不是孤立的。它们配合起来才能让Sensor正常工作。

我总结了一个典型的数据流启动流程:

  1. Media Controller初始化:建立所有Entity之间的Link,形成完整的Pipeline。
  2. Subdev初始化:每个Subdev配置自己的硬件,比如Sensor设置默认分辨率、MIPI设置lane数。
  3. Video节点注册:创建/dev/video0,绑定到Pipeline的末端。
  4. 用户程序调用:通过Video节点下发格式设置、启动流、获取帧数据。

你看,Media Controller负责拓扑,Subdev负责硬件,Video节点负责用户接口。各司其职,互不干扰。

避坑指南:我曾经遇到一个问题,Sensor能出图但颜色不对。查了半天发现是Media Controller的Link配置错了,数据从Sensor出来后没经过ISP,直接送到了Video节点。所以,出问题时先检查拓扑对不对。

4.6 总结一下

V4L2框架的核心就这三样东西:

组件 作用 用户空间接口
Video设备节点 提供/dev/videoX,用户程序直接操作 open/close/ioctl/mmap/poll
Subdev架构 抽象每个硬件单元,独立管理 通过Video节点间接访问
Media Controller 描述设备内部拓扑,管理数据流 /dev/media0,通过media-ctl工具操作

搞懂了这三者的关系,Sensor驱动框架你就拿下了。后面咱们写具体驱动时,你会发现所有代码都是围绕这三个概念展开的。