V4L2 驱动层分析

一、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

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值