1. 前言:导演的工作
在我们的“电影剧组”比喻中,subdev 是具体的工作人员,而 Media 子系统是“导演”。导演的核心工作有两件:
- 绘制分镜图 (构建拓扑): 导演需要了解所有工作人员的站位和他们之间的连接关系,形成一张精确的“分镜图”。这个过程发生在内核驱动加载时。
- 指挥拍摄 (响应应用): 导演需要提供一个接口,让“制片人”(APP)可以查看分镜图,并下达指令(比如“激活A机位到B录机的线路”)。这个过程发生在 APP 运行时。
这一讲,我们就来详细拆解导演的这两件核心工作。
2. 内核注册过程 —— 绘制“分镜图”
“分镜图”(硬件拓扑)的绘制不是由“导演”一个人完成的,而是由所有“工作人员(subdev)”和“导演助理”(桥接驱动)协同完成的。整个过程层次分明,可以概括为两个层次,四个步骤。
2.1 两个层次
- 自我描述 (
media_entity): 每个subdev驱动负责描述自己。知道自己有几个“输入/输出端口”(pads),但它不知道会和谁连接。 - 建立联系 (
link): 更高一层的、统筹全局的桥接驱动扮演了“导演助理”的角色。它最清楚硬件的物理连接,因此由它来负责在各个media_entity之间建立“连接线”(links)。
2.2 瑞芯微(Rockchip)平台实例解析:谁是“桥接驱动”?
在这个平台上,rkisp1 驱动(Rockchip ISP1 Driver)就是一个典型的桥接驱动。它同时控制着 SoC 内部的 ISP、MIPI CSI-2 Host 等多个硬件模块。摄像头 Sensor (例如 Sony IMX378) 通常是通过 I2C 和 MIPI 总线连接到 RK3399 上的。
rkisp1驱动知道自己有一个 MIPI CSI-2 接收器,可以接收外部 Sensor 的数据。imx378驱动(一个subdev驱动)知道自己是一个图像传感器,可以输出 MIPI 信号。
但是,imx378 驱动并不知道自己连接到了 rkisp1。建立它们之间联系的责任,就落在了 rkisp1 这个桥接驱动身上。它会解析设备树(Device Tree),在设备树中会明确描述 imx378 的 MIPI 输出端口连接到了 rkisp1 的 MIPI 输入端口。rkisp1 驱动读取到这个信息后,就会调用 media_create_pad_link 来完成这个连接。
2.3 四个步骤
现在我们结合瑞芯微的例子,再来看这四个步骤,就会豁然开朗:
- 第 1 步:描述自己 (Sensor 驱动, 如
imx378.c)- 做什么?
imx378驱动在初始化时,会调用media_entity_pads_init,告诉“导演”:“我是一个 Sensor,我有一个 MIPI 输出端口(Pad)”。
- 做什么?
- 第 2 步:注册自己 (Sensor 驱动, 如
imx378.c)- 做什么?
imx378驱动调用v4l2_device_register_subdev,将自己的media_entity注册到rkisp1创建的media_device中。相当于向导演报到:“摄影师 IMX378 加入剧组了”。
- 做什么?
- 第 3 步:和别人建立联系 (桥接驱动, 如
rkisp1.c)- 做什么? “导演助理”
rkisp1登场。它在自己的probe函数中,解析设备树,得知imx378连接到了自己身上。于是,它调用media_create_pad_link,在imx378的输出pad和rkisp1的输入pad之间创建一条link。 - 意义: 这条
link就在内核中建立了从 Sensor 到 SoC 的数据流逻辑通路。
- 做什么? “导演助理”
- 第 4 步:暴露给 APP 使用 (桥接驱动, 如
rkisp1.c)- 做什么? 当
rkisp1把所有连接到它上面的subdev(可能还有其他 ISP 相关的subdev) 都注册并建立好link后,一张完整的“分镜图”就绘制好了。此时,rkisp1驱动调用media_device_register,创建/dev/media0设备节点,将这套完整的拓扑结构暴露给用户空间。
- 做什么? 当
至此,整个 Media 子系统的内核注册过程完成。一个包含所有硬件模块及其连接关系的、可供 APP 查询和配置的 media_device 就绪。
3. APP 使用 Media 子系统 —— 与“导演”对话
APP 作为“制片人”,它不关心具体硬件细节,只关心如何通过“导演”来配置出想要的拍摄效果。APP 与 media 子系统的交互非常简洁,除了 open,几乎只涉及 5 个核心 ioctl 命令。
3.1 ioctl 调用核心机制
当 APP 对 /dev/media0 执行 ioctl 时,内核流程如下:
- APP: 调用
ioctl(fd, cmd, arg)。 - 内核 VFS: 调用
media_devnode_fops中的media_ioctl函数。 media_ioctl: 这个函数是一个通用分发器。它不实现具体功能,而是通过一个名为ioctl_info的静态数组来查找cmd对应的处理函数。ioctl_info数组: 这个数组的每一项都定义了一个ioctl命令的处理流程:cmd: 命令号。fn: 实际执行工作的函数指针。arg_from_user/arg_to_user: 用于在用户空间和内核空间之间安全拷贝数据的函数指针。
现在我们来逐一解析这 5 个核心 ioctl。
-
MEDIA_IOC_DEVICE_INFO-
作用: 获取设备的基本信息,比如驱动名称、设备型号等。
-
APP 操作:
struct media_device_info info; ioctl(fd, MEDIA_IOC_DEVICE_INFO, &info); -
内核执行:
ioctl_info数组将此命令分发给media_device_get_info函数,该函数会从media_device结构体中复制信息到用户传入的info结构体中。
-
-
MEDIA_IOC_ENUM_ENTITIES- 作用: 枚举设备中所有的
entity(工作人员)。 - APP 操作: APP 通常在一个
for循环中调用此ioctl。通过在media_entity_desc结构体的id字段中设置MEDIA_ENT_ID_FLAG_NEXT标志,APP 可以告诉内核:“请给我下一个entity的信息”。 - 内核执行: 分发给
media_device_enum_entities函数。该函数根据 APP 传入的id找到对应的entity,然后将其信息(名字、类型、pad 数量等)返回给 APP。
- 作用: 枚举设备中所有的
-
MEDIA_IOC_ENUM_LINKS- 作用: 枚举指定
entity的所有link(连接线)。 - APP 操作: 与枚举
entity类似,APP 提供一个entity的 ID,然后内核返回该entity的所有pad信息和link信息。 - 内核执行: 分发给
media_device_enum_links函数。该函数首先找到指定的entity,然后遍历其pads数组和links链表,将信息复制给用户空间。
- 作用: 枚举指定
-
MEDIA_IOC_SETUP_LINK(最核心的控制接口)-
作用: 激活 (enable) 或 闲置 (disable) 一条
link。这是配置pipeline的关键。 -
APP 操作:
struct media_link_desc link_desc; // ... 填充 link_desc 的 source 和 sink pad 信息 ... link_desc.flags = MEDIA_LNK_FL_ENABLED; // 激活 ioctl(fd, MEDIA_IOC_SETUP_LINK, &link_desc); -
内核执行: 分发给
media_device_setup_link函数。该函数会:- 找到
link_desc描述的link。 - 检查该
link是否是MEDIA_LNK_FL_IMMUTABLE(不可变的)。 - 如果可变,就根据 APP 传入的
flags来更新link的状态位。 - 最关键的一步: 调用
link两端subdev的.pad->link_setup回调函数,通知驱动程序:“这条线路的状态变了,请进行相应的硬件配置!”
- 找到
-
-
MEDIA_IOC_G_TOPOLOGY- 作用: 一次性获取整个拓扑图的快照信息,包括所有
entities,pads,links的数量和大小。 - APP 操作: APP 先调用一次此
ioctl获取拓扑信息的大小,然后分配足够大的内存,再次调用以获取全部数据。 - 内核执行: 分发给
media_device_get_topology函数,该函数负责将整个拓扑图的统计信息返回给 APP。
- 作用: 一次性获取整个拓扑图的快照信息,包括所有
总结
- 内核注册 (绘制蓝图): 这是一个自下而上的协作过程。
Subdev驱动负责描述自己并注册自己,而桥接驱动则负责建立连接并最终暴露给应用。 - APP 使用 (按图索骥): 这是一个自上而下的控制过程。APP 首先通过
ENUM系列ioctl读取完整的拓扑图,然后根据需求,通过SETUP_LINKioctl配置出一条有效的数据pipeline。
155

被折叠的 条评论
为什么被折叠?



