可以看到v4l2_device中有一个v4l2_subdev的链表,v4l2_device的主要目的时用来管理v4l2_subdev

  • v4l2_subdev

    struct v4l2_subdev {
    	struct list_head list;
    	struct v4l2_device *v4l2_dev;
    	const struct v4l2_subdev_ops *ops;
    };
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
  • v4l2_subdev中有一个v4l2_subdev_ops,实现了一系列的操作,供v4l2_device调用

    struct v4l2_subdev_ops {
    	const struct v4l2_subdev_core_ops	*core;
    	const struct v4l2_subdev_tuner_ops	*tuner;
    	const struct v4l2_subdev_audio_ops	*audio;
    	const struct v4l2_subdev_video_ops	*video;
    	const struct v4l2_subdev_vbi_ops	*vbi;
    	const struct v4l2_subdev_ir_ops		*ir;
    	const struct v4l2_subdev_sensor_ops	*sensor;
    };
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
    struct v4l2_subdev_core_ops {
        ...
    	int (*s_config)(struct v4l2_subdev *sd, int irq, void *platform_data);
    	int (*init)(struct v4l2_subdev *sd, u32 val);
    	int (*s_gpio)(struct v4l2_subdev *sd, u32 val);
    	int (*g_ctrl)(struct v4l2_subdev *sd, struct v4l2_control *ctrl);
    	int (*s_ctrl)(struct v4l2_subdev *sd, struct v4l2_control *ctrl);
    	int (*s_std)(struct v4l2_subdev *sd, v4l2_std_id norm);
    	long (*ioctl)(struct v4l2_subdev *sd, unsigned int cmd, void *arg);
    	...
    };
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
    struct v4l2_subdev_video_ops {
    	...
        int (*enum_fmt)(struct v4l2_subdev *sd, struct v4l2_fmtdesc *fmtdesc);
    	int (*g_fmt)(struct v4l2_subdev *sd, struct v4l2_format *fmt);
    	int (*try_fmt)(struct v4l2_subdev *sd, struct v4l2_format *fmt);
    	int (*s_fmt)(struct v4l2_subdev *sd, struct v4l2_format *fmt);
    	int (*cropcap)(struct v4l2_subdev *sd, struct v4l2_cropcap *cc);
    	int (*g_crop)(struct v4l2_subdev *sd, struct v4l2_crop *crop);
    	int (*s_crop)(struct v4l2_subdev *sd, struct v4l2_crop *crop);
    	int (*g_parm)(struct v4l2_subdev *sd, struct v4l2_streamparm *param);
    	int (*s_parm)(struct v4l2_subdev *sd, struct v4l2_streamparm *param);
    	...
    };
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

    2.2 V4L2提供的注册接口

    三、源码剖析

    3.1 V4L2驱动模板

    此示例中并没有设计到v4l2_subdev,这部分将会在后面分析具体的驱动中出现

    #include <...>
    
    static struct video_device* video_dev;
    static struct v4l2_device v4l2_dev;
    
    /* 实现各种系统调用 */
    static const struct v4l2_file_operations video_dev_fops = {
    	.owner		    = THIS_MODULE,
    	.release        = vdev_close,
    	.read           = vdev_read,
    	.poll		    = vdev_poll,
    	.ioctl          = video_ioctl2,
    	.mmap           = vdev_mmap,
    };
    
    /* 实现各种系统调用 */
    static const struct v4l2_ioctl_ops video_dev_ioctl_ops = {
    	.vidioc_querycap      = vidioc_querycap,
    	.vidioc_enum_fmt_vid_cap  = vidioc_enum_fmt_vid_cap,
    	.vidioc_g_fmt_vid_cap     = vidioc_g_fmt_vid_cap,
    	.vidioc_try_fmt_vid_cap   = vidioc_try_fmt_vid_cap,
    	.vidioc_s_fmt_vid_cap     = vidioc_s_fmt_vid_cap,
    	.vidioc_reqbufs       = vidioc_reqbufs,
    	.vidioc_querybuf      = vidioc_querybuf,
    	.vidioc_qbuf          = vidioc_qbuf,
    	.vidioc_dqbuf         = vidioc_dqbuf,
    	.vidioc_enum_input    = vidioc_enum_input,
    	.vidioc_g_input       = vidioc_g_input,
    	.vidioc_s_input       = vidioc_s_input,
    	.vidioc_streamon      = vidioc_streamon,
    	.vidioc_streamoff     = vidioc_streamoff,
    };
    
    static int __init video_init(void)
    {
        /* 分配并设置一个video_device */
        video_dev = video_device_alloc();
        video_dev->fops = &video_dev_fops;
        video_dev->ioctl_ops = &video_dev_ioctl_ops;
        video_dev->release = video_device_release;
        video_dev->tvnorms = V4L2_STD_525_60;
        video_dev->current_norm = V4L2_STD_NTSC_M;
    
        /* 注册一个v4l2_device */
        v4l2_device_register(video_dev->dev, &v4l2_dev);    
        video_dev->v4l2_dev = &video_dev;
    
        /* 注册一个video_device字符设备 */
        video_register_device(video_dev, VFL_TYPE_GRABBER, -1);
    
        return 0;
    }
    
    static void __exit video_exit(void)
    {
        video_unregister_device(video_dev);
        v4l2_device_unregister(&v4l2_dev);
        video_device_release(video_dev);
    }
    
    
    module_init(video_init);
    module_exit(video_exit);
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"> class="hide-preCode-box">

    如果你熟悉v4l2应用编程的话,你应该知道v4l2有许多ioctl操作,上面的模板就实现了很多ioctl操作

    3.2 V4L2源码剖析

    下面我们来分析分析源码

    在上面的video_init中,我们分配并设置了video_dev,注册了v4l2_device(v4l2_device_register),然后向v4l2核心层注册video_device(video_register_device)

    我们先来看v4l2_device_register

    int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)
    {
        INIT_LIST_HEAD(&v4l2_dev->subdevs);
        spin_lock_init(&v4l2_dev->lock);
        dev_set_drvdata(dev, v4l2_dev);
        ...
    }
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

    从源码中可以看出v4l2_device_register并没有做什么事,只是初始化链表,自旋锁,还有设置数据,这函数并不是我们的重点

    下面来仔细分析video_register_device

    int video_register_device(struct video_device *vdev, int type, int nr)
    {   
        /* 分配字符设备 */
        vdev->cdev = cdev_alloc();
        
        /* 设置fops */
        vdev->cdev->ops = &v4l2_fops;
        
        /* 注册字符设备 */
        cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
    
    	/* 生成设备节点 */
    	dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num);
        device_register(&vdev->dev);
        
        /* 设置全局数组 */
        video_device[vdev->minor] = vdev;
    }
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"> class="hide-preCode-box">

    可以看到这个函数会为video_device分配一个cdev,然后设置fops,向内核注册字符设备,再者生成设备节点

    然后设置video_device全局数组,video_device一个全局数组

    static struct video_device *video_device[VIDEO_NUM_DEVICES];
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

    保存着注册的video_device

    接下来看一下其中设置的fops(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,
    	.ioctl = v4l2_ioctl,
    	.release = v4l2_release,
    	.poll = v4l2_poll,
    	.llseek = no_llseek,
    };
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

    这个是video_device中的字符设备对应的fops,应用层发生系统调用会率先调用到这里,我们来好好看一看这些调用

    data-report-view="{"mod":"1585297308_001","spm":"1001.2101.3001.6548","dest":"http://iyenn.com/rec/2146286.html","extend1":"pc","ab":"new"}">>
    id="article_content" class="article_content clearfix"> id="content_views" class="markdown_views prism-atom-one-light">

    深入学习Linux摄像头系列

    深入学习Linux摄像头(一)v4l2应用编程

    深入学习Linux摄像头(二)v4l2驱动框架

    深入学习Linux摄像头(三)虚拟摄像头驱动分析

    深入学习Linux摄像头(四)三星平台fimc驱动详解

    深入学习Linux摄像头(二)v4l2驱动框架

    class="toc">

    文章目录

    一、V4L2 框架

    1.1 相关对象

    v4l2驱动框架主要的对象有video_devicev4l2_devicev4l2_subdevvideobuf

    下面有必要对v4l2_device和v4l2_subdev来进行说明

    subdev的设计目的是为了多路复用,就是用一个v4l2_device可以服务多个v4l2_subdev

    下面以我们手机的摄像头来举例

    对于手机而言,一般都有两个摄像头,一个前置摄像头,一个后置摄像头,其接发下图所示

    在这里插入图片描述

    我们可以选择让控制器去操作哪一个摄像头,这就做到了使用一个摄像头控制器来控制多个摄像头,这就是多路复用

    上面说要使用一个摄像头控制器去操作多个摄像头,这是我们的目的,那么在软件中是怎么实现的呢?

    我们回到V4L2来,再来谈v4l2_devicev4l2_subdev

    上面我们介绍到v4l2_device表示一个v4l2实例

    在V4L2驱动中,使用v4l2_device来表示摄像头控制器

    使用v4l2_subdev来表示具体的某一个摄像头的I2C控制模块,进而通过其控制摄像头

    v4l2_device里有一个v4l2_subdev链表,可以选择v4l2_device去控制哪一个v4l2_subdev

    相信到此,你对v4l2_devicev4l2_subdev就有所了解了

    当然某些驱动是没有v4l2_subdev,只有video_device

    经过上面的讲解,我们用一张图来总结

    在这里插入图片描述

    前面说video_device是一个字符设备,从图中可以看出,video_device内含一个cdev

    v4l2_device是一个v4l2实例,嵌入到video_device中

    v4l2_device维护者一个链表管理v4l2_subdev,v4l2_subdev表示摄像头的I2C控制模块

    1.2 V4L2 框架

    在理清楚V4L2中的主要对象后,我们来介绍V4L2的框架

    在介绍V4L2驱动框架前,我们先回顾一下简单的字符设备的编写

    复杂的字符设备

    对于复杂的字符设备,内核都是采用分层的方法,一般分驱动核心层还有硬件相关层

    核心层会帮你完成字符设备的分配,fops的设置,注册字符设备,并向硬件相关层提供一个相应的对象和注册接口

    硬件相关层则需要分配相应的对象,设置对象和对象的fops,并注册到核心层中

    当应用层发生系统调用,会先来到核心层,核心层再通过回调函数调用到硬件相关层的驱动

    对于V4L2的驱动框架也是如此,可分为V4L2驱动核心层硬件相关层

    下面先用一张图来总结大致V4L2的驱动框架

    在这里插入图片描述

    从图中可以看出V4L2分为核心层还有硬件相关层

    核心层负责注册字符设备,然后提供video_device对象和相应的注册接口给硬件相关层使用

    硬件相关层需要分配一个video_device并设置它,然后向核心层注册,核心层会为其注册字符设备并且创建设备节点(/dev/videox)。同时硬件相关层还需要分配和设置相应的v4l2_devicev4l2_subdev,其中v4l2_device的一个比较重要的意义就是管理v4l2_subdev,当然有一些驱动并不需要实现v4l2_subdev,此时v4l2_device的意义就不是很大了

    当应用层通过/dev/video来操作设备的时候,首先会来到V4L2的核心层,核心层通过注册进的video_device的回调函数调用相应的操作函数,video_device可以直接操作硬件或者是通过v4l2_subdev来操作硬件

    二、V4L2的数据结构

    介绍完V4L2的驱动框架后,来看一看内核中各对象的数据结构

    2.1 V4L2主要对象的数据结构

    2.2 V4L2提供的注册接口

    三、源码剖析

    3.1 V4L2驱动模板

    此示例中并没有设计到v4l2_subdev,这部分将会在后面分析具体的驱动中出现

    #include <...>
    
    static struct video_device* video_dev;
    static struct v4l2_device v4l2_dev;
    
    /* 实现各种系统调用 */
    static const struct v4l2_file_operations video_dev_fops = {
    	.owner		    = THIS_MODULE,
    	.release        = vdev_close,
    	.read           = vdev_read,
    	.poll		    = vdev_poll,
    	.ioctl          = video_ioctl2,
    	.mmap           = vdev_mmap,
    };
    
    /* 实现各种系统调用 */
    static const struct v4l2_ioctl_ops video_dev_ioctl_ops = {
    	.vidioc_querycap      = vidioc_querycap,
    	.vidioc_enum_fmt_vid_cap  = vidioc_enum_fmt_vid_cap,
    	.vidioc_g_fmt_vid_cap     = vidioc_g_fmt_vid_cap,
    	.vidioc_try_fmt_vid_cap   = vidioc_try_fmt_vid_cap,
    	.vidioc_s_fmt_vid_cap     = vidioc_s_fmt_vid_cap,
    	.vidioc_reqbufs       = vidioc_reqbufs,
    	.vidioc_querybuf      = vidioc_querybuf,
    	.vidioc_qbuf          = vidioc_qbuf,
    	.vidioc_dqbuf         = vidioc_dqbuf,
    	.vidioc_enum_input    = vidioc_enum_input,
    	.vidioc_g_input       = vidioc_g_input,
    	.vidioc_s_input       = vidioc_s_input,
    	.vidioc_streamon      = vidioc_streamon,
    	.vidioc_streamoff     = vidioc_streamoff,
    };
    
    static int __init video_init(void)
    {
        /* 分配并设置一个video_device */
        video_dev = video_device_alloc();
        video_dev->fops = &video_dev_fops;
        video_dev->ioctl_ops = &video_dev_ioctl_ops;
        video_dev->release = video_device_release;
        video_dev->tvnorms = V4L2_STD_525_60;
        video_dev->current_norm = V4L2_STD_NTSC_M;
    
        /* 注册一个v4l2_device */
        v4l2_device_register(video_dev->dev, &v4l2_dev);    
        video_dev->v4l2_dev = &video_dev;
    
        /* 注册一个video_device字符设备 */
        video_register_device(video_dev, VFL_TYPE_GRABBER, -1);
    
        return 0;
    }
    
    static void __exit video_exit(void)
    {
        video_unregister_device(video_dev);
        v4l2_device_unregister(&v4l2_dev);
        video_device_release(video_dev);
    }
    
    
    module_init(video_init);
    module_exit(video_exit);
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"> class="hide-preCode-box">

    如果你熟悉v4l2应用编程的话,你应该知道v4l2有许多ioctl操作,上面的模板就实现了很多ioctl操作

    3.2 V4L2源码剖析

    下面我们来分析分析源码

    在上面的video_init中,我们分配并设置了video_dev,注册了v4l2_device(v4l2_device_register),然后向v4l2核心层注册video_device(video_register_device)

    我们先来看v4l2_device_register

    int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)
    {
        INIT_LIST_HEAD(&v4l2_dev->subdevs);
        spin_lock_init(&v4l2_dev->lock);
        dev_set_drvdata(dev, v4l2_dev);
        ...
    }
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

    从源码中可以看出v4l2_device_register并没有做什么事,只是初始化链表,自旋锁,还有设置数据,这函数并不是我们的重点

    下面来仔细分析video_register_device

    int video_register_device(struct video_device *vdev, int type, int nr)
    {   
        /* 分配字符设备 */
        vdev->cdev = cdev_alloc();
        
        /* 设置fops */
        vdev->cdev->ops = &v4l2_fops;
        
        /* 注册字符设备 */
        cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
    
    	/* 生成设备节点 */
    	dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num);
        device_register(&vdev->dev);
        
        /* 设置全局数组 */
        video_device[vdev->minor] = vdev;
    }
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"> class="hide-preCode-box">

    可以看到这个函数会为video_device分配一个cdev,然后设置fops,向内核注册字符设备,再者生成设备节点

    然后设置video_device全局数组,video_device一个全局数组

    static struct video_device *video_device[VIDEO_NUM_DEVICES];
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

    保存着注册的video_device

    接下来看一下其中设置的fops(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,
    	.ioctl = v4l2_ioctl,
    	.release = v4l2_release,
    	.poll = v4l2_poll,
    	.llseek = no_llseek,
    };
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

    这个是video_device中的字符设备对应的fops,应用层发生系统调用会率先调用到这里,我们来好好看一看这些调用

    data-report-view="{"mod":"1585297308_001","spm":"1001.2101.3001.6548","dest":"http://iyenn.com/rec/2146286.html","extend1":"pc","ab":"new"}">>
    id="article_content" class="article_content clearfix"> id="content_views" class="markdown_views prism-atom-one-light">

    深入学习Linux摄像头系列

    深入学习Linux摄像头(一)v4l2应用编程

    深入学习Linux摄像头(二)v4l2驱动框架

    深入学习Linux摄像头(三)虚拟摄像头驱动分析

    深入学习Linux摄像头(四)三星平台fimc驱动详解

    深入学习Linux摄像头(二)v4l2驱动框架

    class="toc">

    文章目录

    一、V4L2 框架

    1.1 相关对象

    v4l2驱动框架主要的对象有video_devicev4l2_devicev4l2_subdevvideobuf

    下面有必要对v4l2_device和v4l2_subdev来进行说明

    subdev的设计目的是为了多路复用,就是用一个v4l2_device可以服务多个v4l2_subdev

    下面以我们手机的摄像头来举例

    对于手机而言,一般都有两个摄像头,一个前置摄像头,一个后置摄像头,其接发下图所示

    在这里插入图片描述

    我们可以选择让控制器去操作哪一个摄像头,这就做到了使用一个摄像头控制器来控制多个摄像头,这就是多路复用

    上面说要使用一个摄像头控制器去操作多个摄像头,这是我们的目的,那么在软件中是怎么实现的呢?

    我们回到V4L2来,再来谈v4l2_devicev4l2_subdev

    上面我们介绍到v4l2_device表示一个v4l2实例

    在V4L2驱动中,使用v4l2_device来表示摄像头控制器

    使用v4l2_subdev来表示具体的某一个摄像头的I2C控制模块,进而通过其控制摄像头

    v4l2_device里有一个v4l2_subdev链表,可以选择v4l2_device去控制哪一个v4l2_subdev

    相信到此,你对v4l2_devicev4l2_subdev就有所了解了

    当然某些驱动是没有v4l2_subdev,只有video_device

    经过上面的讲解,我们用一张图来总结

    在这里插入图片描述

    前面说video_device是一个字符设备,从图中可以看出,video_device内含一个cdev

    v4l2_device是一个v4l2实例,嵌入到video_device中

    v4l2_device维护者一个链表管理v4l2_subdev,v4l2_subdev表示摄像头的I2C控制模块

    1.2 V4L2 框架

    在理清楚V4L2中的主要对象后,我们来介绍V4L2的框架

    在介绍V4L2驱动框架前,我们先回顾一下简单的字符设备的编写

    复杂的字符设备

    对于复杂的字符设备,内核都是采用分层的方法,一般分驱动核心层还有硬件相关层

    核心层会帮你完成字符设备的分配,fops的设置,注册字符设备,并向硬件相关层提供一个相应的对象和注册接口

    硬件相关层则需要分配相应的对象,设置对象和对象的fops,并注册到核心层中

    当应用层发生系统调用,会先来到核心层,核心层再通过回调函数调用到硬件相关层的驱动

    对于V4L2的驱动框架也是如此,可分为V4L2驱动核心层硬件相关层

    下面先用一张图来总结大致V4L2的驱动框架

    在这里插入图片描述

    从图中可以看出V4L2分为核心层还有硬件相关层

    核心层负责注册字符设备,然后提供video_device对象和相应的注册接口给硬件相关层使用

    硬件相关层需要分配一个video_device并设置它,然后向核心层注册,核心层会为其注册字符设备并且创建设备节点(/dev/videox)。同时硬件相关层还需要分配和设置相应的v4l2_devicev4l2_subdev,其中v4l2_device的一个比较重要的意义就是管理v4l2_subdev,当然有一些驱动并不需要实现v4l2_subdev,此时v4l2_device的意义就不是很大了

    当应用层通过/dev/video来操作设备的时候,首先会来到V4L2的核心层,核心层通过注册进的video_device的回调函数调用相应的操作函数,video_device可以直接操作硬件或者是通过v4l2_subdev来操作硬件

    二、V4L2的数据结构

    介绍完V4L2的驱动框架后,来看一看内核中各对象的数据结构

    2.1 V4L2主要对象的数据结构

    2.2 V4L2提供的注册接口

    三、源码剖析

    3.1 V4L2驱动模板

    此示例中并没有设计到v4l2_subdev,这部分将会在后面分析具体的驱动中出现

    #include <...>
    
    static struct video_device* video_dev;
    static struct v4l2_device v4l2_dev;
    
    /* 实现各种系统调用 */
    static const struct v4l2_file_operations video_dev_fops = {
    	.owner		    = THIS_MODULE,
    	.release        = vdev_close,
    	.read           = vdev_read,
    	.poll		    = vdev_poll,
    	.ioctl          = video_ioctl2,
    	.mmap           = vdev_mmap,
    };
    
    /* 实现各种系统调用 */
    static const struct v4l2_ioctl_ops video_dev_ioctl_ops = {
    	.vidioc_querycap      = vidioc_querycap,
    	.vidioc_enum_fmt_vid_cap  = vidioc_enum_fmt_vid_cap,
    	.vidioc_g_fmt_vid_cap     = vidioc_g_fmt_vid_cap,
    	.vidioc_try_fmt_vid_cap   = vidioc_try_fmt_vid_cap,
    	.vidioc_s_fmt_vid_cap     = vidioc_s_fmt_vid_cap,
    	.vidioc_reqbufs       = vidioc_reqbufs,
    	.vidioc_querybuf      = vidioc_querybuf,
    	.vidioc_qbuf          = vidioc_qbuf,
    	.vidioc_dqbuf         = vidioc_dqbuf,
    	.vidioc_enum_input    = vidioc_enum_input,
    	.vidioc_g_input       = vidioc_g_input,
    	.vidioc_s_input       = vidioc_s_input,
    	.vidioc_streamon      = vidioc_streamon,
    	.vidioc_streamoff     = vidioc_streamoff,
    };
    
    static int __init video_init(void)
    {
        /* 分配并设置一个video_device */
        video_dev = video_device_alloc();
        video_dev->fops = &video_dev_fops;
        video_dev->ioctl_ops = &video_dev_ioctl_ops;
        video_dev->release = video_device_release;
        video_dev->tvnorms = V4L2_STD_525_60;
        video_dev->current_norm = V4L2_STD_NTSC_M;
    
        /* 注册一个v4l2_device */
        v4l2_device_register(video_dev->dev, &v4l2_dev);    
        video_dev->v4l2_dev = &video_dev;
    
        /* 注册一个video_device字符设备 */
        video_register_device(video_dev, VFL_TYPE_GRABBER, -1);
    
        return 0;
    }
    
    static void __exit video_exit(void)
    {
        video_unregister_device(video_dev);
        v4l2_device_unregister(&v4l2_dev);
        video_device_release(video_dev);
    }
    
    
    module_init(video_init);
    module_exit(video_exit);
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"> class="hide-preCode-box">

    如果你熟悉v4l2应用编程的话,你应该知道v4l2有许多ioctl操作,上面的模板就实现了很多ioctl操作

    3.2 V4L2源码剖析

    下面我们来分析分析源码

    在上面的video_init中,我们分配并设置了video_dev,注册了v4l2_device(v4l2_device_register),然后向v4l2核心层注册video_device(video_register_device)

    我们先来看v4l2_device_register

    int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)
    {
        INIT_LIST_HEAD(&v4l2_dev->subdevs);
        spin_lock_init(&v4l2_dev->lock);
        dev_set_drvdata(dev, v4l2_dev);
        ...
    }
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

    从源码中可以看出v4l2_device_register并没有做什么事,只是初始化链表,自旋锁,还有设置数据,这函数并不是我们的重点

    下面来仔细分析video_register_device

    int video_register_device(struct video_device *vdev, int type, int nr)
    {   
        /* 分配字符设备 */
        vdev->cdev = cdev_alloc();
        
        /* 设置fops */
        vdev->cdev->ops = &v4l2_fops;
        
        /* 注册字符设备 */
        cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
    
    	/* 生成设备节点 */
    	dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num);
        device_register(&vdev->dev);
        
        /* 设置全局数组 */
        video_device[vdev->minor] = vdev;
    }
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"> class="hide-preCode-box">

    可以看到这个函数会为video_device分配一个cdev,然后设置fops,向内核注册字符设备,再者生成设备节点

    然后设置video_device全局数组,video_device一个全局数组

    static struct video_device *video_device[VIDEO_NUM_DEVICES];
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

    保存着注册的video_device

    接下来看一下其中设置的fops(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,
    	.ioctl = v4l2_ioctl,
    	.release = v4l2_release,
    	.poll = v4l2_poll,
    	.llseek = no_llseek,
    };
     class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

    这个是video_device中的字符设备对应的fops,应用层发生系统调用会率先调用到这里,我们来好好看一看这些调用

    data-report-view="{"mod":"1585297308_001","spm":"1001.2101.3001.6548","dest":"http://iyenn.com/rec/2146286.html","extend1":"pc","ab":"new"}">>
    注:本文转载自blog.csdn.net的JT同学的文章"https://blog.csdn.net/weixin_42462202/article/details/99680969"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
    复制链接

    评论记录:

    未查询到任何数据!