前言:
这篇文章是基于1126B平台上的imx335摄像头+v4l2驱动框架写的。
一、为什么学习 V4L2 要先建立对象模型
很多驱动工程师第一次阅读 V4L2 时,会直接打开 imx335.c 或 RKCIF 的 capture.c。结果通常是:函数很多、回调很多、结构体互相嵌套,读了几千行代码,却仍然回答不了下面几个最基本的问题:
- 为什么 Sensor 驱动通常没有注册
/dev/videoX? - 为什么
media-ctl -p 能看到 Sensor、CSI、Capture,而 ls /dev/video* 只看到部分节点? video_device 已经表示视频设备,为什么里面还要嵌入 media_entity?v4l2_device 和 media_device 名字如此相近,它们是不是同一个层次?QBUF 以后,缓冲区究竟属于 VB2、平台驱动,还是 DMA 硬件?- 同一个
/dev/videoX 被两个进程打开时,哪些状态共享,哪些状态独立?
这些问题之所以容易混乱,是因为 V4L2 不是一个单独的结构体,也不是一套简单的 file_operations。它实际上由几套互相协作的通用框架组成:
- V4L2 Core:负责视频字符设备、文件上下文、IOCTL 分发、Subdev 和 Controls 等通用机制。
- Media Controller:负责描述一个复杂媒体设备内部的 entity、pad、link 和 pipeline。
- Videobuf2(VB2):负责用户缓冲区的申请、映射、排队、流状态和完成通知。
- 平台驱动:把标准回调连接到实际 Sensor、CSI、CIF、DMA、中断和寄存器。
如果一开始没有把对象放到正确层次,阅读真实平台代码时就会把 Rockchip 私有实现误认为 V4L2 通用规则,也会把数据流、控制流、对象所有权和设备节点混在一起。
1.1 一条最重要的阅读原则
阅读任何一个对象时,都固定回答五个问题:
| |
|---|
| |
| 父驱动、Sensor 驱动、Capture 驱动,还是每次 open() 动态创建? |
| 调用了哪个 Core 辅助函数?注册到了哪套框架? |
| 注销、引用计数、close() 和设备移除分别负责哪部分? |
| 对应 /dev/videoX、/dev/mediaX、/dev/v4l-subdevX,还是只能被间接操作? |
这五个问题比背字段更重要。字段会随内核版本调整,但对象职责和边界相对稳定。
二、先看全局:V4L2 驱动处在什么架构位置
下面这张图把用户态节点、通用框架对象、RV1126B 驱动对象以及实际像素数据流放到同一张图中。
图 1:用户态、V4L2 Core、Media Controller、VB2 与 RV1126B 驱动的总体关系
2.1 用户态看到的是“入口”,不是完整硬件
用户态通常能看到三类节点:
| | |
|---|
/dev/videoX | 查询能力、协商内存格式、申请缓冲区、启动采集、取帧 | video_device |
/dev/mediaX | 枚举 entity、pad、link,配置或检查媒体拓扑 | media_device |
/dev/v4l-subdevX | 对某些 Subdev 直接做 pad 格式、选择区域、事件等操作 | v4l2_subdev,其可选 devnode 由一个 video_device 承载 |
这里必须建立一个认识:设备节点只是某个对象暴露给用户态的访问入口,不等于硬件实体本身。
例如,IMX335 是一个真实 I2C Sensor,但它通常不会自己提供采集缓冲区,所以它的核心身份是 v4l2_subdev。真正承接 REQBUFS/QBUF/STREAMON/DQBUF 的节点往往由 RKCIF、ISP mainpath 或其他 Capture 驱动注册。
2.2 数据流和控制流是两条不同的路
在相机系统中,最容易混淆的就是数据流和控制流。
数据流大致是:
IMX335 像素输出
-> CSI-2 / D-PHY 接收
-> RKCIF Capture
-> DMA 写入 VB2 缓冲区
-> 应用 DQBUF 取走一帧
控制流大致是:
应用 ioctl / media ioctl
-> 字符设备入口
-> V4L2 Core 或 Media Controller
-> 标准 ops 回调
-> IMX335 / CSI / RKCIF 驱动函数
-> 更新软件状态或操作硬件
像素不会“经过 video_ioctl2() 流动”;video_ioctl2() 只是控制命令的分发入口。反过来,media_link 也不是物理数据缓存,它只是描述实体之间的连接关系。
三、把 RV1126B 相机链路翻译成 V4L2 对象
在 RV1126B 的典型采集链路里,可以把硬件和软件对象对应成下面的模型。
图 2:IMX335、CSI/DPHY、RKCIF、VB2 与应用之间的数据流和控制流
3.1 IMX335:数据源,但不是内存采集节点
压缩代码包中的 media/i2c/imx335.c 定义了 struct imx335。其中直接内嵌:
structimx335 {
structv4l2_subdevsubdev;
structmedia_padpad;
structv4l2_ctrl_handlerctrl_handler;
/* 电源、时钟、模式、控制和状态字段 */
};
这段结构说明了三件事:
- IMX335 用
v4l2_subdev 对接 V4L2 子设备框架。 - 它有控制处理器,用于曝光、增益、VBLANK 等属性。
它没有内嵌 video_device,也没有 vb2_queue。因此,不能因为 Sensor 是“摄像头硬件”,就推断它一定对应 /dev/videoX。
3.2 CSI / D-PHY:媒体流水线中的桥接组件
CSI 接收和 D-PHY 通常也以 Subdev 形式存在。它们往往有:
- 一个或多个
SINK pad,接收上游 Sensor 数据; - 一个或多个
SOURCE pad,向下游 CIF 或 ISP 发送数据; pad_ops,用于媒体总线格式枚举、获取和设置;
它们参与媒体拓扑和流控制,但通常也不负责给用户态管理一组视频帧缓冲区。
3.3 RKCIF:真正承接采集缓冲区的 Video Device
RKCIF 的 dev.h 中,struct rkcif_vdev_node 直接内嵌:
structrkcif_vdev_node {
structvb2_queuebuf_queue;
structmutexvlock;
structvideo_devicevdev;
structmedia_padpad;
};
而 struct rkcif_stream 又内嵌 rkcif_vdev_node,并增加硬件队列、帧状态、中断相关字段。这个结构非常典型:
rkcif_stream 保存 Rockchip 硬件特有状态。
这就是“通用框架对象嵌入平台私有结构体”的基本写法。
四、V4L2 对象模型总图
先把所有核心对象放在一张图里,再逐一拆解。
图 3:V4L2、Media Controller 与 VB2 核心对象关系
可以把这些对象分成四类:
| | |
|---|
| v4l2_device | |
| video_device | |
| v4l2_subdev | 描述 Sensor、CSI、解码器、ISP 子模块等 |
| media_entity/pad/link/pipeline | |
接下来阅读每个对象时,不只看“它是什么”,还要看它在源码中如何被初始化、注册、引用和释放。
五、v4l2_device:V4L2 子对象的聚合器
5.1 它解决什么问题
一个相机媒体设备里可能包含多个 Sensor、CSI 接收器、解码器、视频节点和控制处理器。V4L2 Core 需要一个对象把这些 V4L2 组件组织起来,这就是 struct v4l2_device。
它通常承担:
它不是字符设备,也不会因为调用 v4l2_device_register() 就自动出现 /dev/videoX。
5.2 从 Core 实现看初始化动作
media/v4l2-core/v4l2-device.c 中的 v4l2_device_register() 做的事情很克制:
INIT_LIST_HEAD(&v4l2_dev->subdevs);
spin_lock_init(&v4l2_dev->lock);
v4l2_prio_init(&v4l2_dev->prio);
kref_init(&v4l2_dev->ref);
v4l2_dev->dev = dev;
它初始化管理结构、建立与父设备的关系,但没有创建视频节点。这里可以得到一个重要结论:
v4l2_device_register() 注册的是一个 V4L2 聚合对象,不是注册 /dev/videoX。
5.3 Subdev 注册到哪里
v4l2_device_register_subdev() 的核心动作包括:
- 检查
v4l2_device、v4l2_subdev 和名称是否合法; - 设置
sd->v4l2_dev = v4l2_dev; - 如果关联了
media_device,把 sd->entity 注册进媒体图; - 调用 Subdev 内部的
registered 回调; - 把 Subdev 加入
v4l2_dev->subdevs 链表。
因此,注册一个 Subdev 并不是简单“挂到链表”这么一件事,它同时可能跨越 V4L2 Controls 和 Media Controller 两套框架。
5.4 它和 media_device 有什么区别
| |
|---|
v4l2_device | 这个父设备管理了哪些 V4L2 子对象和 Subdev? |
media_device | 这些 entity、pad、link 在拓扑上如何连接? |
二者经常同时存在,但职责不同。常见写法是:
structmy_device {
structmedia_devicemdev;
structv4l2_devicev4l2_dev;
};
my->v4l2_dev.mdev = &my->mdev;
VIMC 的 struct vimc_device 正是这种写法,它同时内嵌 media_device mdev 和 v4l2_device v4l2_dev。
六、media_device:媒体拓扑的根对象
6.1 它解决什么问题
现代相机并不是“一个节点对应一个硬件”。一个媒体设备内部可能有多个 Sensor、多个 CSI 端口、复用器、ISP、缩放器和多个输出节点。Media Controller 需要一个根对象维护整张图,这就是 struct media_device。
media_device_init() 会初始化 entity、interface、pad、link 等列表以及图锁。media_device_register() 最终注册媒体 devnode,用户态通常通过 /dev/mediaX 访问拓扑。
6.2 media_device 不是像素采集节点
通过 /dev/mediaX,用户态主要执行的是:
它不承接 VIDIOC_REQBUFS,也不直接返回一帧图像。/dev/mediaX 管“图”,/dev/videoX 管“视频 I/O”。
6.3 为什么 media-ctl -p 很重要
media-ctl -p 输出的不是 Linux 设备树本身,也不是驱动源码结构体的原样打印,而是内核最终注册成功的媒体对象图。它能帮助回答:
- 哪个 entity 对应
/dev/videoX 或 /dev/v4l-subdevX。
所以,在真实板端排查问题时,应当从实际媒体图反查源码对象,而不是固定假设 media0、video0 一定属于某个模块。
七、video_device:用户态视频节点的核心描述对象
7.1 它解决什么问题
struct video_device 把一个 V4L2 功能节点注册成字符设备,并告诉 V4L2 Core:
常见关键字段如下:
| |
|---|
fops | open/release/unlocked_ioctl/poll/mmap/read |
ioctl_ops | VIDIOC_QUERYCAP |
v4l2_dev | |
queue | 指向 vb2_queue,让通用 fops/ioctl helper 找到缓冲区队列 |
lock | |
release | video_device |
entity | 内嵌的 media_entity,用于放进媒体拓扑 |
device_caps | 节点能力,例如 capture、streaming、mplane |
7.2 从注册函数看 /dev/videoX 如何产生
media/v4l2-core/v4l2-dev.c 中的 __video_register_device() 是关键入口。它大致完成:
- 校验
release、v4l2_dev、能力等必填项; - 按节点类型选择名称范围,例如
video 或 v4l-subdev; - 分配 minor、node number 和 index;
- 建立通用
cdev,其底层使用 V4L2 Core 的统一文件操作; - 如果启用 Media Controller,把内嵌 entity 和 devnode interface 注册进媒体图;
驱动通常调用的是包装宏或函数:
video_register_device(vdev, VFL_TYPE_VIDEO, -1);
第三个参数传 -1 表示让内核动态分配节点编号。因此,不应把 /dev/videoX 的 X 写死在调试脚本和设计文档中。
7.3 fops 和 ioctl_ops 不是一回事
fops 回答的是“VFS 调到哪个入口”,例如:
staticconststructv4l2_file_operationsrkcif_fops = {
.open = rkcif_fh_open,
.release = rkcif_fh_release,
.unlocked_ioctl = video_ioctl2,
.poll = vb2_fop_poll,
.mmap = vb2_fop_mmap,
};
ioctl_ops 回答的是“一个具体 V4L2 命令最终交给哪个处理函数”,例如:
staticconststructv4l2_ioctl_opsrkcif_v4l2_ioctl_ops = {
.vidioc_reqbufs = vb2_ioctl_reqbufs,
.vidioc_qbuf = vb2_ioctl_qbuf,
.vidioc_dqbuf = rkcif_dqbuf,
.vidioc_streamon = vb2_ioctl_streamon,
.vidioc_streamoff = vb2_ioctl_streamoff,
.vidioc_s_fmt_vid_cap_mplane = rkcif_s_fmt_vid_cap_mplane,
};
可以先形成下面这条概念链:
系统调用 ioctl()
-> VFS
-> video_device.fops->unlocked_ioctl
-> video_ioctl2()
-> video_device.ioctl_ops 中对应的 vidioc_xxx
-> VB2 helper 或平台驱动回调
7.4 两个叫 release 的回调不要混淆
这是阅读源码时非常常见的误区:
v4l2_file_operations.release:关闭一个文件描述符时调用,处理“这次 open 的上下文”。video_device.release:video_device 最终引用归零时调用,处理“节点对象本身的生命周期”。
RKCIF 使用:
vdev->release = video_device_release_empty;
因为 video_device 内嵌在 rkcif_stream 的对象里,不需要由 V4L2 Core 单独 kfree(vdev)。但文件关闭仍然走 rkcif_fh_release()。
八、v4l2_fh:一次 open() 对应一份文件上下文
8.1 为什么不能只靠 video_device
video_device 是节点级对象,多个进程打开同一个节点时会共享它。但每次 open() 仍然需要独立保存:
这就是 struct v4l2_fh 的职责。
8.2 标准辅助函数的生命周期
media/v4l2-core/v4l2-fh.c 中的典型路径是:
v4l2_fh_open()
-> kzalloc(struct v4l2_fh)
-> filp->private_data = fh
-> v4l2_fh_init(fh, vdev)
-> v4l2_fh_add(fh)
关闭时:
v4l2_fh_release()
-> v4l2_fh_del(fh)
-> v4l2_fh_exit(fh)
-> kfree(fh)
v4l2_fh_add() 会把当前文件上下文放进 vdev->fh_list。因此,节点对象可以知道有哪些打开实例,事件框架也能把事件分发给合适的文件上下文。
8.3 驱动可以扩展 v4l2_fh
更复杂的驱动常采用嵌入方式:
structmy_fh {
structv4l2_fhfh;
/* 每个文件独立的选择、格式、上下文或队列状态 */
};
此时 filp->private_data 指向 my_fh 中的 v4l2_fh 或整个扩展对象,驱动通过 container_of() 找回自己的上下文。
九、v4l2_subdev:媒体流水线内部组件的标准抽象
9.1 它解决什么问题
Sensor、CSI 接收器、解码器、桥接芯片、镜头执行器和 ISP 子模块,往往都是整个媒体流水线的一部分。它们需要:
但它们未必直接提供一组用户帧缓冲区。v4l2_subdev 就是为这种“流水线内部组件”设计的。
9.2 v4l2_subdev_init() 做了什么
media/v4l2-core/v4l2-subdev.c 中,v4l2_subdev_init() 会:
- 初始化内嵌
media_entity 的名称、对象类型和 function 基础状态。
它只是初始化对象,不等于完成异步绑定,更不等于媒体链路已经完整。
9.3 core_ops、video_ops、pad_ops 如何分工
| |
|---|
core_ops | |
video_ops | s_stream |
pad_ops | 枚举 mbus code、get/set format、selection、frame size 等 pad 操作 |
对于 Sensor 驱动,最值得先看的通常是 pad_ops 和 video_ops。它们解释“格式如何在媒体链路上表达”和“谁真正让 Sensor 开始输出”。
9.4 Subdev 也可能有设备节点
通常说“Subdev 不对应 /dev/videoX”,这句话是对的;但不能进一步错误推导为“Subdev 绝不对应任何字符设备节点”。
当 sd->flags 包含 V4L2_SUBDEV_FL_HAS_DEVNODE,V4L2 Core 可以为 Subdev 分配一个内部 video_device,注册为 VFL_TYPE_SUBDEV,从而形成 /dev/v4l-subdevX。
因此应当精确表述:
v4l2_subdev 通常不是视频帧采集节点;它可以选择性地暴露 /dev/v4l-subdevX,但这与 /dev/videoX 的 Capture 缓冲区语义不同。
十、media_entity、media_pad、media_link:把对象放进一张图
10.1 media_entity 是拓扑身份,不是设备节点
video_device 和 v4l2_subdev 都内嵌 media_entity,原因是:它们都需要出现在同一张媒体图中。
video_device.entity 表示一个用户 I/O 节点在拓扑中的位置;v4l2_subdev.entity 表示 Sensor、CSI、ISP 模块等内部实体;- 纯拓扑对象也可以是 entity,但未必有字符设备节点。
所以:
media_entity 解决“在图里是谁”
设备节点解决“用户态从哪里访问”
两者是不同维度。
10.2 media_pad 表示端口和方向
一个 entity 可以有多个 pad。每个 pad 至少要知道:
- 是
MEDIA_PAD_FL_SINK 还是 MEDIA_PAD_FL_SOURCE;
media_entity_pads_init() 会设置 entity->num_pads、entity->pads,并为每个 pad 写入 entity 和 index。如果 entity 已经注册到 media_device,该函数也会补建 pad 对应的图对象。
这解释了为什么不同驱动的初始化顺序不一定完全相同:VIMC 先初始化 pad 再注册 video node,而 RKCIF 的当前代码先 video_register_device(),随后 media_entity_pads_init()。不要把某一个驱动的顺序机械背成唯一规则,应当理解 Core 对两种时机的支持和各自错误回滚。
10.3 media_link 表示有方向的连接
media_create_pad_link() 会检查:
- source 和 sink entity 是否有效;
- source pad 是否带
MEDIA_PAD_FL_SOURCE; - sink pad 是否带
MEDIA_PAD_FL_SINK;
然后创建正向 link 和 backlink,并把它们挂到相关对象中。
一个典型链路可以表示为:
IMX335:0 [SOURCE]
-> rockchip-csi2-dphy:0 [SINK]
-> rockchip-csi2-dphy:1 [SOURCE]
-> rkcif:0 [SINK]
注意,pad 索引和 entity 名称必须以板端实际 media-ctl -p 输出为准。
十一、静态媒体图与运行时 media_pipeline
初学时常把 media_entity/link 和 media_pipeline 当成一回事。更准确的理解是:
- 静态 Media Graph:描述系统中有哪些 entity、pad、link,以及哪些 link 当前启用。
- 运行时 Media Pipeline:在一次流操作中,沿启用链路得到的有效 pad 集合,并记录该链路正在被哪个 pipeline 使用。
图 4:静态媒体拓扑与运行时 pipeline 的区别
media_pipeline_start() 会沿启用 link 遍历图,检查:
- entity 的
link_validate 是否通过; - 标记为
MUST_CONNECT 的 pad 是否真的连接;
如果同一个 pipeline 已经启动,会增加 start_count。这说明 media_pipeline 不只是“画图对象”,它还承载运行时一致性和引用式启动计数。
但仍要避免另一个误区:**media_pipeline_start() 主要建立和校验运行时图状态,它并不自动代替平台驱动完成 Sensor 寄存器开流、DMA 配置和中断使能。** 驱动仍需在自己的 start_streaming 或 pipeline 控制路径中调用各层硬件操作。
十二、vb2_queue:用户缓冲区与平台 DMA 之间的通用边界
12.1 它解决什么问题
视频采集的缓冲区生命周期非常复杂:用户申请、查询、映射、排队、驱动取走、硬件写入、完成通知、用户出队,然后再次排队。Videobuf2 把这套通用状态机抽象为 vb2_queue 和一组标准回调。
vb2_queue 主要保存:
| |
|---|
type | |
io_modes | 支持 MMAP、DMABUF、USERPTR 中的哪些 I/O 模式 |
ops | 平台驱动实现的 queue_setup/buf_queue/start_streaming/stop_streaming 等 |
mem_ops | 内存后端,例如 dma-contig、dma-sg、vmalloc |
drv_priv | |
buf_struct_size | |
lock | |
dev | |
min_buffers_needed | |
12.2 I/O 模式和内存后端不是一回事
这是必须早期分清的一组概念:
- MMAP、DMABUF、USERPTR 是用户如何把缓冲区交给驱动的 I/O 模式;
- dma-contig、dma-sg、vmalloc 是内核如何管理或映射这块内存的后端。
例如 VIMC 可以支持 VB2_MMAP | VB2_DMABUF,并根据模块参数选择 vb2_dma_contig_memops 或 vb2_vmalloc_memops。RKCIF 则根据硬件能力选择相应 mem_ops。
12.3 VB2 状态不等于硬件 DMA 状态
VB2 知道某个缓冲区处于已准备、已排队、活动、完成或错误等通用状态,但平台驱动还要维护:
RKCIF 的 rkcif_stream 中有自己的缓冲区链表、计数、tasklet 和帧状态,这些不是 VB2 自动替代的。平台驱动在一帧完成后调用:
vb2_set_plane_payload(&vb_done->vb2_buf, i, sizeimage);
vb2_buffer_done(&vb_done->vb2_buf, VB2_BUF_STATE_DONE);
vb2_buffer_done() 才把“硬件已经完成一帧”翻译成 VB2 可供用户 DQBUF 的完成状态。
12.4 本篇需要掌握到什么程度
此处先掌握对象边界,不急于把整个状态机追完:
video_device.queue -> vb2_queue
vb2_queue.ops -> 平台驱动的队列回调
平台驱动 -> 把 VB2 缓冲区交给硬件
硬件完成 -> 驱动调用 vb2_buffer_done()
完整的 REQBUFS -> QBUF -> STREAMON -> buffer_done -> DQBUF 调用链适合在对象模型稳定后单独深入。
十三、从 VIMC、IMX335、RKCIF 看同一套标准对象如何落地
图 5:VIMC 标准示例与 IMX335、RKCIF 真实驱动的对象映射
13.1 VIMC:最干净的标准参考
VIMC 的优点是没有真实寄存器、时钟、DMA 中断和 MIPI 错误干扰,可以直接观察标准对象如何组合。
struct vimc_device 内嵌:
structvimc_device {
structmedia_devicemdev;
structv4l2_devicev4l2_dev;
};
struct vimc_sensor_device 内嵌 v4l2_subdev、控制处理器和 source pad;struct vimc_capture_device 内嵌 video_device、vb2_queue 和 sink pad。
这恰好构成一套最小的“Sensor Subdev + Capture Video Device + Media Graph + VB2 Queue”模型。
VIMC Capture 的初始化顺序
vimc_capture_add() 中可以看到一条非常清晰的链:
分配 vimc_capture_device
-> 初始化 media_entity 和 SINK pad
-> 初始化 mutex
-> 填充 vb2_queue 字段
-> vb2_queue_init()
-> 填充 video_device 字段
-> video_register_device()
代表性代码:
q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
q->io_modes = VB2_MMAP | VB2_DMABUF;
q->drv_priv = vcapture;
q->ops = &vimc_capture_qops;
q->mem_ops = &vb2_vmalloc_memops; /* 或 dma-contig */
vb2_queue_init(q);
vdev->fops = &vimc_capture_fops;
vdev->ioctl_ops = &vimc_capture_ioctl_ops;
vdev->queue = q;
vdev->v4l2_dev = v4l2_dev;
video_register_device(vdev, VFL_TYPE_VIDEO, -1);
这里最值得学习的不是具体格式,而是对象先准备完整,再交给 Core 注册。
13.2 VIMC Sensor:没有 I2C 也能成为 Subdev
VIMC Sensor 没有真实 I2C 寄存器,但仍可以:
- 通过
v4l2_subdev_init() 初始化; - 提供
v4l2_subdev_ops 和 pad_ops; - 作为 entity 注册到
v4l2_device 和 media_device;
这证明 Subdev 是一种软件接口与拓扑角色,不要求对象一定对应一颗物理 I2C 芯片。
13.3 IMX335:把虚拟 Sensor 替换成真实硬件
IMX335 的 probe 主线可以抽象为:
设备资源和模式初始化
-> v4l2_i2c_subdev_init()
-> 初始化 controls
-> 设置 HAS_DEVNODE / HAS_EVENTS
-> entity.function = MEDIA_ENT_F_CAM_SENSOR
-> pad.flags = MEDIA_PAD_FL_SOURCE
-> media_entity_pads_init()
-> v4l2_async_register_subdev_sensor()
与 VIMC Sensor 相比,标准框架部分非常相似;新增的是电源、时钟、寄存器表、Runtime PM、真实 mode 和 I2C 访问。
阅读真实 Sensor 驱动时,应把两部分分开标记:
| |
|---|
v4l2_subdev、ops、pad、entity、controls、async 注册 | 寄存器表、供电时序、晶振、link frequency、stream 寄存器 |
13.4 RKCIF:把 VIMC Capture 映射到真实采集硬件
RKCIF 的 rkcif_register_stream_vdev() 主线是:
设置 video_device 名称和锁
-> 填充 ioctl_ops / fops / release / v4l2_dev / device_caps
-> 初始化 SINK pad
-> rkcif_init_vb2_queue()
-> vdev->queue = &node->buf_queue
-> video_register_device()
-> media_entity_pads_init()
-> 初始化帧完成 tasklet
其 rkcif_fops、rkcif_v4l2_ioctl_ops 和 rkcif_vb2_ops 分别对应三层接口:
| | |
|---|
rkcif_fops | | open、release、ioctl、poll、mmap |
rkcif_v4l2_ioctl_ops | | format、REQBUFS、QBUF、STREAMON/OFF |
rkcif_vb2_ops | | queue_setup、buf_queue、start/stop_streaming |
真实硬件完成一帧后,RKCIF 的 rkcif_vb_done_oneframe() 设置每个 plane 的 payload,并调用 vb2_buffer_done()。这正是“硬件完成状态交回通用缓冲区框架”的边界点。
十四、对象所有权、注册与用户态可见性总表
下面这张表是本篇最重要的输出之一。阅读任何新驱动时,都可以按同样方式填写。
| | | | |
|---|
v4l2_device | | v4l2_device_register() | 父驱动 remove,v4l2_device_unregister() | |
media_device | | media_device_init() | 父驱动 remove,unregister/cleanup | |
video_device | | 填字段后 video_register_device() | video_unregister_device() | 是,通常 /dev/videoX;也可承载其他 VFL 类型 |
v4l2_fh | | v4l2_fh_init() | 文件 release() 时 del/exit/free | |
v4l2_subdev | | v4l2_subdev_init() | | 默认否;带 devnode 标志时可有 /dev/v4l-subdevX |
media_entity | | media_entity_pads_init() | media_entity_cleanup() | 没有独立字符节点;可从 /dev/mediaX 枚举 |
media_pad | | media_entity_pads_init() | | |
media_link | | media_create_pad_link() | | |
media_pipeline | | media_pipeline_start() | media_pipeline_stop() | |
vb2_queue | | | | |
14.1 “创建、初始化、注册、暴露节点”是四件事
以 video_device 为例:
创建:驱动分配或在私有结构体中内嵌 video_device
初始化:填写 fops、ioctl_ops、queue、v4l2_dev、release 等字段
注册:调用 video_register_device()
暴露节点:Core 分配 minor 并形成 /dev/videoX
以 IMX335 Subdev 为例:
创建:struct imx335 中内嵌 v4l2_subdev
初始化:v4l2_i2c_subdev_init() + ops + pad + entity + controls
注册:v4l2_async_register_subdev_sensor()
完成绑定:父设备 async notifier bound/complete 后建立完整关系
可选节点:HAS_DEVNODE 时再注册 /dev/v4l-subdevX
把这四件事分开后,就不会再用“probe 成功”笼统代替所有状态。
十五、三个节点与七个对象:建立稳定的心智模型
可以用“园区”来类比:
| | |
|---|
media_device | | 维护所有 entity、pad、link 和接口关系 |
v4l2_device | | |
video_device | | 承接 open、ioctl、mmap、poll 等用户访问 |
v4l2_fh | | |
v4l2_subdev | | Sensor、CSI、解码器、ISP 子模块等内部组件 |
media_entity/pad/link | | |
vb2_queue | | |
类比只用于快速记忆,最终仍要回到结构体关系和注册函数。
十六、必须彻底区分的六组概念
16.1 video_device 与 v4l2_subdev
video_device 主要面向用户态 I/O 节点;v4l2_subdev 主要面向媒体流水线内部组件;- Subdev 可选地有
/dev/v4l-subdevX,但通常不管理视频帧缓冲区。
16.2 v4l2_device 与 media_device
- 调用
v4l2_device_register() 不会自动创建 /dev/mediaX。
16.3 media_entity 与设备节点
- 一个 entity 可以对应设备节点,也可以没有;
- 一个 Subdev 的 devnode 由额外的
video_device 承载,但 entity 仍属于原 Subdev。
16.4 media bus format 与 memory pixel format
- Sensor/CSI pad 上使用
MEDIA_BUS_FMT_*,描述在线路或 pad 间传输的样本组织; /dev/videoX 缓冲区常用 V4L2_PIX_FMT_*,描述写入内存的布局;MEDIA_BUS_FMT_SRGGB10_1X10 不等于 V4L2_PIX_FMT_SRGGB10,更不等于 NV12;
16.5 VB2 状态与 DMA 硬件状态
- 平台驱动维护硬件 slot、地址寄存器、中断和 active buffer;
- 只有驱动调用
vb2_buffer_done() 后,VB2 才知道硬件结果已经完成或出错。
16.6 静态 link 与运行时 stream
- pipeline start 表示本次流操作选择并占用了某条有效链;
- Sensor
s_stream(1)、CIF DMA 启动和中断使能仍需驱动明确执行;
十七、常见错误认识及修正
| |
|---|
“Sensor 是摄像头,所以一定注册 /dev/videoX。” | Sensor 通常是 Subdev;Capture 或 ISP 输出节点才管理帧缓冲区。 |
“看到 media entity,就一定能在 /dev 找到节点。” | entity 只表示拓扑身份,不保证有独立字符设备。 |
“v4l2_device_register() 就是注册视频节点。” | 它注册聚合对象;视频节点由 video_register_device() 注册。 |
| 带 HAS_DEVNODE 时可以暴露 /dev/v4l-subdevX。 |
“video_device.release 是关闭 fd 的回调。” | 关闭 fd 走 fops.release;vdev.release 管节点对象最终释放。 |
| QBUF 先进入 VB2/驱动队列,何时编程给硬件由平台驱动决定。 |
| “media link enabled 就能出图。” | 仍需格式一致、pipeline 校验、VB2 启动和硬件真正完成。 |
| MMAP 是用户 I/O 模式,dma-contig 是内存后端。 |
| 编号由注册顺序和动态分配决定,应从媒体图和 sysfs 反查。 |
| 大驱动混入硬件、中断、多通道和私有逻辑,先用 VIMC 建立标准框架更高效。 |
十八、按源码锚点阅读,而不是通读大文件
压缩代码包中的实现文件很多,最有效的方法是用结构体和注册函数作为锚点。
18.1 完整 SDK 中先打开这些头文件
include/media/v4l2-device.h
include/media/v4l2-dev.h
include/media/v4l2-fh.h
include/media/v4l2-subdev.h
include/media/media-device.h
include/media/media-entity.h
include/media/videobuf2-core.h
include/media/videobuf2-v4l2.h
每个头文件只做三件事:
- 记录公开的 init/register/unregister/helper 函数。
不要在第一次阅读时追每个宏和每个条件编译分支。
18.2 再看这些 Core 实现锚点
| | |
|---|
| media/v4l2-core/v4l2-device.c | v4l2_device_register()、v4l2_device_register_subdev() |
| media/v4l2-core/v4l2-dev.c | __video_register_device()、video_unregister_device() |
| media/v4l2-core/v4l2-fh.c | v4l2_fh_init()、v4l2_fh_open()、v4l2_fh_release() |
| media/v4l2-core/v4l2-subdev.c | v4l2_subdev_init() |
| media/mc/mc-device.c | media_device_init()、media_device_register_entity() |
| media/mc/mc-entity.c | media_entity_pads_init() |
| media/mc/mc-entity.c | media_pipeline_start() |
| media/common/videobuf2/ | vb2_queue_init() |
18.3 用命令快速建立源码地图
在解压后的 media 目录执行:
# 找对象定义或关键使用点
rg -n "struct v4l2_device|struct video_device|struct v4l2_subdev" .
rg -n "struct media_entity|struct media_pad|struct media_link" .
rg -n "struct vb2_queue" .
# 找注册和初始化入口
rg -n "v4l2_device_register\(" media/v4l2-core media/test-drivers/vimc
rg -n "video_register_device\(" media/test-drivers/vimc media/platform/rockchip/cif
rg -n "v4l2_subdev_init\(|v4l2_i2c_subdev_init\(" media/test-drivers/vimc media/i2c/imx335.c
rg -n "media_entity_pads_init\(|media_create_pad_link\(" media
rg -n "vb2_queue_init\(" media/test-drivers/vimc media/platform/rockchip/cif
# 找平台回调表
rg -n "v4l2_file_operations|v4l2_ioctl_ops|vb2_ops" media/test-drivers/vimc/vimc-capture.c media/platform/rockchip/cif/capture.c
一次只追一条链,控制在 5 到 10 个函数。每深入一层,就在笔记上标记:
[Core] 通用实现
[VB2] 缓冲区框架
[MC] Media Controller
[VIMC] 标准示例
[IMX335] Sensor 私有实现
[RKCIF] Rockchip 平台实现
十九、两天完成对象模型学习的实操安排
19.1 第一天:术语、头文件和对象关系
任务一:读两个本地文档
在完整 SDK 中阅读:
Documentation/driver-api/media/v4l2-intro.rst
Documentation/driver-api/media/v4l2-core.rst
只记录三类信息:对象、入口、状态。不要整段抄文档。
任务二:制作对象卡片
每个对象填写一张卡:
对象:video_device
解决的问题:把一个 V4L2 功能注册为用户态字符设备入口
谁创建:Capture/Output 驱动内嵌或分配
谁注册:驱动调用 video_register_device()
谁释放:video_unregister_device() + vdev->release
用户态可见:通常 /dev/videoX
关键关系:v4l2_dev、queue、entity、fops、ioctl_ops
至少完成:
v4l2_device
media_device
video_device
v4l2_fh
v4l2_subdev
media_entity / media_pad / media_link
media_pipeline
vb2_queue
任务三:手画对象关系图
不要照抄本文图片,而是关掉资料后自己画一遍,并保证图中至少包含:
video_device -> v4l2_device
video_device -> vb2_queue
video_device -> embedded media_entity
v4l2_subdev -> v4l2_device
v4l2_subdev -> embedded media_entity
media_entity -> pads -> links
v4l2_device -> optional media_device
v4l2_fh -> video_device
当天验收
能够不用看源码回答:
video_device 和 v4l2_subdev 的核心差异是什么?- 为什么
media_entity 不等于 /dev/videoX? v4l2_device 为什么不是媒体拓扑根对象?vb2_queue 为什么通常挂在 Capture Video Device 上,而不是 Sensor 上?
19.2 第二天:用 VIMC、IMX335、RKCIF 验证对象模型
任务一:VIMC 找标准模板
只看:
media/test-drivers/vimc/vimc-core.c
media/test-drivers/vimc/vimc-common.h
media/test-drivers/vimc/vimc-common.c
media/test-drivers/vimc/vimc-sensor.c
media/test-drivers/vimc/vimc-capture.c
输出两棵对象树:
vimc_device
├── media_device mdev
└── v4l2_device v4l2_dev
├── vimc_sensor_device
│ └── v4l2_subdev + media_entity + SOURCE pad
└── vimc_capture_device
└── video_device + media_entity + SINK pad + vb2_queue
以及:
用户态 /dev/videoX
-> video_device
-> fops / ioctl_ops
-> vb2_queue
-> vimc_capture_qops
任务二:IMX335 只验证 Sensor 身份
在 imx335.c 中只找:
struct imx335
v4l2_i2c_subdev_init
v4l2_ctrl_handler
MEDIA_PAD_FL_SOURCE
MEDIA_ENT_F_CAM_SENSOR
media_entity_pads_init
v4l2_async_register_subdev_sensor
暂时不要进入寄存器表和曝光计算。目标只是证明:真实 Sensor 如何复用 VIMC Sensor 所体现的标准 Subdev 模型。
任务三:RKCIF 只验证 Capture 身份
在 capture.c 和 dev.h 中只找:
struct rkcif_vdev_node
struct rkcif_stream
rkcif_fops
rkcif_v4l2_ioctl_ops
rkcif_vb2_ops
rkcif_init_vb2_queue
rkcif_register_stream_vdev
rkcif_vb_done_oneframe
暂时不进入 HDR、多通道、中断细节和寄存器配置。目标只是证明:RKCIF 如何把 video_device + vb2_queue + media_pad 嵌入平台 stream 对象。
当天验收
能够在不翻代码的情况下画出:
IMX335 Subdev -> CSI/DPHY Subdev -> RKCIF Video Device -> VB2 buffers
并在每个节点上标出:
二十、板端观察实验:用实际节点验证对象关系
这些实验不需要修改内核。
20.1 枚举媒体设备和视频设备
ls -l /dev/media* /dev/video* /dev/v4l-subdev* 2>/dev/null
不要根据编号猜用途。先逐个查询:
for n in /dev/video*; do
echo"===== $n ====="
v4l2-ctl -d "$n" --all 2>/dev/null | head -80
done
关注:
20.2 打印媒体拓扑
media-ctl -d /dev/mediaX -p
对每个 entity 做源码映射:
media-ctl | |
|---|
| struct imx335.subdev.entity |
| |
| rkcif_stream.vnode.vdev.entity |
| 对应驱动中的 media_pad 数组或单个 pad |
| |
/dev/videoX | video_device |
20.3 查询内存格式
v4l2-ctl -d /dev/videoX --list-formats-ext
这里看到的是 Capture 节点的内存像素格式,不是 Sensor pad 的 mbus code。
20.4 查询 Subdev pad 格式
实体名和 pad 编号以实际拓扑为准:
media-ctl -d /dev/mediaX --get-v4l2 'm00_b_imx335 3-001a:0'
这里看到的通常是 MEDIA_BUS_FMT_* 及宽高、field 等媒体总线信息。
20.5 建立“节点 - entity - 源码对象”对照表
建议在板端输出一张这样的表:
| | | | |
|---|
/dev/mediaX | | media_device | | |
/dev/v4l-subdevY | | v4l2_subdev | struct imx335 | |
/dev/videoZ | | video_device | struct rkcif_stream | rkcif_vdev_node.buf_queue |
真正掌握的标志不是记住 X/Y/Z,而是节点变化后仍能重新建立这张表。
二十一、源码阅读笔记模板
每次阅读只围绕一个具体问题,避免写成泛泛的“今天学习 V4L2”。
问题:为什么 IMX335 不注册 /dev/videoX?
入口:imx335_probe()
对象:struct imx335 / v4l2_subdev / media_entity / media_pad
关键函数:
1. v4l2_i2c_subdev_init
2. v4l2_ctrl_handler_init
3. media_entity_pads_init
4. v4l2_async_register_subdev_sensor
对象关系:
imx335.subdev.entity -> imx335.pad(SOURCE)
imx335.subdev -> async 注册 -> 父 v4l2_device
用户态可见性:
可选 /dev/v4l-subdevX;没有 video capture buffer queue
结论:
IMX335 是媒体流水线的数据源和控制对象,真正的帧缓冲区由下游 Capture 节点管理。
另一个示例:
问题:RKCIF 的 /dev/videoX 为什么能支持 mmap 和 STREAMON?
入口:rkcif_register_stream_vdev()
对象:rkcif_stream / rkcif_vdev_node / video_device / vb2_queue
关键函数:
1. rkcif_init_vb2_queue
2. vb2_queue_init
3. video_register_device
4. video_ioctl2
5. vb2_ioctl_streamon
边界:
V4L2 Core 负责命令分发;VB2 负责队列状态;RKCIF 负责硬件启动和帧完成。
二十二、自测题与参考答案
22.1 为什么 media_entity 不等于 /dev/videoX?
因为 media_entity 只负责在媒体拓扑中表示一个对象。Sensor、CSI、Video Device 都可以有 entity;其中只有注册了对应字符设备接口的对象才会形成 /dev/videoX、/dev/v4l-subdevX 或其他节点。
22.2 为什么 Sensor 通常用 v4l2_subdev?
因为 Sensor 主要负责模式、媒体总线格式、帧间隔、曝光增益和开关流,它通常不直接管理用户态视频缓冲区。Subdev 正好提供 pad、video、core 和 control 等内部组件接口。
22.3 为什么 Capture 驱动通常同时需要 video_device 和 vb2_queue?
video_device 提供用户态字符设备和 IOCTL 分发入口,vb2_queue 管理帧缓冲区生命周期。前者解决“从哪里访问”,后者解决“缓冲区如何流转”。
22.4 v4l2_device 和 media_device 可以只保留一个吗?
它们解决不同问题。简单驱动可能不启用完整 Media Controller,但在复杂相机拓扑中,v4l2_device 用于管理 V4L2 子对象,media_device 用于维护 entity/pad/link 图,二者通常协作而不是互相替代。
22.5 为什么同一个 video_ioctl2() 能服务不同平台驱动?
因为 video_ioctl2() 是通用命令分发器,它根据当前 video_device->ioctl_ops 查找对应回调。VIMC、RKCIF 和其他驱动只需填写不同的 v4l2_ioctl_ops,就能复用同一套 Core 入口。
22.6 v4l2_fh 与 video_device 哪个是每次打开独立的?
v4l2_fh 是每次 open() 独立的;video_device 是节点级共享对象。事件订阅等每文件状态放在 fh 中,节点能力和 ops 等公共信息放在 vdev 中。
22.7 link 已经 enabled,为什么仍可能 select timeout?
link enabled 只说明静态拓扑连接可用。还可能存在格式不一致、pipeline 校验失败、VB2 未开始、缓冲区未交给硬件、Sensor 未开流、CSI/CIF 无帧中断或硬件未调用 vb2_buffer_done() 等问题。
22.8 如何从一个 /dev/videoX 反查上游 Sensor?
先用 v4l2-ctl --all 确认驱动和节点类型,再用对应 /dev/mediaX 的 media-ctl -p 找到该 video entity,从它的 sink pad 沿 enabled link 逐级向上游追踪到 CSI 和 Sensor entity,最后用 entity 名称、总线信息和源码注册位置对应到私有结构体。
二十三、最终应能画出的两张图
23.1 对象关系图
media_device
└── media graph
├── v4l2_subdev.entity ── SOURCE pad ── link ── SINK pad
└── video_device.entity
v4l2_device
├── subdevs list -> v4l2_subdev
└── optional mdev -> media_device
video_device
├── fops / ioctl_ops
├── v4l2_dev -> v4l2_device
├── queue -> vb2_queue
├── fh_list -> many v4l2_fh
└── embedded media_entity
23.2 RV1126B 对象落地图
struct imx335
└── v4l2_subdev + media_entity + SOURCE pad
|
| media link / mbus format
v
CSI / D-PHY Subdev
└── SINK pad + SOURCE pad
|
v
struct rkcif_stream
└── struct rkcif_vdev_node
├── video_device + embedded media_entity + SINK pad
└── vb2_queue
|
v
DMA buffers -> DQBUF -> application
能够不看资料独立重画这两张图,并讲清每条线表示“指针关系、内嵌关系、注册关系还是数据连接”,说明对象模型已经建立起来。
二十四、总结:用一句话记住整个框架
v4l2_device 管 V4L2 子对象,media_device 管媒体图,video_device 管用户节点,v4l2_fh 管一次打开,v4l2_subdev 管流水线内部组件,media_entity/pad/link 管连接关系,media_pipeline 管一次运行时链路,vb2_queue 管帧缓冲区生命周期,而平台驱动负责把这些标准对象真正连接到 Sensor、CSI、CIF、DMA 和中断。
当这句话能够对应到 VIMC、IMX335 和 RKCIF 的具体结构体与注册函数时,再继续追 open、video_ioctl2、格式协商、异步绑定和完整 VB2 状态机,学习效率会明显高于直接通读大型平台驱动。
附录 A:源码入口速查
| |
|---|
| v4l2_device_register() |
| v4l2_device_register_subdev() |
| __video_register_device() / video_register_device() |
| v4l2_fh_open() |
| v4l2_subdev_init() |
| media_device_init() / media_device_register() |
| media_entity_pads_init() |
| media_create_pad_link() |
| media_pipeline_start() |
| vb2_queue_init() |
| vimc_capture_add() |
| vimc_ent_sd_register() |
| imx335_probe() / v4l2_async_register_subdev_sensor() |
| rkcif_register_stream_vdev() |
| rkcif_init_vb2_queue() |
| rkcif_vb_done_oneframe() |
附录 B:术语速记
| |
|---|
| 视频设备、Subdev、fh、ioctl、controls 等通用核心 |
| |
| |
| |
| source pad 到 sink pad 的有向连接 |
| 一次运行中沿 enabled link 形成的有效链路集合 |
| |
| |
| |
| |
| |
| |