谷动谷力

 找回密码
 立即注册
查看: 919|回复: 0
打印 上一主题 下一主题
收起左侧

Linux V4L2子系统-Video设备框架分析

[复制链接]
跳转到指定楼层
楼主
发表于 2023-8-24 22:56:00 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 sunsili 于 2023-8-24 22:58 编辑

Linux V4L2子系统-Video设备框架分析

一、概述:

在V4L2子系统中,Video设备是一个字符设备,设备节点为/dev/videoX,主设备号为81,次设备号范围为0-63。在用户空间,应用可以通过open/close/ioctl/mmap/read/write系统调用操作Video设备。在内核空间中,Video设备的具体操作方法由驱动中的struct video_device提供。驱动使用video_register_device函数将struct video_device注册到V4L2的核心层,然后V4L2的核心层在向上注册一个字符设备,该字符设备实现了虚拟文件系统要求的方法。这样应用就可以使用系统调用访问虚拟文件系统中Video设备提供的方法,然后进一步访问V4L2核心层提供的v4l2_fops方法集合,最后通过struct video_device结构体中的fops和ioctl_ops方法集合访问Video主设备。Video主设备通过v4l2_subdev_call方法访问Video从设备,同时Video从设备可以通过notify回掉方法通知主设备发生了事件。Camera Host控制器为Video主设备,Camear Sensor(摄像头)为Video从设备,一般为I2C设备。


二、数据结构介绍:

Video设备使用struct video_device结构体表示。设备的操作方法由fops和ioctl_ops函数指针集合指定,fops是文件操作方法,实现常见的open、read、write方法,ioctl_ops实现应用层的ioctl函数,功能较多。具体的设备类型由vfl_type指定,可用VFL_TYPE_XXXX表示。Video设备是一个字符设备,由cdev指向创建的字符设备结构体。使用video_device_alloc函数动态分配video_device结构体内存,使用video_device_release释放分配的video_device结构体内存,video_device的release函数可以初始化为video_device_release,当设备注销时释放内存。使用video_register_device注册该结构体,使用video_unregister_device注销该结构体。

    [include/media/v4l2-dev.h]
    // video设备类型,由video_device的vfl_type成员指定
    #define VFL_TYPE_GRABBER 0  // 图像采集设备,包括摄像头、调谐器等
    #define VFL_TYPE_VBI  1  // 从视频消隐的时间段取得信息的设备
    #define VFL_TYPE_RADIO  2  // 无线电设备,如收音机等
    #define VFL_TYPE_SUBDEV  3  // v4l2从设备
    #define VFL_TYPE_SDR  4  // Software Defined Radio
    #define VFL_TYPE_MAX  5  

    // 传输方向,设备类型为VFL_TYPE_SUBDEV时忽略,由video_device的vfl_dir成员指定
    #define VFL_DIR_RX  0  // 接收
    #define VFL_DIR_TX  1  // 发送
    #define VFL_DIR_M2M  2  // 内存到内存

    // video_device注册状态掩码,若注册成功则video_device的flags的bit0为1,
    // 注销时video_device的flags的bit0清零
    #define V4L2_FL_REGISTERED (0)
    // video_device的flags的bit1为1,则file->private_data指向struct v4l2_fh
    #define V4L2_FL_USES_V4L2_FH (1)

    struct video_device
    {
    #if defined(CONFIG_MEDIA_CONTROLLER)  // 多媒体设备配置选项
        struct media_entity entity;   // 用于运行时数据流的管理
    #endif
        /* video设备操作函数集合,实现read、write等函数 */
        const struct v4l2_file_operations *fops;
        // video_device对应的设备结构体
        struct device dev;
        // 字符设备指针,注册时创建字符设备结构体
        struct cdev *cdev;
        struct v4l2_device *v4l2_dev; /* v4l2_device parent */
        /* Only set parent if that can't be deduced from v4l2_dev */
        struct device *dev_parent; /* device parent */
        /* Control handler associated with this device node. May be NULL. */
        struct v4l2_ctrl_handler *ctrl_handler;
        /* vb2_queue associated with this device node. May be NULL. */
        struct vb2_queue *queue;
        /* Priority state. If NULL, then v4l2_dev->prio will be used. */
        struct v4l2_prio_state *prio;
        /* 设备名称 */
        char name[32];
        int vfl_type; /* 设备类型 */
        int vfl_dir; /* 传输方向 */
        int minor;  // 次设备号,如果注册失败,将被设置为-1
        u16 num;  // 设备数量
        // 标志位,可以使用set/clear/test操作
        unsigned long flags;
        // 物理设备的索引,用来区分不同的物理设备
        int index;
        /* V4L2 file handles */
        spinlock_t  fh_lock; /* Lock for all v4l2_fhs */
        struct list_head fh_list; /* List of struct v4l2_fh */
        /* Video standard vars */
        v4l2_std_id tvnorms;  /* Supported tv norms */
        // 设备引用计数为0时被调用,用来释放资源,如释放动态分配的struct video_device结构体
        void (*release)(struct video_device *vdev);
        // 控制函数,应用层的ioctl都在这里实现,比较重要
        const struct v4l2_ioctl_ops *ioctl_ops;  
        DECLARE_BITMAP(valid_ioctls, BASE_VIDIOC_PRIVATE);
        /* serialization lock */
        DECLARE_BITMAP(disable_locking, BASE_VIDIOC_PRIVATE);
        struct mutex *lock;
    };

    // 动态分配video_device结构体,返回动态分配的video_device结构体指针,若失败返回NULL
    struct video_device *video_device_alloc(void);
    // 释放动态分配的video_device结构内存,release可以初始化为该函数
    void video_device_release(struct video_device *vdev);

    // 注册video_device结构体,注册失败时不会调用release函数
    // vdev-video_device结构体指针
    // type-设备类型,由VFL_TYPE_XXXX宏指定
    // nr-生成的设备节点数量(0 == /dev/video0, 1 == /dev/video1, ...-1 == first free)
    // 返回值-0成功,小于0失败
    static inline int video_register_device(struct video_device *vdev, int type, int nr)
    // 注销video_device结构体
    void video_unregister_device(struct video_device *vdev);

Video设备的文件操作方法由struct v4l2_file_operations结构体表示,指定了read、write、ioctl、mmap等常见的文件操作方法。应用层对Video设备发起IO操作,最终会调用到该结构体中的函数。

    [include/media/v4l2-dev.h]
    struct v4l2_file_operations {
        struct module *owner;
        // 读函数
        ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
        // 写函数
        ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
        // poll函数
        unsigned int (*poll) (struct file *, struct poll_table_struct *);
        // ioctl函数
        long (*ioctl) (struct file *, unsigned int, unsigned long);
        long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    #ifdef CONFIG_COMPAT
        long (*compat_ioctl32) (struct file *, unsigned int, unsigned long);
    #endif
        unsigned long (*get_unmapped_area) (struct file *, unsigned long,
                    unsigned long, unsigned long, unsigned long);
        // 内存映射函数
        int (*mmap) (struct file *, struct vm_area_struct *);
        // 打开设备
        int (*open) (struct file *);
        // 关闭设备
        int (*release) (struct file *);
    };

视频设备的应用层ioctl功能由struct v4l2_ioctl_ops结构体来表示。ioctl命令在内核include/uapi/linux/videodev2.h文件中定义。比较常用的命令如下面的宏定义所示。

    [include/uapi/linux/videodev2.h]
    // 查询底层驱动支持的功能
    #define VIDIOC_QUERYCAP  _IOR('V',  0, struct v4l2_capability)
    // 获取底层驱动支持的视频格式
    #define VIDIOC_ENUM_FMT     _IOWR('V',  2, struct v4l2_fmtdesc)
    // 获取底层驱动当前的视频捕获格式
    #define VIDIOC_G_FMT  _IOWR('V',  4, struct v4l2_format)
    // 设置底层驱动当前的视频捕获格式
    #define VIDIOC_S_FMT  _IOWR('V',  5, struct v4l2_format)
    // 申请数据缓存内存
    #define VIDIOC_REQBUFS      _IOWR('V',  8, struct v4l2_requestbuffers)
    // 获取VIDIOC_REQBUFS中分配的缓冲区信息,包括长度、偏移等信息
    #define VIDIOC_QUERYBUF  _IOWR('V',  9, struct v4l2_buffer)
    // 图像采集模式-将读取完数据的空缓存返还给驱动的缓存队列
    // 图像输出模式-将填充满数据的缓存返还给驱动的缓存队列
    #define VIDIOC_QBUF      _IOWR('V', 15, struct v4l2_buffer)
    // 图像采集模式-将填充满数据的缓存从驱动中返回给应用
    // 图像输出模式-将读取完数据的空显示缓存从驱动中返回给应用
    #define VIDIOC_DQBUF  _IOWR('V', 17, struct v4l2_buffer)
    // 开始采集视频
    #define VIDIOC_STREAMON   _IOW('V', 18, int)
    // 停止采集视频
    #define VIDIOC_STREAMOFF  _IOW('V', 19, int)
    // 获取视频流的参数
    #define VIDIOC_G_PARM  _IOWR('V', 21, struct v4l2_streamparm)
    // 设置视频流的参数
    #define VIDIOC_S_PARM  _IOWR('V', 22, struct v4l2_streamparm)
    // 查询当前视频输入设备采用的标准
    #define VIDIOC_G_STD   _IOR('V', 23, v4l2_std_id)
    // 设置当前视频输入设备的标准
    #define VIDIOC_S_STD   _IOW('V', 24, v4l2_std_id)
    // 列出所有视频输入源
    #define VIDIOC_ENUMINPUT  _IOWR('V', 26, struct v4l2_input)
    // 获取目前使用的视频输入源编号
    #define VIDIOC_G_INPUT   _IOR('V', 38, int)
    // 选择视频输入源,输入源编号通过参数传入
    #define VIDIOC_S_INPUT  _IOWR('V', 39, int)
    // 查询底层驱动的修剪能力
    #define VIDIOC_CROPCAP  _IOWR('V', 58, struct v4l2_cropcap)
    // 获取视频信号的矩形边框
    #define VIDIOC_G_CROP  _IOWR('V', 59, struct v4l2_crop)
    // 设置视频信号的矩形边框
    #define VIDIOC_S_CROP  _IOW('V', 60, struct v4l2_crop)
    // 获取当前视频设备支持的标准,如PAL或NTSC
    #define VIDIOC_QUERYSTD        _IOR('V', 63, v4l2_std_id)
    // 验证底层驱动的显示格式
    #define VIDIOC_TRY_FMT       _IOWR('V', 64, struct v4l2_format)
    // 列出在给定的像素格式下视频帧的尺寸
    #define VIDIOC_ENUM_FRAMESIZES _IOWR('V', 74, struct v4l2_frmsizeenum)
    // 列出在给定的像素格式和帧尺寸下视频帧的间隔
    #define VIDIOC_ENUM_FRAMEINTERVALS _IOWR('V', 75, struct v4l2_frmivalenum)

    [include/media/v4l2-ioctl.h]
    struct v4l2_ioctl_ops {
        /* VIDIOC_QUERYCAP handler */
        int (*vidioc_querycap)(struct file *file,
                void *fh, struct v4l2_capability *cap);

        /* VIDIOC_ENUM_FMT handlers */
        int (*vidioc_enum_fmt_vid_cap)(struct file *file,
                void *fh, struct v4l2_fmtdesc *f);
        ......

        /* VIDIOC_G_FMT handlers */
        int (*vidioc_g_fmt_vid_cap)(struct file *file, void *fh,struct v4l2_format *f);
        ......

        /* VIDIOC_S_FMT handlers */
        int (*vidioc_s_fmt_vid_cap)(struct file *file, void *fh,struct v4l2_format *f);
        ......

        /* VIDIOC_TRY_FMT handlers */
        int (*vidioc_try_fmt_vid_cap)(struct file *file, void *fh,struct v4l2_format *f);
        ......

        /* Buffer handlers */
        int (*vidioc_reqbufs) (struct file *file,
                void *fh, struct v4l2_requestbuffers *b);
        int (*vidioc_querybuf)(struct file *file, void *fh, struct v4l2_buffer *b);
        int (*vidioc_qbuf)    (struct file *file, void *fh, struct v4l2_buffer *b);
        int (*vidioc_dqbuf)   (struct file *file, void *fh, struct v4l2_buffer *b);
        ......

        /* Stream on/off */
        int (*vidioc_streamon) (struct file *file, void *fh, enum v4l2_buf_type i);
        int (*vidioc_streamoff)(struct file *file, void *fh, enum v4l2_buf_type i);

        /* Standard handling, ENUMSTD is handled by videodev.c */
        int (*vidioc_g_std) (struct file *file, void *fh, v4l2_std_id *norm);
        int (*vidioc_s_std) (struct file *file, void *fh, v4l2_std_id norm);
        int (*vidioc_querystd) (struct file *file, void *fh, v4l2_std_id *a);

        /* Input handling */
        int (*vidioc_enum_input)(struct file *file, void *fh,struct v4l2_input *inp);
        int (*vidioc_g_input)   (struct file *file, void *fh, unsigned int *i);
        int (*vidioc_s_input)   (struct file *file, void *fh, unsigned int i);

        /* Crop ioctls */
        int (*vidioc_cropcap)(struct file *file, void *fh,struct v4l2_cropcap *a);
        int (*vidioc_g_crop)(struct file *file, void *fh,struct v4l2_crop *a);
        int (*vidioc_s_crop)(struct file *file, void *fh,const struct v4l2_crop *a);
        ......

        /* Stream type-dependent parameter ioctls */
        int (*vidioc_g_parm)(struct file *file, void *fh,struct v4l2_streamparm *a);
        int (*vidioc_s_parm)(struct file *file, void *fh,struct v4l2_streamparm *a);
        ......

        /* Debugging ioctls */
        ......
        int (*vidioc_enum_framesizes)(struct file *file,
                void *fh,struct v4l2_frmsizeenum *fsize);
        int (*vidioc_enum_frameintervals)(struct file *file,
                void *fh,struct v4l2_frmivalenum *fival);
    };

struct video_device数据结构中fops和ioctl_ops成员最重要,video_device数据结构示意图如下图所示。



Video从设备的操作方法由struct v4l2_subdev_video_ops结构体表示。Video主设备可以通过v4l2_subdev_call调用这些方法。

    struct v4l2_subdev_video_ops {
        int (*s_routing)(struct v4l2_subdev *sd, u32 input, u32 output, u32 config);
        // 设置时钟频率,单位为Hz,额外的标志位可以记录分频系数,如果没有使用,
        // 则设置为0,如果不支持设置频率,则返回-EINVAL
        int (*s_crystal_freq)(struct v4l2_subdev *sd, u32 freq, u32 flags);
        int (*g_std)(struct v4l2_subdev *sd, v4l2_std_id *norm);
        int (*s_std)(struct v4l2_subdev *sd, v4l2_std_id norm);
        // 设置视频输出设备的标准ID,视频输入设备忽略
        int (*s_std_output)(struct v4l2_subdev *sd, v4l2_std_id std);
        // 获取视频输出设备当前采用的标准,视频输入设备忽略
        int (*g_std_output)(struct v4l2_subdev *sd, v4l2_std_id *std);
        int (*querystd)(struct v4l2_subdev *sd, v4l2_std_id *std);
        // 获取视频采集设备支持的所有标准ID,视频输出设备忽略
        int (*g_tvnorms)(struct v4l2_subdev *sd, v4l2_std_id *std);
        // 获取视频输出设备支持的所有标准ID,视频采集设备忽略
        int (*g_tvnorms_output)(struct v4l2_subdev *sd, v4l2_std_id *std);
        // 获取输入状态,和v4l2_input结构体中的status字段一样
        int (*g_input_status)(struct v4l2_subdev *sd, u32 *status);
        int (*s_stream)(struct v4l2_subdev *sd, int enable);
        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, const 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);
        int (*g_frame_interval)(struct v4l2_subdev *sd,
                struct v4l2_subdev_frame_interval *interval);
        int (*s_frame_interval)(struct v4l2_subdev *sd,
                struct v4l2_subdev_frame_interval *interval);
        // 设置传统摄像机图像采集的时序
        int (*s_dv_timings)(struct v4l2_subdev *sd,struct v4l2_dv_timings *timings);
        // 获取传统摄像机图像采集的时序
        int (*g_dv_timings)(struct v4l2_subdev *sd, struct v4l2_dv_timings *timings);
        int (*query_dv_timings)(struct v4l2_subdev *sd,struct v4l2_dv_timings *timings);
        // 列举像素格式
        int (*enum_mbus_fmt)(struct v4l2_subdev *sd, unsigned int index,u32 *code);
        // 获取像素格式
        int (*g_mbus_fmt)(struct v4l2_subdev *sd,struct v4l2_mbus_framefmt *fmt);
        // 尝试设置像素格式
        int (*try_mbus_fmt)(struct v4l2_subdev *sd,struct v4l2_mbus_framefmt *fmt);
        // 设置像素格式
        int (*s_mbus_fmt)(struct v4l2_subdev *sd,struct v4l2_mbus_framefmt *fmt);
        // 获取多媒体总线设置
        int (*g_mbus_config)(struct v4l2_subdev *sd,struct v4l2_mbus_config *cfg);
        // 设置明确的媒体总线设置
        int (*s_mbus_config)(struct v4l2_subdev *sd,const struct v4l2_mbus_config *cfg);
        // 设置主机给从设备分配的缓存,从设备可以调整缓冲区的size到较低的水平
        int (*s_rx_buffer)(struct v4l2_subdev *sd, void *buf,unsigned int *size);
    };

三、注册与注销过程分析:

视频设备的主要功能都由struct video_device结构体实现,video_device结构体由video_register_device函数注册。video_register_device函数内部只调用了__video_register_device函数,下面重点分析一下__video_register_device函数。

    [include/media/v4l2-dev.h]
    // 注册video_device结构体
    // vdev-video_device结构体指针
    // type-注册的设备类型,有效的设备类型如下:
    //     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
    // nr-生成的设备节点数量(0 == /dev/video0, 1 == /dev/video1, ...-1 == first free)
    // warn_if_nr_in_use-若设备节点编号已被占用则发出警告,同时会选择其他设备节点编号
    // owner-video设备节点所属的模块
    // 返回值-0成功,小于0失败
    int __video_register_device(struct video_device *vdev, int type, int nr,
             int warn_if_nr_in_use, struct module *owner)

__video_register_device函数的执行流程如下图所示,可总结如下:

  • (1)在注册之前必须设置release和v4l2_dev成员,前者用于设备注销时回调释放资源,后者指向了管理video_device的v4l2_device结构体。

  • (2)检查设备类型并确定设备节点基本名称。

  • (3)设置设备类型、次设备号(由设备类型和全局video_device数组决定)及设备节点数量。

  • (4)将要注册的video_device结构体指针保存到全局的video_device数组中。

  • (5)根据设备类型验证那些ioctl函数可以使用。

  • (6)分配字符设备结构体。

  • (7)设置字符设备的操作函数集合为v4l2_fops。

  • (8)将video设备注册为字符设备。

  • (9)注册设备。

  • (10)设置设备引用计数为0时的回调函数,回调函数为v4l2_device_release。v4l2_device_release主要的工作是删除注册的字符设备,回调v4l2_device中的release函数(通常是video_device_release函数)释放video_device结构体内存,最后减少v4l2_device的引用计数(一个v4l2_device可管理多个video_device,当v4l2_device的引用计数为0时,将进行注销工作)。

  • (11)增加video_device所属v4l2_device的引用计数。

  • (12)设置已注册标志V4L2_FL_REGISTERED。




调用video_unregister_device函数注销注册的video_device,主要执行流程如下:

    void video_unregister_device(struct video_device *vdev)
    {
        /* 如果没有注册,则直接返回 */
        if (!vdev || !video_is_registered(vdev))
            return;

        mutex_lock(&videodev_lock);
        // 清楚已注册标志
        clear_bit(V4L2_FL_REGISTERED, &vdev->flags);
        mutex_unlock(&videodev_lock);
        // 注销设备
        device_unregister(&vdev->dev);
    }

四、Video设备访问流程总结:

Video设备访问流程如下图所示。总结如下:

  • (1)首先通过系统调用访问/dev/videoX用户空间设备节点。

  • (2)进入到内核空间,访问字符设备struct file_operations中的方法。对于Vedio设备,该操作集合被V4L2子系统初始化为v4l2_fops集合。

  • (3)通过V4L2子系统提供的v4l2_fops集合,可直接调用底层驱动实现的Video主设备struct v4l2_file_operations方法,对于ioctl方法,则需要借助中间函数__video_do_ioctl调用底层驱动实现的struct v4l2_ioctl_ops中的ioctl功能。struct v4l2_file_operations方法和struct v4l2_ioctl_ops方法属于主设备方法,需要主设备的驱动实现。

  • (4)struct v4l2_file_operations和struct v4l2_ioctl_ops中的函数都可以通过v4l2_subdev_call调用Video从设备struct v4l2_subdev_core_ops、struct v4l2_subdev_video_ops、struct v4l2_subdev_pad_ops等方法,这些方法都要在从设备驱动中实现。




五、Video设备访问流程实例分析:

下图是Linux 4.1版本中imx6ull CMOS Sensor Interface (CSI)和ov5640 Image Sensor设备的访问流程,imx6ull CMOS Sensor Interface (CSI)是主设备,ov5640 Image Sensor是从设备,ov5640通过CSI接口连接到CPU上。imx6ull CSI驱动实现了主设备的操作方法,分别为:

  • struct v4l2_file_operations mx6s_csi_fops

  • struct v4l2_ioctl_ops mx6s_csi_ioctl_ops。


ov5640驱动实现了从设备的操作方法,分别为:

  • struct v4l2_subdev_core_ops、

  • struct v4l2_subdev_video_ops

  • ov5640_subdev_video_ops

  • struct v4l2_subdev_pad_ops ov5640_subdev_pad_ops。


后面会具体分析这些方法的执行流程。



参考资料:

  • 内核文档-v4l2-framework_zh_CN.txt

  • Linux内核4.1版本源码

  • Android驱动开发权威指南

  • Android驱动开发与移植实战详解

  • http://blog.sina.com.cn/s/blog_602f87700101a52s.html

  • https://www.kernel.org/doc/html/v4.11/media/uapi/v4l/v4l2.html

  • IMX6ULL参考手册

  • ov5640参考手册


文章参考;http://t.csdn.cn/nqy5W



+10
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|深圳市光明谷科技有限公司|光明谷商城|Sunshine Silicon Corpporation ( 粤ICP备14060730号|Sitemap

GMT+8, 2024-11-16 10:25 , Processed in 0.160082 second(s), 42 queries .

Powered by Discuz! X3.2 Licensed

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表