2. V4L2设备注册流程:从platform_driver到video_register_device的完整调用链
在嵌入式Linux系统中,V4L2设备驱动通常基于platform_driver框架实现。设备注册的完整调用链从驱动入口函数开始,经过设备匹配、初始化、V4L2核心注册,最终在用户空间/sys/class/video4linux下创建设备节点。本章将详细拆解这一流程,并分析关键数据结构与函数调用关系。
2.1 整体调用链概览
下图展示了从platform_driver注册到video_register_device完成的完整调用链,以及/sys/class/video4linux下设备节点的创建过程。
2.2 关键数据结构
V4L2设备注册涉及多个核心数据结构,它们之间的关联关系决定了驱动的组织方式。下表列出了最重要的几个结构体及其作用:
| 数据结构 | 头文件 | 核心作用 | 注册阶段 |
|---|---|---|---|
platform_driver |
linux/platform_device.h | 平台驱动框架,提供probe/remove回调 | 驱动入口 |
video_device |
media/v4l2-dev.h | V4L2设备核心结构,包含fops、ioctl_ops等 | 分配与注册 |
v4l2_device |
media/v4l2-device.h | V4L2父设备,管理子设备实例 | 初始化 |
v4l2_subdev |
media/v4l2-subdev.h | 子设备(如sensor、decoder)抽象 | 子设备注册 |
video_device中的vfl_type |
media/v4l2-dev.h | 指定设备类型(VFL_TYPE_VIDEO/VFL_TYPE_VBI等) | 注册参数 |
2.3 platform_driver注册阶段
驱动入口函数通过module_platform_driver()宏或手动调用platform_driver_register()完成注册。内核在设备树匹配或ACPI匹配成功后,调用驱动的probe()函数。
/* 驱动入口示例 */
static struct platform_driver my_v4l2_driver = {
.probe = my_v4l2_probe,
.remove = my_v4l2_remove,
.driver = {
.name = "my_v4l2_device",
.of_match_table = my_v4l2_of_match,
},
};
module_platform_driver(my_v4l2_driver);
2.4 video_device分配与初始化
在probe()中,驱动需要分配并初始化video_device结构体。分配通常使用video_device_alloc(),初始化则包括设置v4l2_dev、fops、ioctl_ops以及release回调。
/* video_device分配与初始化 */
struct video_device *vdev;
vdev = video_device_alloc();
if (!vdev) {
dev_err(dev, "video_device_alloc failed\n");
return -ENOMEM;
}
vdev->v4l2_dev = &my_v4l2_dev;
vdev->fops = &my_fops;
vdev->ioctl_ops = &my_ioctl_ops;
vdev->release = video_device_release;
vdev->vfl_type = VFL_TYPE_VIDEO;
vdev->vfl_dir = VFL_DIR_RX; /* 捕获方向 */
snprintf(vdev->name, sizeof(vdev->name), "%s", "my_camera");
2.5 video_register_device内部调用链
video_register_device()是V4L2设备注册的核心函数,其内部调用__video_register_device(),完成以下关键步骤:
- 设备号分配: 调用
__video_register_device(),根据vfl_type从V4L2主设备号(81)下分配次设备号。 - cdev注册: 初始化
cdev结构体并调用cdev_add(),将字符设备注册到内核。 - device_register: 调用
device_register()将video_device的dev成员注册到Linux设备模型。 - sysfs创建: 在
/sys/class/video4linux/下创建videoX目录,并生成设备属性文件。 - 设备节点创建: udev/mdev根据sysfs信息在
/dev下创建设备节点/dev/videoX。
/* video_register_device核心调用链(简化) */
int video_register_device(struct video_device *vdev, int type, int nr)
{
return __video_register_device(vdev, type, nr, 1, vdev->fops->owner);
}
static int __video_register_device(struct video_device *vdev, int type, int nr,
int warn_if_nr_in_use, struct module *owner)
{
/* 1. 设置设备号 */
vdev->vfl_type = type;
vdev->cdev = cdev_alloc();
vdev->cdev->ops = &v4l2_fops;
vdev->cdev->owner = owner;
/* 2. 分配次设备号 */
ret = video_register_media_controller(vdev);
/* 3. 注册字符设备 */
cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
/* 4. 注册到设备模型 */
vdev->dev->devt = MKDEV(VIDEO_MAJOR, vdev->minor);
vdev->dev->class = video_class;
ret = device_register(vdev->dev);
/* 5. sysfs属性创建 */
video_device_create_attributes(vdev);
return 0;
}
2.6 /sys/class/video4linux下的设备节点创建
当device_register()成功执行后,内核会在/sys/class/video4linux/目录下创建以videoX命名的子目录。该目录包含多个属性文件,用于用户空间查询设备信息。
- dev: 包含主设备号和次设备号,格式为
81:0,udev据此创建设备节点。 - name: 驱动设置的设备名称,用于用户空间识别。
- index: 设备索引号,用于区分同一驱动管理的多个设备。
2.7 设备节点创建与udev交互
当device_register()完成后,内核会发送uevent事件。用户空间的udev(或mdev)监听到该事件后,根据/sys/class/video4linux/videoX/dev中的设备号,在/dev下创建对应的设备节点。
/* udev规则示例:/etc/udev/rules.d/99-v4l2.rules */
SUBSYSTEM=="video4linux", KERNEL=="video[0-9]*", GROUP="video", MODE="0660"
/* 手动创建设备节点(调试用) */
mknod /dev/video0 c 81 0
- 如果
/dev/videoX节点未自动创建,检查udev服务是否运行,以及/sys/class/video4linux/videoX/dev文件是否存在。 - 设备节点权限不足时,应用程序可能无法打开设备,需调整udev规则或手动修改节点权限。
- 多个V4L2设备共存时,次设备号按注册顺序分配,可通过
nr参数指定起始编号。
2.8 完整注册流程总结
下表总结了从驱动入口到设备节点创建的完整流程及各阶段的关键函数:
| 阶段 | 关键函数/宏 | 输出/结果 | 错误处理 |
|---|---|---|---|
| 驱动入口 | module_platform_driver() |
platform_driver注册到内核 | 返回错误码 |
| 设备匹配 | of_match_table/ACPI |
匹配成功调用probe | probe不被调用 |
| 硬件初始化 | probe() |
资源获取、时钟使能等 | 返回-ENODEV等 |
| vdev分配 | video_device_alloc() |
分配video_device内存 | 返回NULL |
| vdev初始化 | 设置fops/ioctl_ops等 | vdev结构体填充完成 | 空指针检查 |
| V4L2注册 | video_register_device() |
字符设备注册、sysfs创建 | 返回负值错误码 |
| 设备节点 | udev/mdev | /dev/videoX创建 | 检查udev规则 |
dev_info()打印,观察注册流程是否完整。使用ls -l /sys/class/video4linux/和cat /sys/class/video4linux/video0/dev验证设备节点是否创建成功。