4. RK V4L2框架入门:核心数据结构与ioctl流程

在RK平台Sensor驱动开发中,V4L2(Video for Linux 2)是贯穿始终的核心框架。本章将深入剖析V4L2的三大核心数据结构——video_device、v4l2_device、v4l2_subdev,并详细解析ioctl调用在用户空间与内核驱动之间的完整流转路径。理解这些内容是编写正确、高效的Sensor驱动的基石。

学习提示: V4L2框架采用“设备-子设备”分层模型。在RK平台上,v4l2_device代表整个摄像头子系统,v4l2_subdev代表Sensor、ISP等独立硬件单元,而video_device则是用户空间操作/dev/video*节点的接口。三者协同工作,缺一不可。

4.1 V4L2核心数据结构全景

V4L2框架围绕三个核心结构体构建,它们之间的关系可以用下面的架构图清晰展示:

V4L2核心数据结构关系图 V4L2核心数据结构关系图 v4l2_device 摄像头子系统根设备 video_device /dev/video* 字符设备 提供ioctl接口给用户空间 v4l2_subdev Sensor / ISP 子设备 实现具体硬件控制逻辑 注册/管理 注册/管理 通过v4l2_device桥接 用户空间应用程序 open / ioctl / mmap / close 系统调用

4.2 三大核心数据结构详解

4.2.1 v4l2_device —— 子系统根容器

v4l2_device是V4L2框架的顶层结构体,代表一个完整的视频采集设备。在RK平台上,通常一个Camera Sensor对应一个v4l2_device实例。它的主要职责是管理所有关联的v4l2_subdev和video_device。

成员 类型 说明
dev struct device * 指向内核通用设备结构体,用于设备模型集成
subdevs struct list_head 链表头,挂载所有注册到此设备的v4l2_subdev
ctrl_handler struct v4l2_ctrl_handler * 控制句柄,管理曝光、增益等控制参数
name char[32] 设备名称,用于标识和调试
notify void (*notify)() 通知回调,子设备通过此回调向主设备发送事件
RK平台实践: 在RK驱动中,v4l2_device通常在probe函数中通过v4l2_device_register()注册。例如RKISP驱动会创建一个v4l2_device,然后将Sensor的v4l2_subdev和ISP的v4l2_subdev都注册到它下面。

4.2.2 video_device —— 用户空间接口

video_device是用户空间与内核驱动交互的桥梁。它创建一个字符设备节点(如/dev/video0),并注册一组file_operations,其中最重要的就是ioctl操作。

// video_device 核心结构(简化版)
struct video_device {
    const struct v4l2_file_operations *fops;  // 文件操作函数集
    struct v4l2_device *v4l2_dev;             // 所属的v4l2_device
    struct device *dev;                       // 字符设备
    int index;                                // 设备节点编号
    unsigned long flags;                      // 设备标志
    // ... 其他成员
};

// v4l2_file_operations 定义
struct v4l2_file_operations {
    struct module *owner;
    long (*ioctl) (struct file *file, unsigned int cmd, unsigned long arg);
    int (*mmap) (struct file *file, struct vm_area_struct *vma);
    __poll_t (*poll) (struct file *file, struct poll_table_struct *wait);
    // ... 其他操作
};

4.2.3 v4l2_subdev —— 硬件抽象层

v4l2_subdev是V4L2框架中最重要的抽象层,用于表示一个独立的硬件子模块。在RK Sensor驱动中,Sensor本身就是一个v4l2_subdev。它通过v4l2_subdev_ops结构体向框架暴露操作接口。

操作组 回调函数 功能描述
core s_power / ioctl 电源管理、自定义ioctl
video s_stream / g_frame_interval 视频流控制、帧率获取
pad get_fmt / set_fmt / enum_mbus_code 媒体总线格式协商
ctrl s_ctrl / g_ctrl 控制参数(曝光、增益等)读写
常见误区: 初学者容易混淆video_device和v4l2_subdev。记住:video_device是用户空间看到的“设备文件”,而v4l2_subdev是内核内部管理的“硬件单元”。一个video_device可以对应多个v4l2_subdev(如Sensor + ISP)。

4.3 V4L2 ioctl调用流程详解

ioctl是用户空间与V4L2驱动交互的主要方式。理解ioctl的完整调用流程,对于调试驱动问题至关重要。下面是一个典型的ioctl调用流程图:

V4L2 ioctl调用流程图 V4L2 ioctl调用流程图 用户空间应用程序 ioctl(fd, VIDIOC_*, arg) VFS 系统调用层 sys_ioctl() video_device->fops->ioctl v4l2_ioctl() 分发 V4L2 核心框架 video_ioctl2() 驱动具体实现 sensor_subdev_ops 关键路径 ioctl → VFS → video_device → v4l2核心 → subdev ops

4.3.1 ioctl调用详细步骤

  1. 用户空间发起调用: 应用程序调用 ioctl(fd, VIDIOC_S_FMT, &fmt),其中 fd 是 /dev/video0 的文件描述符。
  2. VFS层路由: 内核VFS根据 fd 找到对应的 struct file,调用其 file_operations 中的 unlocked_ioctl 函数指针。
  3. video_device分发: video_device 注册的 v4l2_ioctl 被调用。该函数根据命令号(如 VIDIOC_S_FMT)进行初步分类。
  4. V4L2核心处理: video_ioctl2() 函数接管,通过查表找到对应的处理函数。对于需要子设备参与的命令,会遍历 v4l2_device 下的所有 v4l2_subdev。
  5. 驱动实现: 最终调用到Sensor驱动注册的 v4l2_subdev_ops 中的具体回调函数,如 set_fmt、s_stream 等。

4.3.2 关键ioctl命令分类

命令类别 示例命令 功能描述
格式协商 VIDIOC_S_FMT / VIDIOC_G_FMT 设置/获取图像格式(分辨率、像素格式)
流控制 VIDIOC_STREAMON / VIDIOC_STREAMOFF 启动/停止视频流
缓冲区管理 VIDIOC_REQBUFS / VIDIOC_QBUF / VIDIOC_DQBUF 请求缓冲区、入队/出队缓冲区
控制参数 VIDIOC_S_CTRL / VIDIOC_G_CTRL 设置/获取曝光、增益等控制参数
查询能力 VIDIOC_QUERYCAP / VIDIOC_ENUM_FMT 查询设备能力、枚举支持的格式
调试技巧: 在RK平台开发中,可以使用 v4l2-ctl 工具从用户空间直接发送ioctl命令,快速验证驱动功能。例如:v4l2-ctl -d /dev/video0 --set-fmt-video=width=1920,height=1080,pixelformat=NV12 可以测试格式设置是否正常。

4.4 RK平台V4L2驱动初始化流程

在RK平台上,Sensor驱动的初始化流程遵循标准的V4L2子设备注册模式。下面是一个典型的初始化序列:

// RK Sensor驱动 probe 函数核心流程
static int sensor_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    struct sensor_dev *sensor;
    int ret;

    // 1. 分配并初始化私有数据结构
    sensor = devm_kzalloc(&client->dev, sizeof(*sensor), GFP_KERNEL);
    
    // 2. 初始化 v4l2_subdev
    v4l2_i2c_subdev_init(&sensor->sd, client, &sensor_subdev_ops);
    sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
    
    // 3. 设置 pad 和媒体总线格式
    sensor->pad.flags = MEDIA_PAD_FL_SOURCE;
    media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad);
    
    // 4. 注册控制句柄(曝光、增益等)
    v4l2_ctrl_handler_init(&sensor->ctrl_handler, 5);
    sensor->ctrl = v4l2_ctrl_new_std(&sensor->ctrl_handler,
                                     &sensor_ctrl_ops,
                                     V4L2_CID_EXPOSURE, 0, 65535, 1, 1000);
    sensor->sd.ctrl_handler = &sensor->ctrl_handler;
    
    // 5. 注册子设备到 V4L2 框架
    ret = v4l2_async_register_subdev(&sensor->sd);
    
    return ret;
}
RK平台差异点: 在RK平台上,Sensor驱动通常通过 v4l2_async_register_subdev() 进行异步注册,而不是直接调用 v4l2_device_register_subdev()。这是因为RK的ISP驱动会先注册 v4l2_device,然后等待所有Sensor子设备异步注册完成后,再进行绑定和初始化。

4.5 本章小结

  • v4l2_device 是顶层容器,管理所有子设备和视频设备。
  • video_device 提供用户空间接口,通过 /dev/video* 节点暴露。
  • v4l2_subdev 是硬件抽象层,Sensor驱动主要实现此接口。
  • ioctl调用路径:用户空间 → VFS → video_device → V4L2核心 → subdev ops。
  • RK平台使用异步注册机制,Sensor驱动通过 v4l2_async_register_subdev() 注册。