v4l2_subdev 与 /dev/videoX 的关联
1. 核心组件关联流程
步骤1:传感器驱动注册 v4l2_subdev
传感器驱动(如 ov5640.c
)在 probe 阶段创建并注册 v4l2_subdev
:
// drivers/media/i2c/ov5640.c
static int ov5640_probe(struct i2c_client *client)
{struct v4l2_subdev *sd;struct ov5640 *ov5640;// 分配subdev结构sd = devm_kzalloc(&client->dev, sizeof(*sd), GFP_KERNEL);// 初始化subdevv4l2_i2c_subdev_init(sd, client, &ov5640_subdev_ops);// 设置subdev名称和IDsnprintf(sd->name, sizeof(sd->name), "ov5640 %s", dev_name(&client->dev));// 注册subdev到V4L2核心err = v4l2_async_register_subdev(sd);// 实现subdev操作集static const struct v4l2_subdev_ops ov5640_subdev_ops = {.core = &ov5640_core_ops,.video = &ov5640_video_ops,.pad = &ov5640_pad_ops,};
}
关键点:
- 通过
v4l2_i2c_subdev_init()
绑定 I2C 客户端 - 实现
v4l2_subdev_ops
提供传感器控制接口 - 最终通过
v4l2_async_register_subdev()
注册到 V4L2 子系统
步骤2:桥梁驱动创建 video_device
SoC 的 CSI 接收器驱动(如 sun6i_csi.c
)创建顶层设备:
// drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c
static int sun6i_csi_probe(struct platform_device *pdev)
{struct sun6i_csi *csi;// 分配video_devicecsi->vdev = video_device_alloc();// 配置video设备属性csi->vdev->fops = &sun6i_csi_fops;csi->vdev->ioctl_ops = &sun6i_csi_ioctl_ops;csi->vdev->v4l2_dev = &csi->v4l2_dev;csi->vdev->queue = &csi->queue;strscpy(csi->vdev->name, "sun6i-csi", sizeof(csi->vdev->name));// 注册video设备ret = video_register_device(csi->vdev, VFL_TYPE_VIDEO, -1);
}
关键点:
video_device_alloc()
分配设备结构- 实现
fops
和ioctl_ops
处理用户空间调用 video_register_device()
生成/dev/videoX
节点
步骤3:建立 subdev 与 video_device 的关联
通过 media controller 或 async subdev 机制建立连接:
// 异步匹配示例 (drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c)
static int sun6i_csi_subdev_notifier_bound(struct v4l2_async_notifier *notifier,struct v4l2_subdev *subdev,struct v4l2_async_subdev *asd)
{struct sun6i_csi *csi = container_of(notifier, struct sun6i_csi, notifier);// 保存传感器subdev引用csi->sensor_sd = subdev;// 建立数据流链接media_create_pad_link(&subdev->entity, SENSOR_PAD_SRC,&csi->vdev->entity, CSI_PAD_SINK,MEDIA_LNK_FL_ENABLED);
}
步骤4:用户操作传递路径
当用户操作 /dev/videoX
时:
// 用户调用ioctl(VIDIOC_S_FMT)
sun6i_csi_ioctl_s_fmt(struct file *file, void *priv, struct v4l2_format *f)
{struct sun6i_csi *csi = video_drvdata(file);// 通过subdev调用传感器驱动struct v4l2_subdev_format fmt = {.which = V4L2_SUBDEV_FORMAT_ACTIVE,.format = /* 转换格式 */,};// 关键调用链:video_device -> subdevv4l2_subdev_call(csi->sensor_sd, pad, set_fmt, NULL, &fmt);
}
2. 关键关联机制
(1) 媒体控制器(Media Controller)
- 实体通过
media_entity
结构表示 - 使用
media_create_pad_link()
建立数据流路径
(2) 异步子设备注册
// 桥梁驱动配置异步匹配
static const struct v4l2_async_notifier_operations csi_async_ops = {.bound = sun6i_csi_subdev_notifier_bound,
};// 添加异步描述符
v4l2_async_notifier_add_subdev(&csi->notifier, &asd);
(3) 操作转发机制
// 核心转发函数 (drivers/media/v4l2-core/v4l2-subdev.c)
long v4l2_subdev_call(struct v4l2_subdev *sd, int o, int f, ...)
{// 通过函数指针调用具体实现if (sd && sd->ops->o && sd->ops->o->f)return sd->ops->o->f(sd, ...);
}
4. 典型场景示例
用户空间打开摄像头:
open("/dev/video0")
→ 调用video_device->fops.open()
- 桥梁驱动通过
v4l2_subdev_call(sensor_sd, core, s_power, 1)
开启传感器 - 传感器驱动通过 I2C 发送上电序列
设置分辨率:
ioctl(VIDIOC_S_FMT)
→video_device->ioctl_ops.vidioc_s_fmt_vid_cap()
- 桥梁驱动调用
v4l2_subdev_call(sensor_sd, video, s_fmt, &fmt)
- 传感器驱动通过 I2C 配置寄存器实现分辨率切换
5. 关键数据结构关系
struct video_device {const struct v4l2_file_operations *fops; // 文件操作struct v4l2_ioctl_ops *ioctl_ops; // ioctl操作struct vb2_queue *queue; // 缓冲区队列struct media_entity entity; // 媒体实体
};struct v4l2_subdev {const struct v4l2_subdev_ops *ops; // 子设备操作集struct v4l2_subdev_video_ops *video; // 视频操作struct media_entity entity; // 媒体实体struct v4l2_async_subdev *asd; // 异步描述
};
总结:关联本质
-
层级关系
/dev/videoX
→video_device
(桥梁驱动) →v4l2_subdev
(传感器) -
控制流
用户空间操作 → V4L2 核心 → 桥梁驱动 → 通过v4l2_subdev_call()
转发 → 传感器驱动 -
数据流
传感器 → MIPI CSI-2 → CSI 接收器 → DMA →vb2
缓冲区 → 用户空间 -
配置依赖
所有传感器控制(分辨率/帧率等)最终通过v4l2_subdev_ops
中的 I2C 操作实现