1. 序章:从“入职”到“开拍”
在之前的学习中,我们认识了 subdev 这位身怀绝技的“工作人员”(比如摄影师 Sensor)。但一个工作人员光有简历和技能还不够,他需要正式加入剧组(注册),并且能够听懂导演和制片人的指令(使用)。
这一讲,我们就将焦点完全放在 subdev 的生命周期上,解决两个核心问题:
- Subdev 的“入职手续”是怎样办理的? (内核注册过程)
- “导演”(内核)和“制片人”(APP)如何向它下达指令? (内核态与用户态的使用)
2. Subdev 的两种“岗位职责”
在办理入职手续前,我们首先要明确这个 subdev 的岗位职责。它决定了谁有权指挥它。主要分为两种模式:
- 幕后模式 (Kernel Internal):
- 职责描述: 这种
subdev就像剧组里的场务或灯光助理,默默无闻地在幕后工作。它的所有操作都由“导演”(内核里的其他驱动,如 CSI/ISP 控制器)直接调用。 - 对用户: 上层 APP 完全感觉不到它的存在,摄像头驱动程序在内部“偷偷地”调用它的函数来完成硬件协同。
- 例子: 一个简单的镜头马达控制器,其对焦操作可能完全由 ISP 驱动在内部自动控制。
- 职责描述: 这种
- 台前模式 (App Exposed):
- 职责描述: 对于功能复杂、参数繁多的硬件,比如 ISP,驱动程序需要提供一个途径,让“制片人”(APP)能够直接调整它的参数,就像导演指导演员的表演细节一样。
- 对用户: 这种
subdev的函数会被“暴露”给用户空间,APP 可以通过操作一个设备文件来调用它们。 - 例子: 相机 APP 通过操作 ISP
subdev的设备节点,来调整降噪等级、锐度、色彩饱和度等参数。
3. 内核注册过程—— 办理“入职手续”
Subdev 的“入职手续”分为两步,由 V4L2 框架提供的标准函数完成。
第 1 步:登记到剧组花名册 (v4l2_device_register_subdev)
这是最基本、最核心的一步,所有 subdev 都必须执行。
int v4l2_device_register_subdev(struct v4l2_device *v4l2_dev,
struct v4l2_subdev *sd);
// 核心代码
list_add_tail(&sd->list, &v4l2_dev->subdevs);
- 函数:
int v4l2_device_register_subdev(struct v4l2_device *v4l2_dev, struct v4l2_subdev *sd); - 作用: 这个函数将
subdev(工作人员) 注册到它所属的v4l2_device(剧组) 中。 - 核心动作: 其本质就是
list_add_tail(&sd->list, &v4l2_dev->subdevs);,即将该subdev的链表节点挂载到v4l2_device的subdevs链表尾部。 - 意义: 从此,这个
subdev就正式成为该设备的一部分,被内核统一管理。
第 2 步:分配独立的“休息室” (v4l2_device_register_subdev_nodes)
这一步是可选的,只针对需要暴露给 APP 的“台前模式”subdev。
int v4l2_device_register_subdev_nodes(struct v4l2_device *v4l2_dev);
v4l2_device_register_subdev_nodes
struct video_device *vdev;
vdev->fops = &v4l2_subdev_fops;
err = __video_register_device(vdev, VFL_TYPE_SUBDEV, -1, 1,
sd->owner);
name_base = "v4l-subdev";
vdev->cdev->ops = &v4l2_fops;
ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
-
函数:
int v4l2_device_register_subdev_nodes(struct v4l2_device *v4l2_dev); -
作用: 这个函数会遍历(
v4l2_device->subdevs链表),检查(即subdev->flags中设置了V4L2_SUBDEV_FL_HAS_DEVNODE标志位),然后为举了手的subdev注册一个标准的字符设备节点,通常命名为/dev/v4l-subdevX。 -
调用流程揭秘:
-
为
subdev分配一个video_device结构体(这是 Linux 视频设备的通用描述符)。设置两级操作集 (Two-Level fops): 这是理解调用流程的关键所在!
- 底层
cdev操作集:vdev->cdev->ops = &v4l2_fops;v4l2_fops是 V4L2 框架的通用文件操作集。所有 V4L2 设备(无论是 video、vbi 还是 subdev)的字符设备cdev都使用它作为入口。它的unlocked_ioctl函数 (v4l2_ioctl) 是一个强大的中央调度器。 video_device专属操作集:vdev->fops = &v4l2_subdev_fops;v4l2_subdev_fops则是专为 subdev 节点准备的文件操作集。
注册设备: 调用
__video_register_device,它内部会进一步调用cdev_add,最终完成字符设备的注册。 - 底层
-
- 工作原理: 这种两级设计非常精妙。当 APP 对
/dev/v4l-subdevX发起ioctl时:- VFS 首先调用的是底层
cdev的ops,也就是v4l2_fops里的v4l2_ioctl函数。 v4l2_ioctl这个中央调度器在做了一些通用检查后,会发现这是一个subdev类型的设备,于是它会进一步调用video_device专属的fops,即v4l2_subdev_fops里的unlocked_ioctl函数 (subdev_ioctl)。- 这样,调用就被精确地分发到了
subdev的处理逻辑中。这种设计实现了通用逻辑(v4l2_fops)和特有逻辑(v4l2_subdev_fops)的完美分离。
- VFS 首先调用的是底层
4. Subdev 的使用 —— 下达工作指令
Subdev 注册成功后,就可以开始接收来自内核态和用户态的指令了。
4.1 内核态使用:导演的内部沟通
内核中的其他驱动(通常是桥接驱动)可以直接调用 subdev 的操作函数,但更推荐、更安全的方式是使用 v4l2_subdev_call 宏。
- 宏定义:
#define v4l2_subdev_call(sd, o, f, args...) - 示例:
v4l2_subdev_call(sensor_sd, core, s_power, 1);// 打开 sensor 电源 - 来龙去脉: 这个宏的作用是在调用前进行一系列安全检查,如同一个严谨的副导演:
- 检查
sd(subdev 实例) 是否为空?—— “摄影师在吗?” - 检查
sd->ops->o(操作类别,如core) 是否为空?—— “他有核心技能手册吗?” - 检查
sd->ops->o->f(具体操作函数,如s_power) 是否为空?—— “手册里写了怎么开机吗?” 只有所有检查都通过,才会执行真正的函数调用。这极大地增强了驱动代码的健壮性,避免了因调用空指针而导致的内核崩溃。
- 检查
4.2 用户态使用:制片人的远程遥控
当 APP 对 /dev/v4l-subdevX 发起 ioctl 时,调用路径如下:

- APP 发出指令
- 应用程序
open打开设备文件,然后发起ioctl()系统调用。
- 应用程序
- VFS & CDEV 层 (第一级跳转)
- 内核的虚拟文件系统 (VFS) 根据设备号找到对应的
cdev(字符设备) 结构体。 - 它调用
cdev->ops->unlocked_ioctl。从图中清晰可见,这个指针指向 V4L2 框架的通用入口v4l2_fops里的v4l2_ioctl函数。
- 内核的虚拟文件系统 (VFS) 根据设备号找到对应的
- V4L2 中央调度器 (
v4l2_ioctl)v4l2_ioctl函数是一个“总调度员”,它会处理所有 V4L2 设备的ioctl。- 它通过
filp->private_data找到对应的video_device结构体。 - 在进行了一系列通用检查后,它会将请求转发给
video_device专属的操作集,即调用vdev->fops->unlocked_ioctl。
- Subdev 专属处理层 (第二级跳转)
- 从图中
video_device的指针关系可以看到,对于subdev节点,vdev->fops指向的是v4l2_subdev_fops。 - 因此,
v4l2_subdev_fops里的unlocked_ioctl函数(即subdev_ioctl)被调用。
- 从图中
- 命令分发与执行
subdev_ioctl调用subdev_do_ioctl。subdev_do_ioctl会通过video_get_drvdata(vdev)找到v4l2_subdev实例。- 然后,它根据传入的
cmd(命令号),在switch-case语句中找到对应的处理分支,最终通过v4l2_subdev实例中的ops指针,调用到我们驱动程序实现的具体函数,例如pad->set_fmt。
这张图完美地诠释了分层与解耦的设计思想:VFS 只与 cdev 交互,cdev 交给通用的 V4L2 调度器,调度器再根据设备类型分发给专属的处理器,最终才到达具体的驱动实现。
总结
- 两种角色:
subdev可以只在内核内部被调用,也可以通过创建设备节点暴露给用户 APP。 - 两步注册: 通过
v4l2_device_register_subdev加入“花名册”,再通过v4l2_device_register_subdev_nodes获得“独立休息室”。这个过程巧妙地运用了两级fops结构来实现通用与专用的分离。 - 两种调用方式: 内核通过安全的
v4l2_subdev_call宏进行内部调度;APP 通过标准的ioctl接口,经由v4l2_fops和v4l2_subdev_fops的两级分发,对subdev进行远程遥控。
40

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



