subdev子系统的注册与使用

1. 序章:从“入职”到“开拍”

在之前的学习中,我们认识了 subdev 这位身怀绝技的“工作人员”(比如摄影师 Sensor)。但一个工作人员光有简历和技能还不够,他需要正式加入剧组(注册),并且能够听懂导演和制片人的指令(使用)。

这一讲,我们就将焦点完全放在 subdev生命周期上,解决两个核心问题:

  1. Subdev 的“入职手续”是怎样办理的? (内核注册过程)
  2. “导演”(内核)和“制片人”(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_devicesubdevs 链表尾部。
  • 意义: 从此,这个 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

  • 调用流程揭秘:

    1. 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 首先调用的是底层 cdevops,也就是 v4l2_fops 里的 v4l2_ioctl 函数。
    • v4l2_ioctl 这个中央调度器在做了一些通用检查后,会发现这是一个 subdev 类型的设备,于是它会进一步调用 video_device 专属的 fops,即 v4l2_subdev_fops 里的 unlocked_ioctl 函数 (subdev_ioctl)。
    • 这样,调用就被精确地分发到了 subdev 的处理逻辑中。这种设计实现了通用逻辑v4l2_fops)和特有逻辑v4l2_subdev_fops)的完美分离。

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 电源
  • 来龙去脉: 这个宏的作用是在调用前进行一系列安全检查,如同一个严谨的副导演:
    1. 检查 sd (subdev 实例) 是否为空?—— “摄影师在吗?”
    2. 检查 sd->ops->o (操作类别,如 core) 是否为空?—— “他有核心技能手册吗?”
    3. 检查 sd->ops->o->f (具体操作函数,如 s_power) 是否为空?—— “手册里写了怎么开机吗?” 只有所有检查都通过,才会执行真正的函数调用。这极大地增强了驱动代码的健壮性,避免了因调用空指针而导致的内核崩溃。

4.2 用户态使用:制片人的远程遥控

当 APP 对 /dev/v4l-subdevX 发起 ioctl 时,调用路径如下:
在这里插入图片描述

  1. APP 发出指令
    • 应用程序 open 打开设备文件,然后发起 ioctl() 系统调用。
  2. VFS & CDEV 层 (第一级跳转)
    • 内核的虚拟文件系统 (VFS) 根据设备号找到对应的 cdev (字符设备) 结构体。
    • 它调用 cdev->ops->unlocked_ioctl。从图中清晰可见,这个指针指向 V4L2 框架的通用入口 v4l2_fops 里的 v4l2_ioctl 函数。
  3. V4L2 中央调度器 (v4l2_ioctl)
    • v4l2_ioctl 函数是一个“总调度员”,它会处理所有 V4L2 设备的 ioctl
    • 它通过 filp->private_data 找到对应的 video_device 结构体。
    • 在进行了一系列通用检查后,它会将请求转发video_device 专属的操作集,即调用 vdev->fops->unlocked_ioctl
  4. Subdev 专属处理层 (第二级跳转)
    • 从图中 video_device 的指针关系可以看到,对于 subdev 节点,vdev->fops 指向的是 v4l2_subdev_fops
    • 因此,v4l2_subdev_fops 里的 unlocked_ioctl 函数(即 subdev_ioctl)被调用。
  5. 命令分发与执行
    • 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_fopsv4l2_subdev_fops 的两级分发,对 subdev 进行远程遥控。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值