一、Camera V4L2 驱动层分析
Linux系统中视频输入设备主要包括以下四个部分:
1.字符设备驱动:V4L2本身就是一个字符设备,具有字符设备所有的特性,暴露接口给用户空间;
2.V4L2驱动核心:主要是构建一个内核中标准视频设备驱动的框架,为视频操作提供统一的接口函数;
3.平台V4L2设备驱动:在V4L2框架下,根据平台自身的特性实现与平台相关的V4L2驱动部分,包括注册video_device和v4l2_dev;
4.具体的sensor驱动:主要上电、提供工作时钟、视频图像裁剪、流IO开启等,实现各种设备控制方法供上层调用并注册v4l2_subdev。
V4L2核心源码位于drivers/media/v4l2-core,根据功能可以划分为四类:
1.字符设备模块:由v4l2-dev.c实现,主要作用申请字符主设备号、注册class和提供video device注册注销等相关函数。
2.V4L2基础框架:由v4l2-device.c、v4l2-subdev.c、v4l2-fh.c、v4l2-ctrls.c等文件构建V4L2基础框架。
3.videobuf管理
由videobuf2-core.c、videobuf2-dma-contig.c、videobuf2-dma-sg.c、videobuf2-memops.c、videobuf2-vmalloc.c、v4l2-mem2mem.c等文件实现,完成videobuffer的分配、管理和注销。
4.Ioctl框架:由v4l2-ioctl.c文件实现,构建V4L2ioctl的框架。
-
创建v4l2_device结构体,填充信息,通过v4l2_device_register方法向系统注册并且创建video设备节点。 //“kernel/msm-4.19/drivers/media/v4l2-core/v4l2-device.c”
-
创建media_device结构体,填充信息,通过media_device_register向系统注册,并创建media设备节点,并将其赋值给v4l2_device中的mdev。 //“kernel/msm-4.19/drivers/media/media-device.c”
-
创建v4l2_subdev结构体,填充信息,通过v4l2_device_register_subdev向系统注册,并将其挂载到v4l2_device设备中 //“kernel/msm-4.19/drivers/media/v4l2-core/v4l2-device.c”
-
创建对应的media_entity,并通过media_device_register_entity方法其添加到media controller中进行统一管理。 //“kernel/msm-4.19/drivers/media/media-device.c”
二、V4L2基础框架
2.1 /media/v4l2-core/v4l2-dev.c
在该文件中,主要是负责创建/sys/classs/video4linux目录 ,当有设备注册进来时,创建对应的 /dev/videox 、/dev/vbix、/dev/radiox、/dev/subdevx等节点。
主要工作如下:
1.将字符设备号(81,0)到(81,255)这期间256个字次设备号,均申请为 v4l2 使用,name=video4linux
2.注册 /sys/classs/video4linux目录
@ kernel/msm-4.4/drivers/media/v4l2-core/v4l2-dev.c
static struct class video_class = {
.name = VIDEO_NAME, // video4linux
.dev_groups = video_device_groups,
};
static int __init videodev_init(void)
{
dev_t dev = MKDEV(VIDEO_MAJOR, 0); // VIDEO_MAJOR: 81
printk(KERN_INFO "Linux video capture interface: v2.00\n");
// 1. 将字符设备号(81,0) 到 (81,255) 这期间256个字次设备号,均申请为 v4l2 使用,name=video4linux
ret = register_chrdev_region(dev, VIDEO_NUM_DEVICES, VIDEO_NAME); //VIDEO_NUM_DEVICES: 256 VIDEO_NAME:"video4linux"
======> int register_chrdev_region(dev_t from, unsigned count, const char *name)
// 2. 注册 /sys/classs/video4linux 目录
ret = class_register(&video_class);
return 0;
}
2.2 注册V4L2设备 __video_register_device()
当用设备需要注册为 v4l2 subdev 时,会调用video_register_device()函数进行注册:
"kernel/msm-4.19/drivers/media/platform/msm/camera_v2/camera/camera.c"
int camera_init_v4l2(struct device *dev, unsigned int *session)
{
struct msm_video_device *pvdev;
struct v4l2_device *v4l2_dev = NULL;
int rc = 0;
pvdev = kzalloc(sizeof(struct msm_video_device),
GFP_KERNEL);
if (WARN_ON(!pvdev)) {
rc = -ENOMEM;
goto init_end;
}
pvdev->vdev = video_device_alloc(); //分配video_device内存
if (WARN_ON(!pvdev->vdev)) {
rc = -ENOMEM;
goto video_fail;
}
v4l2_dev = kzalloc(sizeof(struct v4l2_device), GFP_KERNEL); //分配v4l2_dev 内存
if (WARN_ON(!v4l2_dev)) {
rc = -ENOMEM;
goto v4l2_fail;
}
#if defined(CONFIG_MEDIA_CONTROLLER)
v4l2_dev->mdev = kzalloc(sizeof(struct media_device), //分配media_device 内存
GFP_KERNEL);
if (!v4l2_dev->mdev) {
rc = -ENOMEM;
goto mdev_fail;
}
media_device_init(v4l2_dev->mdev);
strlcpy(v4l2_dev->mdev->model, MSM_CAMERA_NAME,
sizeof(v4l2_dev->mdev->model)); //model 为msm_camera
v4l2_dev->mdev->dev = dev;
rc = media_device_register(v4l2_dev->mdev); //media_device 注册
if (WARN_ON(rc < 0))
goto media_fail;
rc = media_entity_pads_init(&pvdev->vdev->entity, 0, NULL); //建立media_entity与media_pad之间的链接:
if (WARN_ON(rc < 0))
goto entity_fail;
pvdev->vdev->entity.function = QCAMERA_VNODE_GROUP_ID;
#endif
v4l2_dev->notify = NULL;
pvdev->vdev->v4l2_dev = v4l2_dev;
rc = v4l2_device_register(dev, pvdev->vdev->v4l2_dev); // 设置父设备为dev ,信息根据传入参数. 例如:dv4l2_dev->name =qcom,camera ca0c000.qcom,cci:qcom,c
if (WARN_ON(rc < 0))
goto register_fail;
strlcpy(pvdev->vdev->name, "msm-sensor", sizeof(pvdev->vdev->name));
pvdev->vdev->release = video_device_release;
pvdev->vdev->fops = &camera_v4l2_fops; // 配置 video_device 的字符设备操作函数
pvdev->vdev->ioctl_ops = &camera_v4l2_ioctl_ops; // 配置 v4l2 IOCTRL
pvdev->vdev->minor = -1;
pvdev->vdev->vfl_type = VFL_TYPE_GRABBER;
pvdev->vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
rc = video_register_device(pvdev->vdev,
VFL_TYPE_GRABBER, -1); 调用__video_register_device()
kernel/msm-4.19/drivers/media/platform/msm/camera_v2/msm.c
static int msm_probe(struct platform_device *pdev)
{
struct msm_video_device *pvdev = NULL;
static struct dentry *cam_debugfs_root;
int rc = 0;
// 1. 初始化一个 v4l2_device 类型的结构体,并分配好结构体内存
msm_v4l2_dev = kzalloc(sizeof(*msm_v4l2_dev),
GFP_KERNEL);
if (WARN_ON(!msm_v4l2_dev)) {
rc = -ENOMEM;
goto probe_end;
}
pvdev = kzalloc(sizeof(struct msm_video_device),
GFP_KERNEL);
if (WARN_ON(!pvdev)) {
rc = -ENOMEM;
goto pvdev_fail;
}
// 2. 分配 video_device 结构体内存
pvdev->vdev = video_device_alloc();
if (WARN_ON(!pvdev->vdev)) {
rc = -ENOMEM;
goto video_fail;
}
#if defined(CONFIG_MEDIA_CONTROLLER)
// 3. 分配 media_device 结构体内存
msm_v4l2_dev->mdev = kzalloc(sizeof(struct media_device),
GFP_KERNEL);
if (!msm_v4l2_dev->mdev) {
rc = -ENOMEM;
goto mdev_fail;
}
// 4.初始化 media_device 结构体
media_device_init(msm_v4l2_dev->mdev);
strlcpy(msm_v4l2_dev->mdev->model, MSM_CONFIGURATION_NAME,sizeof(msm_v4l2_dev->mdev->model)); //MSM_CONFIGURATION_NAME = "msm_config" 代码中open 节点,会比较是否为smsm_config
msm_v4l2_dev->mdev->dev = &(pdev->dev);
// 5. 注册 media_device , 使用的 v4l2
rc = media_device_register(msm_v4l2_dev->mdev);
/**
media_device_register()
media_devnode_register ()
device_initialize() //初始化media 创建mediaX
cdev_init() //初始化字符设备
cdev_device_add() //
device_create_file(&devnode->dev, &dev_attr_model) // 创建的节点 sys/devices/platform/soc/ca00000.qcom,msm-cam/media0/model
*/
if (WARN_ON(rc < 0))
goto media_fail;
if (WARN_ON((rc == media_entity_pads_init(&pvdev->vdev->entity,
0, NULL)) < 0))
goto entity_fail;
pvdev->vdev->entity.function = QCAMERA_VNODE_GROUP_ID;
#endif
msm_v4l2_dev->notify = msm_sd_notify;
pvdev->vdev->v4l2_dev = msm_v4l2_dev;
// 6. 设置父设备为 pdev->dev (也就是 qcom,msm-cam 的设备信息)
rc = v4l2_device_register(&(pdev->dev), pvdev->vdev->v4l2_dev);
/**
int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)
{
if (v4l2_dev == NULL)
return -EINVAL;
INIT_LIST_HEAD(&v4l2_dev->subdevs);
spin_lock_init(&v4l2_dev->lock);
v4l2_prio_init(&v4l2_dev->prio);
kref_init(&v4l2_dev->ref);
get_device(dev);
v4l2_dev->dev = dev;
if (dev == NULL) {
/* If dev == NULL, then name must be filled in by the caller */
if (WARN_ON(!v4l2_dev->name[0]))
return -EINVAL;
return 0;
}
/* Set name to driver name + device name if it is empty. */
if (!v4l2_dev->name[0])
snprintf(v4l2_dev->name, sizeof(v4l2_dev->name), "%s %s",
dev->driver->name, dev_name(dev));
printk("v4l2_dev->name =%s \n",v4l2_dev->name); //log 信息v4l2_dev->name =msm ca00000.qcom,msm-cam . dev->driver->name 驱动中设置的名字,dev_name(dev) dtsi 中的lable
if (!dev_get_drvdata(dev))
dev_set_drvdata(dev, v4l2_dev);
return 0;
}
*/
if (WARN_ON(rc < 0))
goto register_fail;
// 7. 注册 video_device设备
strlcpy(pvdev->vdev->name, "msm-config", sizeof(pvdev->vdev->name));
pvdev->vdev->release = video_device_release;
pvdev->vdev->fops = &msm_fops;
pvdev->vdev->ioctl_ops = &g_msm_ioctl_ops;
pvdev->vdev->minor = -1;
pvdev->vdev->vfl_type = VFL_TYPE_GRABBER;
rc = video_register_device(pvdev->vdev,
VFL_TYPE_GRABBER, -1); // 节点 /dev/vdieoX
2.3 __video_register_device
以"qcom,msm-cam"为例,其注册时,传递的 nr = -1,说明从第一个开始分配,也就是 /dev/video0。
但是如果有其他先执行video_register_device . /dev/video0 可以是其他值. 可以查看节点
/sys/class/video4linux # cat video0/name
sde_rotator
因为"platform/msm/sde/rotator/sde_rotator_dev.c" 先执行,传入的nr = -1, so /dev/video0 为sde_rotator
kernel/msm-4.4/drivers/media/v4l2-core/v4l2-dev.c
/**
* __video_register_device - register video4linux devices
* @vdev: video device structure we want to register
* @type: type of device to register
* @nr: which device node number (0 == /dev/video0, 1 == /dev/video1, ... -1 == first free)
* @warn_if_nr_in_use: warn if the desired device node number was already in use and another number was chosen instead.
* @owner: module that owns the video device node
*
* The registration code assigns minor numbers and device node numbersbased on the requested type and registers the new device node with the kernel.
*
* This function assumes that struct video_device was zeroed when it was allocated and does not contain any stale date.
*
* An error is returned if no free minor or device node number could be found, or if the registration of the device node failed.
*
* Zero is returned on success.
*
* Valid types are
* %VFL_TYPE_GRABBER - A frame grabber
* %VFL_TYPE_VBI - Vertical blank data (undecoded)
* %VFL_TYPE_RADIO - A radio card
* %VFL_TYPE_SUBDEV - A subdevice
* %VFL_TYPE_SDR - Software Defined Radio
*/
int __video_register_device(struct video_device *vdev, int type, int nr, int warn_if_nr_in_use, struct module *owner)
{
int minor_cnt = VIDEO_NUM_DEVICES;
const char *name_base;
/* A minor value of -1 marks this video device as never having been registered */
vdev->minor = -1;
// 1. 初始化 fh->list
/* v4l2_fh support */
INIT_LIST_HEAD(&vdev->fh_list);
// 2. 检查设备类型
/* Part 1: check device type */
switch (type) {
case VFL_TYPE_GRABBER: name_base = "video"; break;
case VFL_TYPE_VBI: name_base = "vbi"; break;
case VFL_TYPE_RADIO: name_base = "radio"; break;
case VFL_TYPE_SUBDEV: name_base = "v4l-subdev";break;
case VFL_TYPE_SDR: name_base = "swradio"; break; /* Use device name 'swradio' because 'sdr' was already taken. */
}
vdev->vfl_type = type; // VFL_TYPE_GRABBER
vdev->cdev = NULL;
// 3. 寻找一个不在使用的 次设备号, 主设备号为 81,(0~63 为video)(128,191 为sub-dev)
/* Part 2: find a free minor, device node number and device index. */
/* Keep the ranges for the first four types for historical reasons.
* Newer devices (not yet in place) should use the range of 128-191 and just pick the first free minor there (new style). */
switch (type) {
case VFL_TYPE_GRABBER: minor_offset = 0; minor_cnt = 64; break;
case VFL_TYPE_RADIO: minor_offset = 64; minor_cnt = 64; break;
case VFL_TYPE_VBI: minor_offset = 224; minor_cnt = 32; break;
default: minor_offset = 128; minor_cnt = 64; break;
}
/* Pick a device node number */
nr = devnode_find(vdev, nr == -1 ? 0 : nr, minor_cnt);
if (nr == minor_cnt)
nr = devnode_find(vdev, 0, minor_cnt);
/* The device node number and minor numbers are independent, so
we just find the first free minor number. */
for (i = 0; i < VIDEO_NUM_DEVICES; i++)
if (video_device[i] == NULL)
break;
vdev->minor = i + minor_offset;
vdev->num = nr;
devnode_set(vdev);
// 4. 获取 index,将当前需要注册的 video_device 设备保存在 video_device[]全局数组中
vdev->index = get_index(vdev);
video_device[vdev->minor] = vdev;
// 5. 分配对应的字符设备 /dev/video0,字符设备号,就是前面的 (81,minor)
/* Part 3: Initialize the character device */
vdev->cdev = cdev_alloc();
vdev->cdev->ops = &v4l2_fops;
vdev->cdev->owner = owner;
ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
// 6. 分配对应的sys节点 /sys/class/video4linux/video0
/* Part 4: register the device with sysfs */
vdev->dev.class = &video_class;
vdev->dev.devt = MKDEV(VIDEO_MAJOR, vdev->minor);
vdev->dev.parent = vdev->dev_parent;
dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num);
ret = device_register(&vdev->dev);
// 7. 注册release 时调用的函数
/* Register the release callback that will be called when the last reference to the device goes away. */
vdev->dev.release = v4l2_device_release;
/* Increase v4l2_device refcount */
v4l2_device_get(vdev->v4l2_dev);
// 8. 将该 v4l2 subdevice 当成一个 entity 注册到 media device
/* Part 5: Register the entity. */
if (vdev->v4l2_dev->mdev && vdev->vfl_type != VFL_TYPE_SUBDEV) {
vdev->entity.type = MEDIA_ENT_T_DEVNODE_V4L;
vdev->entity.name = vdev->name;
vdev->entity.info.dev.major = VIDEO_MAJOR;
vdev->entity.info.dev.minor = vdev->minor;
ret = media_device_register_entity(vdev->v4l2_dev->mdev,&vdev->entity);
}
/* Part 6: Activate this minor. The char device can now be used. */
set_bit(V4L2_FL_REGISTERED, &vdev->flags);
return 0;
}
EXPORT_SYMBOL(__video_register_device);
2.3.1 字符设备操作函数 v4l2_fops
static const struct file_operations v4l2_fops = {
.owner = THIS_MODULE,
.read = v4l2_read,
.write = v4l2_write,
.open = v4l2_open,
.get_unmapped_area = v4l2_get_unmapped_area,
.mmap = v4l2_mmap,
.unlocked_ioctl = v4l2_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = v4l2_compat_ioctl32,
#endif
.release = v4l2_release,
.poll = v4l2_poll,
.llseek = no_llseek,
};
创建成功 /dev/video0 节点后,后续要打开对应的节点时,会调用 fops对应的操作函数,对应的代码在注册时赋值的。
"kernel/msm-4.19/drivers/media/platform/msm/camera_v2/msm.c"
strlcpy(pvdev->vdev->name, "msm-config", sizeof(pvdev->vdev->name));
pvdev->vdev->release = video_device_release;
pvdev->vdev->fops = &msm_fops;
pvdev->vdev->ioctl_ops = &g_msm_ioctl_ops;
static struct v4l2_file_operations msm_fops = {
.owner = THIS_MODULE,
.open = msm_open,
.poll = msm_poll,
.release = msm_close,
.unlocked_ioctl = video_ioctl2,
#ifdef CONFIG_COMPAT
.compat_ioctl32 = video_ioctl2,
#endif
};
"kernel/msm-4.19/drivers/media/platform/msm/camera_v2/camera/camera.c"
strlcpy(pvdev->vdev->name, "msm-sensor", sizeof(pvdev->vdev->name));
pvdev->vdev->release = video_device_release;
pvdev->vdev->fops = &camera_v4l2_fops;
pvdev->vdev->ioctl_ops = &camera_v4l2_ioctl_ops;
static struct v4l2_file_operations camera_v4l2_fops = {
.owner = THIS_MODULE,
.open = camera_v4l2_open,
.poll = camera_v4l2_poll,
.release = camera_v4l2_close,
.unlocked_ioctl = video_ioctl2,
#ifdef CONFIG_COMPAT
.compat_ioctl32 = camera_v4l2_compat_ioctl,
#endif
};
log 信息
02-11 06:44:53.201 0 0 W : v4l2_open
02-11 06:44:53.201 0 0 W : msm_open
02-11 06:44:54.756 0 0 W : v4l2_open
02-11 06:44:54.756 0 0 W : camera_v4l2_open
根据open 不同的节点调用不同的v4l2_file_operations
2.3.2 v4l2_ioctrl
kernel/msm-4.4/drivers/media/v4l2-core/v4l2-compat-ioctl32.c //iotctrl 同理open ,有默认的v4l2 的 ioctrl , 有对应驱动的ioctrl
long v4l2_compat_ioctl32(struct file *file, unsigned int cmd, unsigned long arg)
{
if (_IOC_TYPE(cmd) == 'V' && _IOC_NR(cmd) < BASE_VIDIOC_PRIVATE)
ret = do_video_ioctl(file, cmd, arg);
else if (vdev->fops->compat_ioctl32)
ret = vdev->fops->compat_ioctl32(file, cmd, arg);
return ret;
}
2.4 注册子设备 /media/v4l2-core/v4l2-subdev.c
当有sub-dev 需要注册到v4l2 时,调用 v4l2_device_register_subdev()函数。
最终调用 __video_register_device(),传递参数 VFL_TYPE_SUBDEV,说明是注册 sub_dev 设备。
参考:https://blog.csdn.net/Ciellee/article/details/105483079