Subdev与Media子系统的数据结构

在我们一头扎进代码的海洋之前,先来看一个生动的比喻,它会贯穿我们整个学习过程。

想象一下,我们要用 Linux 系统来控制一个复杂的摄像头模组去拍一部电影。这个过程就像管理一个电影剧组:

  • 演员和工作人员 (Actors & Crew): 摄像头模组里有各种硬件单元:图像传感器 (Sensor)、图像处理器 (ISP)、镜头马达、闪光灯等等。它们就像剧组里的摄影师、灯光师、录音师,每个人都身怀绝技,负责一项具体工作。在我们的技术世界里,这些"工作人员"就是 Subdev (子设备)。
  • 导演 (The Director): 剧组里不能没有导演。导演不亲自操作摄像机,但他需要知道每个工作人员的技能,并指挥他们如何站位、何时开始工作、数据(影像和声音)如何从摄影师传递给录音师,再到剪辑师。这个"导演"角色,就是 Media 子系统。
  • 剧本和分镜图 (Script & Storyboard): 导演脑中有整部电影的蓝图,包括每个场景需要哪些人、设备如何连接。这张"蓝图",就是由 media 子系统维护的硬件拓扑图 (Topology)。
  • 开拍指令 (Action!): 当导演喊出"Action!",整个剧组就按照预定的流程开始工作。这个指令,就相当于上层应用程序下达的启动视频流 (Streaming) 的命令。

有了这个比喻,我们再来看技术细节,就会清晰很多。我们的目标,就是搞清楚:每个"工作人员"(subdev)的简历和技能是怎样的?"导演"(media 子系统)是如何阅读简历、绘制分镜图并指挥拍摄。

1. Subdev 剖析 ------ "工作人员"的简历与技能手册

Subdev 是整个框架的基石,代表一个具体可独立控制的硬件功能单元。内核中,每个 subdev 都由 struct v4l2_subdev 实例描述。

1.1 "简历" - struct v4l2_subdev 深度解析

这个结构体就是 subdev 的"个人简历",详细记录了它的身份信息和核心属性。

c 复制代码
struct v4l2_subdev {
#if defined(CONFIG_MEDIA_CONTROLLER)
	struct media_entity entity;
#endif
	struct list_head list;
	struct module *owner;
	bool owner_v4l2_dev;
	u32 flags;
	struct v4l2_device *v4l2_dev;
	const struct v4l2_subdev_ops *ops;
	const struct v4l2_subdev_internal_ops *internal_ops;
	struct v4l2_ctrl_handler *ctrl_handler;
	char name[V4L2_SUBDEV_NAME_SIZE];
	u32 grp_id;
	void *dev_priv;
	void *host_priv;
	struct video_device *devnode;
	struct device *dev;
	/* .............................. */
};
  • struct media_entity entity : 核心身份 这是最重要的成员,它声明了"我是一个 media_entity",意味着这个 subdev 有资格被"导演"看到和管理,可以拥有自己的"站位"(端口 Pad)和"连接关系"(链接 Link)。
  • const struct v4l2_subdev_ops *ops : 技能认证 是一个指针,指向 subdev 的"技能手册",详细说明它会做什么以及怎么做。
  • struct v4l2_ctrl_handler *ctrl_handler : 可调参数 这是一个非常重要的"控件处理器"。像摄影师需要调节的亮度、对比度、曝光、对焦等所有参数,都是通过这个 ctrl_handler 来注册和管理的。
  • struct v4l2_device *v4l2_dev : 所属单位 指向该 subdev 所属的"总设备"。一个剧组(v4l2_device)可以有很多工作人员(subdevs)。
  • struct list_head list : 花名册入口 通过这个链表节点,每个 subdev 都会被登记到 v4l2_devicesubdevs "花名册"上,便于内核统一管理。
  • u32 flags : 特殊标记 用来描述 subdev 的一些特性,比如:
    • V4L2_SUBDEV_FL_HAS_DEVNODE: 表明这个工作人员比较"大牌",需要一个独立的"休息室"(设备节点 /dev/v4l-subdevX),允许应用直接与他沟通。
    • V4L2_SUBDEV_FL_IS_I2C: 表明这位工作人员是通过 I2C 这条"对讲机信道"来沟通的。
  • void *dev_priv : 私人物品 一个指针,指向驱动自己的私有数据(比如电源状态、当前分辨率等)。框架提供了 v4l2_set_subdevdata()v4l2_get_subdevdata() 这对"储物柜钥匙"来安全地存取。

1.2 "技能手册" - struct v4l2_subdev_ops 的层级结构

c 复制代码
struct v4l2_subdev_ops {
	const struct v4l2_subdev_core_ops	*core;
	const struct v4l2_subdev_tuner_ops	*tuner;
	const struct v4l2_subdev_audio_ops	*audio;
	const struct v4l2_subdev_video_ops	*video;
	const struct v4l2_subdev_vbi_ops	*vbi;
	const struct v4l2_subdev_ir_ops		*ir;
	const struct v4l2_subdev_sensor_ops	*sensor;
	const struct v4l2_subdev_pad_ops	*pad;
};

对于摄像头 Sensor 来说,最重要的就是 .core, .video, 和 .pad 这三类技能:

  • 核心技能 (.core) : 这是所有工作人员都必须具备的基本技能。
    • s_power(): 开关机。控制设备的电源,这是最基本的前提。
    • log_status(): 状态汇报。打印设备当前状态,方便导演调试。
  • 视频技能 (.video) : 负责全局 的视频流操作。
    • s_stream(): 开拍/停拍。当导演喊"Action!"或"Cut!"时,框架就会调用这个函数。
    • s_fmt(): 设置格式 。在简单的、没有"导演"的剧组里,用这个来统一设置格式。但在有"导演"的专业剧组里,更精细的格式设置在 .pad 技能里。
  • 站位与连接技能 (.pad) : 这是 Media 框架的专属高级技能 ,负责精细化的团队协作。
    • set_fmt(): 端口格式设置 。允许一个 subdev 的不同端口有不同的数据格式。比如 ISP,它的输入端口接收 Sensor 的 RAW 格式数据,输出端口则输出 YUV 格式数据。
    • link_validate(): 连接预检。在开拍前,导演会用这个技能来检查:"摄影师,你输出的信号格式和灯光师的输入要求匹配吗?" 驱动可以在这里进行合法性检查,确保数据流能正确建立。

1.3 "入职流程" - probe 函数与 v4l2_subdev_call

一个 subdev 是如何加入剧组的呢?这通常发生在驱动的 probe 函数中

c 复制代码
sensor_probe()
{
    /* 1. 准备一份空的简历(sensor_info),里面包含了 v4l2_subdev */
    info = kzalloc(sizeof(struct sensor_info), GFP_KERNEL);
    sd = &info->sd;

    /* 2. 填写简历,并提交给剧组(V4L2框架) */
    /* &sensor_ops 就是我们上面详细讲解的"技能手册" */
    v4l2_i2c_subdev_init(sd, client, &sensor_ops);
}

v4l2_i2c_subdev_init 这个函数,就像是办理入职手续。它会把"简历"(sd)和"技能手册"(&sensor_ops)绑定,并把这个新员工登记到剧组的"花名册"上。 当导演需要指挥某个工作人员时,他不会直接去翻技能手册,而是通过一个安全的"对讲机宏" v4l2_subdev_call 来下达指令,确保指令能被正确理解和执行,避免出错。

2. Media 子系统剖析 ------ "导演"的剧本与管理工具

2.1 核心数据结构

c 复制代码
struct media_device {
	/* dev->driver_data points to this struct. */
	struct device *dev;
	struct media_devnode *devnode;

	char model[32];
	char driver_name[32];
	char serial[40];
	char bus_info[32];
	u32 hw_revision;

	u64 topology_version;

	u32 id;
	struct ida entity_internal_idx;
	int entity_internal_idx_max;

	struct list_head entities;
	struct list_head interfaces;
	struct list_head pads;
	struct list_head links;

	/* notify callback list invoked when a new entity is registered */
	struct list_head entity_notify;

	/* .............................. */
};
  • struct media_device**: **导演的"制作圣经" :它代表一个完整的 media 设备,对应 /dev/mediaX
  • struct list_head entities: 演职员表。链接该设备下所有 entity 的链表头。
  • struct mutex graph_mutex: 片场秩序锁。一个非常重要的互斥锁,确保在调整机位、连接线路时不会发生混乱。
  • u64 topology_version: 剧本版本号。每当分镜图有变动(比如增加了一个角色),版本号就会更新,方便其他人知道剧本改了。
  • char model[32]: 电影名称,便于用户识别。
  • struct media_entity**: **通用身份:演职员 这是框架对所有功能单元(包括 subdev 和最终输出的 video 节点)的统一抽象。每个人都是一个 entity
  • struct media_pad**: **连接端口 代表 entity 的一个输入或输出端口。就像摄影机上的"视频输出"口和监视器上的"视频输入"口。
  • struct media_link**: **连接线缆 代表两个 pad 之间的一条"连接线",明确了信号的流向。

2.2 "从剧本到大荧幕" - 构建与使用流程

  1. 绘制分镜图 (驱动侧) : 驱动程序是分镜图的绘制者。
    1. 创建剧组 : 桥接驱动(如 SoC 的 CSI/ISP 驱动)会调用 media_device_init() 初始化一个 media_device 实例,相当于成立一个新剧组。
    2. 角色入组: 当 subdev 驱动(如 Sensor 驱动)注册自己时,它就将自己的 media_entity 注册到了这个剧组中。
    3. 绘制连接: 桥接驱动最清楚硬件的物理连接,它会调用 media_create_pad_links(),像绘制电路图一样,在 pad 之间创建 link,从而构建出完整的分镜图。
    4. 对外公布:当所有角色和连接都确定后,驱动调用 media_device_register(),此时 /dev/mediaX 设备节点被创建,这部"电影"的制作信息就对外界(用户空间)可见了。
  2. 指挥拍摄 (应用侧) : 上层应用是最终的"制片人",他来决定如何拍摄。
    1. 审阅剧本: 应用通过 open("/dev/mediaX"),然后用一系列 ioctl 调用,读取所有的 entitylink,从而了解整个剧组的配置。
    2. 确定拍摄方案: 应用根据需求,决定本次拍摄需要哪些人、数据如何流转。比如,"这次我要用主摄像头,经过 ISP 处理后输出"。
    3. 下达布线指令: 应用通过 MEDIA_IOC_SETUP_LINK 这个 ioctl,告诉导演:"把主摄像头和 ISP 的这条线路接通!"
    4. 喊"Action!": 应用打开对应的 /dev/videoX 节点,然后启动流(VIDIOC_STREAMON)。这个操作会触发导演去通知 pipeline 上的所有工作人员:"开拍!"

3. 总结

现在,我们用最精炼的语言回顾一下:

  • Subdev 是"执行者" : 具体硬件的代言人,通过 struct v4l2_subdev (简历) 和 struct v4l2_subdev_ops (技能手册) 描述自己。
  • Media 子系统是"管理者" : 是所有 subdev 的导演,使用 media_device, media_entity, media_pad, media_link 这一套"管理工具"来建立和维护整个硬件的拓扑模型。
  • 工作流程 : 这是一个"自下而上构建,自上而下控制 "的优雅过程。
    1. 驱动 从底层构建出硬件的"分镜图"。
    2. 应用从顶层 读取分镜图,并根据需求配置**出一条具体的"拍摄流水线"(Pipeline)。
    3. 最终实现对复杂视频硬件的灵活、模块化控制。