3. Media Controller框架:核心对象与拓扑管理
Media Controller(媒体控制器)是Linux内核中用于管理复杂多媒体设备拓扑结构的框架。在Camera子系统中,一个ISP设备可能包含多个DMA引擎、MIPI接口、CSI接收器、图像处理模块等硬件单元,这些单元之间存在复杂的连接关系。Media Controller通过统一的抽象模型,将硬件拓扑暴露给用户空间,使得应用程序能够动态配置数据通路。
3.1 Media Controller的作用
Media Controller框架位于V4L2子系统和硬件驱动之间,主要承担以下职责:
| 作用维度 | 具体说明 | 典型场景 |
|---|---|---|
| 拓扑发现 | 将硬件内部模块(如MIPI接收器、ISP、DMA引擎)抽象为实体,暴露连接关系 | 用户空间通过MEDIA_IOC_ENUM_ENTITIES枚举所有硬件模块 |
| 数据通路配置 | 允许用户空间通过链接控制(enable/disable)选择数据流路径 | 选择CSI-2虚拟通道、切换ISP处理管线 |
| 资源隔离 | 通过链路状态管理,防止多个应用程序同时使用冲突的硬件资源 | 两个摄像头应用分别占用不同的MIPI通道 |
| 事件通知 | 支持异步事件(如链路断开、实体状态变化)的上报 | 热插拔传感器检测、链路错误恢复 |
3.2 核心对象模型
Media Controller框架定义了四个核心数据结构,它们共同构成了拓扑描述的基础。下面通过一个SVG图展示这些对象之间的关系:
3.2.1 media_device
struct media_device是Media Controller框架的顶层实例,代表一个完整的媒体设备。在Camera子系统中,通常一个SoC的ISP控制器对应一个media_device。
struct media_device {
struct device *dev; /* 关联的硬件设备 */
struct media_entity **entities; /* 实体数组 */
unsigned int num_entities; /* 实体数量 */
struct list_head pads; /* 所有pad的链表 */
struct list_head links; /* 所有link的链表 */
struct mutex graph_mutex; /* 拓扑图互斥锁 */
const struct media_device_ops *ops; /* 操作函数集 */
char model[32]; /* 设备模型名称 */
char serial[64]; /* 序列号 */
u32 hw_revision; /* 硬件版本 */
};
media_device_register()将设备注册到内核框架,用户空间通过/dev/mediaX设备节点访问。每个media_device拥有唯一的model和serial,用于用户空间识别。
3.2.2 media_entity
struct media_entity代表硬件中的一个功能模块,例如MIPI接收器、ISP核心、DMA引擎、视频解码器等。每个实体包含一个或多个pad,用于与其他实体连接。
| 字段 | 类型 | 说明 |
|---|---|---|
name |
char[32] |
实体名称,如"rkisp-isp"、"rkisp-resizer" |
type |
enum media_entity_type |
实体类型:MEDIA_ENTITY_TYPE_BASE、MEDIA_ENTITY_TYPE_VIDEO等 |
function |
u32 |
功能标识,如MEDIA_ENT_F_VID_IF_BRIDGE、MEDIA_ENT_F_PROC_VIDEO_ISP |
num_pads |
u16 |
该实体拥有的pad数量 |
pads |
struct media_pad * |
指向pad数组的指针 |
num_links |
u16 |
该实体参与的link数量 |
graph_obj |
struct media_graph_obj |
图对象,用于拓扑遍历 |
3.2.3 media_pad
struct media_pad是实体的端口,分为源垫(source pad)和接收垫(sink pad)。数据流从source pad流出,流入sink pad。
struct media_pad {
struct media_entity *entity; /* 所属实体 */
u16 index; /* 在实体中的索引(从0开始) */
unsigned long flags; /* 标志位:MEDIA_PAD_FL_SINK / MEDIA_PAD_FL_SOURCE */
struct media_link *link; /* 当前连接的link(如果有) */
};
media_pad_is_source()和media_pad_is_sink()宏检查flags。一个实体可以有多个source pad(如ISP输出YUV和RAW两路数据),也可以有多个sink pad(如MUX选择器)。
3.2.4 media_link
struct media_link描述两个pad之间的连接关系,包含数据流方向和使能状态。
struct media_link {
struct media_pad *source; /* 源pad */
struct media_pad *sink; /* 接收pad */
struct media_link *reverse; /* 反向link(双向连接时使用) */
unsigned long flags; /* 标志位:MEDIA_LNK_FL_ENABLED / MEDIA_LNK_FL_IMMUTABLE */
};
| 标志位 | 值 | 含义 |
|---|---|---|
MEDIA_LNK_FL_ENABLED |
0x0001 | 链路已使能,数据流可以经过 |
MEDIA_LNK_FL_IMMUTABLE |
0x0002 | 链路不可修改(硬件固定连接) |
MEDIA_LNK_FL_DYNAMIC |
0x0004 | 链路支持动态使能/禁用 |
3.3 拓扑结构管理
Media Controller的拓扑管理包括图的构建、图的遍历和链路的动态配置三个核心环节。下面通过一个典型的Camera ISP拓扑图来展示实际应用:
3.3.1 图的构建
驱动在初始化阶段通过以下步骤构建拓扑图:
- 创建media_device:调用
media_device_init()初始化设备实例。 - 注册实体:对每个硬件模块调用
media_entity_pads_init()初始化实体及其pad。 - 创建链路:调用
media_create_pad_link()在source pad和sink pad之间建立连接。 - 注册设备:调用
media_device_register()将整个拓扑暴露给用户空间。
/* 典型驱动初始化代码片段 */
struct media_device *mdev;
struct media_entity *sensor_ent, *mipi_ent, *isp_ent;
struct media_pad *sensor_pads, *mipi_pads, *isp_pads;
/* 1. 初始化media_device */
mdev = kzalloc(sizeof(*mdev), GFP_KERNEL);
media_device_init(mdev);
strscpy(mdev->model, "rkisp1", sizeof(mdev->model));
/* 2. 初始化实体和pad */
sensor_pads = kzalloc(sizeof(*sensor_pads), GFP_KERNEL);
sensor_pads[0].flags = MEDIA_PAD_FL_SOURCE;
media_entity_pads_init(sensor_ent, 1, sensor_pads);
mipi_pads = kzalloc(2 * sizeof(*mipi_pads), GFP_KERNEL);
mipi_pads[0].flags = MEDIA_PAD_FL_SINK;
mipi_pads[1].flags = MEDIA_PAD_FL_SOURCE;
media_entity_pads_init(mipi_ent, 2, mipi_pads);
/* 3. 创建链路:sensor pad0 → mipi pad0 */
media_create_pad_link(sensor_ent, 0, mipi_ent, 0, MEDIA_LNK_FL_ENABLED);
/* 4. 注册设备 */
media_device_register(mdev);
3.3.2 图的遍历
Media Controller提供了图遍历API,用于从指定实体出发,沿链路方向遍历整个拓扑。常用的遍历方式包括:
| 遍历方式 | API函数 | 遍历方向 | 典型用途 |
|---|---|---|---|
| 深度优先 | media_graph_walk_start() |
从source到sink | 查找数据流终点(video节点) |
| 广度优先 | media_graph_walk_next() |
逐层扩展 | 检测环路、计算路径 |
| 反向遍历 | media_graph_walk_start_rev() |
从sink到source | 追溯数据源(传感器) |
graph_mutex保护下进行,防止并发修改导致拓扑不一致。用户空间通过MEDIA_IOC_SETUP_LINK修改链路状态时,内核会自动加锁。
3.3.3 链路的动态配置
用户空间应用程序可以通过MEDIA_IOC_SETUP_LINK ioctl动态使能或禁用链路,从而改变数据通路。例如,在双摄像头方案中,通过禁用一条链路、启用另一条链路来切换摄像头:
/* 用户空间代码:切换摄像头数据通路 */
struct media_link_desc link_desc;
/* 禁用当前传感器链路 */
link_desc.source.entity = sensor1_entity_id;
link_desc.source.index = 0; /* source pad */
link_desc.sink.entity = mipi_entity_id;
link_desc.sink.index = 0; /* sink pad */
link_desc.flags = 0; /* 清除MEDIA_LNK_FL_ENABLED */
ioctl(fd, MEDIA_IOC_SETUP_LINK, &link_desc);
/* 启用目标传感器链路 */
link_desc.source.entity = sensor2_entity_id;
link_desc.source.index = 0;
link_desc.sink.entity = mipi_entity_id;
link_desc.sink.index = 0;
link_desc.flags = MEDIA_LNK_FL_ENABLED;
ioctl(fd, MEDIA_IOC_SETUP_LINK, &link_desc);
3.4 拓扑结构的用户空间接口
Media Controller通过/dev/mediaX设备节点提供以下ioctl接口,供用户空间查询和修改拓扑:
| ioctl命令 | 功能 | 返回数据 |
|---|---|---|
MEDIA_IOC_DEVICE_INFO |
获取设备信息 | struct media_device_info(驱动名、模型名、版本) |
MEDIA_IOC_ENUM_ENTITIES |
枚举所有实体 | struct media_entity_desc(实体ID、名称、类型、pad数量) |
MEDIA_IOC_ENUM_LINKS |
枚举指定实体的所有链路 | struct media_links_enum(pad列表、link列表) |
MEDIA_IOC_SETUP_LINK |
设置链路状态(使能/禁用) | struct media_link_desc(源pad、接收pad、标志位) |
MEDIA_IOC_G_TOPOLOGY |
获取完整拓扑图(推荐方式) | struct media_v2_topology(实体、pad、link数组) |
MEDIA_IOC_G_TOPOLOGY(v2接口)可以一次性获取整个拓扑图,包括所有实体、pad和link的完整信息,避免了多次ioctl调用的开销。新驱动应优先支持v2接口。
3.5 拓扑管理的最佳实践
- 链路不可变原则:硬件固定连接(如SoC内部MIPI RX到ISP的连线)应标记为
MEDIA_LNK_FL_IMMUTABLE,防止用户空间误操作。 - 原子性配置:在修改多条链路时,应使用
MEDIA_IOC_SETUP_LINK逐个操作,并在应用层实现事务回滚机制。 - 拓扑缓存:用户空间应用应缓存拓扑信息,避免频繁ioctl查询。当
MEDIA_EVENT_NOTIFY事件发生时再刷新缓存。 - 环路检测:内核在创建链路时会自动检测环路,如果新链路形成环路则返回
-EBUSY。驱动开发者无需手动实现环路检测。
media-ctl工具(v4l-utils包)可以快速查看和修改Media Controller拓扑。例如:media-ctl -d /dev/media0 -p打印完整拓扑,media-ctl -d /dev/media0 -l '"sensor":0->"mipi_rx":0[1]'使能链路。