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就是进不去。查了半天,发现是内核没编译进对应的驱动模块……嗯,这种低级错误,犯过一次就记住了。
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,别自己瞎造一个。
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,但我不建议这么做——容易跟其他设备冲突。
4.4 完整调用链:一张图看懂
为了让你更直观地理解整个流程,我画了一张图:
这张图把整个调用链串起来了。你从probe入口进去,先注册v4l2_device,再分配并注册video_device。每一步都有对应的内核函数和数据结构。
4.5 避坑指南:我踩过的三个雷
做V4L2驱动这么多年,我在注册流程上栽过不少跟头。挑三个典型的说说:
- 忘记调用v4l2_device_register —— 直接注册video_device,内核会报错“v4l2_dev is NULL”。我当时查了一天,才发现少调了这个函数。
- video_device的release回调设成NULL —— 内核在卸载驱动时会调用release,如果为NULL直接panic。嗯,这个bug导致开发板重启了十几次我才反应过来。
- 设备树compatible写错 —— probe进不来,所有注册流程都白搭。我建议你在设备树里写compatible时,一定要跟驱动里的of_match_table完全一致,大小写、标点都不能差。
4.6 小结
这一章我们走完了V4L2驱动的核心注册流程。从platform_driver的注册,到v4l2_device的初始化,再到video_device的上线,每一步都有它的意义。
你想想看,整个流程其实就三件事:让内核认识你、让V4L2框架接纳你、让用户空间找到你。搞懂了这三步,V4L2驱动的大框架你就拿下了。
下一章,我们会深入probe函数内部,看看那些fops和ioctl_ops到底该怎么实现。到时候再聊。