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下设备节点的创建过程。

核心流程: module_init → platform_driver_register → probe → video_device_alloc → video_register_device → device_register → sysfs创建节点
V4L2设备注册完整调用链 V4L2设备注册完整调用链 module_init(probe) platform_driver_register() driver匹配 → probe() video_device_alloc() 初始化vdev->v4l2_dev等 video_register_device() __video_register_device() device_register() sysfs创建videoX节点 /sys/class/video4linux/videoX 关键说明 • probe()中完成硬件初始化 • video_device_alloc分配vdev • video_register_device注册到内核

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);
提示: 在probe()函数中,驱动应完成硬件资源获取(如时钟、GPIO、中断、DMA缓冲区)、初始化V4L2核心结构体,并最终调用video_register_device()。probe()失败将导致设备无法注册。

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(),完成以下关键步骤:

  1. 设备号分配: 调用__video_register_device(),根据vfl_type从V4L2主设备号(81)下分配次设备号。
  2. cdev注册: 初始化cdev结构体并调用cdev_add(),将字符设备注册到内核。
  3. device_register: 调用device_register()将video_device的dev成员注册到Linux设备模型。
  4. sysfs创建: 在/sys/class/video4linux/下创建videoX目录,并生成设备属性文件。
  5. 设备节点创建: 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命名的子目录。该目录包含多个属性文件,用于用户空间查询设备信息。

/sys/class/video4linux 目录结构 /sys/class/video4linux 目录结构 /sys/class/video4linux/ video0/ video1/ video2/ video0/ 属性文件 • dev (设备号: 81:0) • name (设备名称) • index (设备索引)
关键属性文件说明:
  • 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规则
调试建议: 在probe()函数中添加dev_info()打印,观察注册流程是否完整。使用ls -l /sys/class/video4linux/和cat /sys/class/video4linux/video0/dev验证设备节点是否创建成功。