2. V4L2框架入门:核心数据结构与设备模型
V4L2(Video for Linux 2)是Linux内核中视频设备驱动的标准框架。理解其核心数据结构是编写Camera驱动的第一步。本章将深入剖析video_device、v4l2_device和v4l2_subdev三个核心结构体,以及它们之间的协作关系。
2.1 v4l2_device:顶层设备容器
v4l2_device 是V4L2框架中最顶层的结构体,代表一个物理视频设备。它作为容器,管理该设备下的所有子设备(subdev)和视频节点(video_node)。
| 成员 | 类型 | 说明 |
|---|---|---|
dev |
struct device * |
指向内核通用设备结构体,用于设备模型集成 |
v4l2_dev |
struct list_head |
用于将本设备链接到全局v4l2设备列表 |
subdevs |
struct list_head |
挂载所有注册到本设备的v4l2_subdev |
name |
char[32] |
设备名称,用于调试和用户空间识别 |
ctrl_handler |
struct v4l2_ctrl_handler * |
控制句柄,管理曝光、增益等控制参数 |
notify |
void (*notify)() |
子设备通知回调函数 |
实战要点: 在驱动probe函数中,通常先调用
v4l2_device_register() 注册顶层设备,再注册子设备和视频节点。一个物理Camera模组通常对应一个 v4l2_device。
2.2 video_device:用户空间接口
video_device 是V4L2框架中与用户空间直接交互的结构体。它创建 /dev/videoX 字符设备节点,并实现 open()、close()、ioctl() 等文件操作。
video_device 的核心成员及其作用:
- fops:指向
v4l2_file_operations结构体,实现open/close/ioctl/mmap/poll等系统调用。 - v4l2_dev:指向所属的顶层
v4l2_device,建立父子关系。 - queue:指向
vb2_queue(videobuf2队列),管理视频缓冲区的分配与流转。 - ctrl_handler:控制句柄,处理用户空间的
VIDIOC_S_CTRL等控制命令。 - minor:次设备号,用于在
/dev下创建设备节点。
常见陷阱: 注册
video_device 前必须确保 v4l2_device 已经注册成功,否则会导致设备节点无法正确关联到顶层设备。
2.3 v4l2_subdev:子设备抽象
v4l2_subdev 是V4L2框架中用于表示硬件子组件的结构体。一个Camera模组通常包含多个子设备:图像传感器(sensor)、ISP(图像信号处理器)、自动对焦控制器等。
| 成员 | 类型 | 说明 |
|---|---|---|
name |
char[32] |
子设备名称,如 "imx219 0-0010" |
grp_id |
u32 |
分组ID,用于将多个子设备归为一组 |
ops |
struct v4l2_subdev_ops * |
子设备操作函数集(core/video/pad等) |
ctrl_handler |
struct v4l2_ctrl_handler * |
子设备级别的控制句柄 |
v4l2_dev |
struct v4l2_device * |
指向所属的顶层v4l2_device |
owner |
struct module * |
所属内核模块 |
2.4 三者协作流程
在Camera驱动初始化过程中,三个结构体的协作顺序如下:
- 注册顶层设备:调用
v4l2_device_register(),创建v4l2_device实例。 - 注册子设备:调用
v4l2_subdev_register(),将sensor、ISP等子设备挂载到顶层设备。 - 注册视频节点:调用
video_register_device(),创建video_device并关联到顶层设备。 - 建立数据通路:通过
media_entity框架连接子设备的pad,形成完整的图像数据流。
2.5 代码示例:驱动初始化骨架
以下是一个典型的Camera驱动probe函数中三个核心结构体的注册顺序:
static int camera_probe(struct platform_device *pdev)
{
struct camera_dev *cam;
int ret;
// 1. 分配设备私有数据结构
cam = devm_kzalloc(&pdev->dev, sizeof(*cam), GFP_KERNEL);
if (!cam)
return -ENOMEM;
// 2. 注册顶层 v4l2_device
ret = v4l2_device_register(&pdev->dev, &cam->v4l2_dev);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to register v4l2_device\n");
return ret;
}
// 3. 注册子设备(例如 sensor)
ret = v4l2_subdev_register(&cam->v4l2_dev, &cam->sensor_subdev);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to register sensor subdev\n");
goto err_unregister_v4l2;
}
// 4. 初始化 video_device
cam->vdev = video_device_alloc();
if (!cam->vdev) {
ret = -ENOMEM;
goto err_unregister_subdev;
}
strscpy(cam->vdev->name, "camera", sizeof(cam->vdev->name));
cam->vdev->v4l2_dev = &cam->v4l2_dev;
cam->vdev->fops = &camera_fops;
cam->vdev->ioctl_ops = &camera_ioctl_ops;
cam->vdev->release = video_device_release;
// 5. 注册 video_device
ret = video_register_device(cam->vdev, VFL_TYPE_VIDEO, -1);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to register video device\n");
goto err_free_vdev;
}
dev_info(&pdev->dev, "Camera driver initialized successfully\n");
return 0;
err_free_vdev:
video_device_release(cam->vdev);
err_unregister_subdev:
v4l2_subdev_unregister(&cam->sensor_subdev);
err_unregister_v4l2:
v4l2_device_unregister(&cam->v4l2_dev);
return ret;
}
实战建议: 在编写驱动时,务必按照
v4l2_device → v4l2_subdev → video_device 的顺序注册,反序注销。使用 devm_ 系列API可以简化资源管理,但要注意 v4l2_device 的注销时机。
2.6 本章小结
V4L2框架通过三个核心结构体实现了清晰的设备分层模型:
- v4l2_device:顶层容器,代表物理设备,管理子设备和视频节点。
- video_device:用户空间接口,提供
/dev/videoX设备节点。 - v4l2_subdev:硬件子组件抽象,如sensor、ISP等。