4. V4L2驱动核心注册流程:platform_driver注册、v4l2_device_register、video_register_device的完整调用链

这一节,我们来啃一块硬骨头——V4L2驱动的核心注册流程。

说白了,就是搞清楚三件事:platform_driver怎么注册、v4l2_device怎么初始化、video_device怎么上线。这三步走完,你的摄像头驱动才算真正“活”了。

我个人习惯把整个流程比作“开一家店”:

  • platform_driver注册 —— 拿到营业执照(告诉内核:我能管这个硬件)
  • v4l2_device_register —— 装修店面(初始化V4L2框架的根节点)
  • video_register_device —— 正式开业(让用户空间能通过/dev/videoX访问)

好,我们一步步拆开看。

4.1 platform_driver注册:驱动与设备的“相亲”

在嵌入式Linux里,大部分摄像头控制器都挂载在platform总线上。所以第一步,你得先注册一个platform_driver。

代码长这样:

static const struct of_device_id my_camera_of_match[] = {
    { .compatible = "vendor,my-camera" },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_camera_of_match);

static struct platform_driver my_camera_driver = {
    .probe  = my_camera_probe,
    .remove = my_camera_remove,
    .driver = {
        .name = "my_camera",
        .of_match_table = my_camera_of_match,
    },
};

module_platform_driver(my_camera_driver);

这里有个关键点——probe函数什么时候被调用?

当设备树里有一个节点,它的compatible属性跟你的of_match_table匹配上时,内核就会调用你的probe。我在项目中遇到过好几次,设备树节点写对了,但probe就是进不去。查了半天,发现是内核没编译进对应的驱动模块……嗯,这种低级错误,犯过一次就记住了。

小技巧: 调试时可以在probe函数入口加一句printk,确认驱动是否被加载。如果没打印,先检查设备树,再检查内核配置。

4.2 v4l2_device_register:V4L2框架的“根”

probe函数里,第一件正事就是注册v4l2_device。它是整个V4L2设备树的根节点,所有子设备(比如sensor、isp)都挂在这个下面。

调用方式:

struct v4l2_device *v4l2_dev;
int ret;

v4l2_dev = devm_kzalloc(dev, sizeof(*v4l2_dev), GFP_KERNEL);
if (!v4l2_dev)
    return -ENOMEM;

ret = v4l2_device_register(dev, v4l2_dev);
if (ret < 0) {
    dev_err(dev, "Failed to register v4l2_device\n");
    return ret;
}

你想想看,这个函数做了什么?

  • 初始化内部的锁和链表
  • 把v4l2_device挂到父设备(platform_device)上
  • 在sysfs里创建对应的目录节点

我曾经在一个项目里,因为v4l2_device_register传错了父设备指针,导致sysfs目录结构乱掉,调试花了两天。后来养成习惯:父设备一定要传probe函数里的struct device *dev,别自己瞎造一个。

注意: v4l2_device_register不会分配内存,你需要自己先分配好struct v4l2_device。用devm_kzalloc可以省去手动释放的麻烦。

4.3 video_register_device:让用户空间看到你

最后一步,注册video_device。这一步做完,用户空间就能通过/dev/videoX来操作你的摄像头了。

代码示例:

struct video_device *vdev;
int ret;

vdev = devm_kzalloc(dev, sizeof(*vdev), GFP_KERNEL);
if (!vdev)
    return -ENOMEM;

strscpy(vdev->name, "My Camera", sizeof(vdev->name));
vdev->vfl_dir = VFL_DIR_RX;  // 捕获设备
vdev->fops   = &my_camera_fops;
vdev->ioctl_ops = &my_camera_ioctl_ops;
vdev->release = video_device_release_empty;
vdev->v4l2_dev = v4l2_dev;

ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
if (ret < 0) {
    dev_err(dev, "Failed to register video device\n");
    return ret;
}

video_set_drvdata(vdev, my_priv_data);

这里有几个坑,我一个个说:

  • vfl_dir:接收数据用VFL_DIR_RX,发送数据用VFL_DIR_TX,别搞反了。我见过有人把摄像头配成TX,结果应用程序打开设备就报错。
  • release回调:如果你用devm_kzalloc分配了video_device,release可以设成video_device_release_empty。否则你得自己实现释放逻辑。
  • 第三个参数:传-1表示让内核自动分配设备号。如果你想固定成某个节点(比如video0),可以传0,但我不建议这么做——容易跟其他设备冲突。
核心要点: video_register_device成功后,内核会在/dev下创建videoX节点,并在sysfs里生成对应的属性文件。用户空间的应用程序(比如v4l2-ctl、gstreamer)就是通过这个节点来调用你的ioctl、mmap、poll等操作。

4.4 完整调用链:一张图看懂

为了让你更直观地理解整个流程,我画了一张图:

V4L2驱动核心注册流程 1. platform_driver注册 module_platform_driver() 设备树匹配 → probe被调用 2. v4l2_device_register 初始化V4L2根节点 挂载到platform_device 3. 分配video_device devm_kzalloc + 设置fops/ioctl_ops 4. video_register_device 创建/dev/videoX节点 用户空间可访问 probe函数上下文 struct platform_device *pdev struct device *dev = &pdev->dev 分配私有数据结构 初始化硬件(时钟、GPIO等) 注册中断、申请DMA缓冲区

这张图把整个调用链串起来了。你从probe入口进去,先注册v4l2_device,再分配并注册video_device。每一步都有对应的内核函数和数据结构。

4.5 避坑指南:我踩过的三个雷

做V4L2驱动这么多年,我在注册流程上栽过不少跟头。挑三个典型的说说:

  1. 忘记调用v4l2_device_register —— 直接注册video_device,内核会报错“v4l2_dev is NULL”。我当时查了一天,才发现少调了这个函数。
  2. video_device的release回调设成NULL —— 内核在卸载驱动时会调用release,如果为NULL直接panic。嗯,这个bug导致开发板重启了十几次我才反应过来。
  3. 设备树compatible写错 —— probe进不来,所有注册流程都白搭。我建议你在设备树里写compatible时,一定要跟驱动里的of_match_table完全一致,大小写、标点都不能差。
调试建议: 在probe函数里每一步都加dev_dbg打印,配合dmesg观察。如果v4l2_device_register成功了但video_register_device失败,多半是fops或ioctl_ops没设置全。

4.6 小结

这一章我们走完了V4L2驱动的核心注册流程。从platform_driver的注册,到v4l2_device的初始化,再到video_device的上线,每一步都有它的意义。

你想想看,整个流程其实就三件事:让内核认识你、让V4L2框架接纳你、让用户空间找到你。搞懂了这三步,V4L2驱动的大框架你就拿下了。

下一章,我们会深入probe函数内部,看看那些fops和ioctl_ops到底该怎么实现。到时候再聊。


可可精品资料库