精华内容
下载资源
问答
  • V4L2驱动框架

    2018-03-16 21:15:44
    V4L2驱动框架主设备号: 81次设备号: 0-63 64-67 192-223 224-255/dev/videoX 应用层————————————char驱动————————————V4L2————————————具体的驱动——————————...
    V4L2驱动框架

    主设备号: 81

    次设备号:
        0-63
        64-67
        192-223
        224-255

    /dev/videoX    

    应用层
    ————————————
    char驱动
    ————————————
    V4L2
    ————————————
    具体的驱动
    ————————————
    硬件




    应用层的操作都需要有底层V4L2驱动的支持。内核中有一些非常完善的例子。比如:linux-2.6.26内核目录drivers/media/video/vivi.c中的驱动代码实例。


    1、V4L2驱动注册、注销函数
    static int __init videodev_init(void)        //注册256个视频设备
    {
            dev_t dev = MKDEV(VIDEO_MAJOR, 0);
            int ret;

            printk(KERN_INFO "Linux video capture interface: v2.00\n");
            ret = register_chrdev_region(dev, VIDEO_NUM_DEVICES, VIDEO_NAME);
            if (ret < 0) {
                    printk(KERN_WARNING "videodev: unable to get major %d\n", VIDEO_MAJOR);
                    return ret;
            }

            ret = class_register(&video_class);
            if (ret < 0) {
                    unregister_chrdev_region(dev, VIDEO_NUM_DEVICES);
                    printk(KERN_WARNING "video_dev: class_register failed\n");
                    return -EIO;
            }

            return 0;
    }

    static void __exit videodev_exit(void)
    {
            dev_t dev = MKDEV(VIDEO_MAJOR, 0);

            class_unregister(&video_class);
            unregister_chrdev_region(dev, VIDEO_NUM_DEVICES);
    }

    module_init(videodev_init)
    module_exit(videodev_exit)


    2、函数实现

    2.1
    Video核心层(drivers/media/video/videodev.c)提供了注册函数供具体的V4L2驱动调用:
    int video_register_device(struct video_device *vfd, int type, int nr)
        —video_device: 要构建的核心数据结构
        —Type: 表示设备类型,此设备号的基地址受此变量的影响
        —Nr: 如果end-base>nr>0 :次设备号=base(基准值,受type影响)+nr;否则:系统自动分配合适的次设备号

    函数内部调用
    static int __video_register_device(struct video_device *vdev, int type, int nr, int warn_if_nr_in_use)
    具体驱动只需要构建video_device结构,然后调用注册函数既可。

    2.2
    Video核心层(drivers/media/video/videodev.c)提供了注销函数
    void video_unregister_device(struct video_device *vdev)

    核心定义设备数组,其中VIDEO_NUM_DEVICES 为 256 是最大设备数
    static struct video_device *video_device[VIDEO_NUM_DEVICES];

    2.3
    V4L2提供了统一的应用层接口

    static const struct file_operations v4l2_fops = {
            .owner = THIS_MODULE,
            .read = v4l2_read,
            .write = v4l2_write,
            .open = v4l2_open,
            .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,
    };
    v4l2_read定义如下
    static ssize_t v4l2_read(struct file *filp, char __user *buf, size_t sz, loff_t *off)
    {
            struct video_device *vdev = video_devdata(filp);
            int ret = -ENODEV;

            if (!vdev->fops->read)
                    return -EINVAL;
            if (vdev->lock && mutex_lock_interruptible(vdev->lock))
                    return -ERESTARTSYS;
            if (video_is_registered(vdev))
                    ret = vdev->fops->read(filp, buf, sz, off);
            if (vdev->lock)
                    mutex_unlock(vdev->lock);
            return ret;
    }

    函数内部调用的    vdev->fops->read  由具体的V4L2驱动实现


    驱动开发

    定义最重要的数据结构体struct video_device,
    其中,重要的是
    const struct v4l2_file_operations *fops;    //帧缓冲操作

    编写帧缓冲驱动的主要工作就是编写fops各个成员函数

    编写具体驱动方法步骤:
    1、构建具体驱动的struct video_device;
    3、构建具体驱动的struct fops,并定义相关的操作函数;

    4、定义具体驱动的XXX_probe。




    struct video_device
    {
            /* device ops */
            const struct v4l2_file_operations *fops;

            /* sysfs */
            struct device dev;              /* v4l device */
            struct cdev *cdev;              /* character device */

            /* Set either parent or v4l2_dev if your driver uses v4l2_device */
            struct device *parent;          /* device parent */
            struct v4l2_device *v4l2_dev;   /* v4l2_device parent */

            /* Control handler associated with this device node. May be NULL. */
            struct v4l2_ctrl_handler *ctrl_handler;

            /* device info */
            char name[32];
            int vfl_type;
            /* 'minor' is set to -1 if the registration failed */
            int minor;
            u16 num;
            /* use bitops to set/clear/test flags */
            unsigned long flags;
            /* attribute to differentiate multiple indices on one physical device */
            int index;

            /* V4L2 file handles */
            spinlock_t              fh_lock; /* Lock for all v4l2_fhs */
            struct list_head        fh_list; /* List of struct v4l2_fh */

            int debug;                      /* Activates debug level*/

            /* Video standard vars */
            v4l2_std_id tvnorms;            /* Supported tv norms */
            v4l2_std_id current_norm;       /* Current tvnorm */

            /* callbacks */
            void (*release)(struct video_device *vdev);

            /* ioctl callbacks */
            const struct v4l2_ioctl_ops *ioctl_ops;

            /* serialization lock */
            struct mutex *lock;
    };
    ================================================================================
    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 *);
            unsigned int (*poll) (struct file *, struct poll_table_struct *);
            long (*ioctl) (struct file *, unsigned int, unsigned long);
            long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
            int (*mmap) (struct file *, struct vm_area_struct *);
            int (*open) (struct file *);
            int (*release) (struct file *);
    };
    ================================================================================
    struct device {
            struct device           *parent;
            struct device_private   *p;
            struct kobject kobj;
            const char              *init_name;/* initial name of the device */
            struct device_type      *type;
            struct mutex            mutex;      /* mutex to synchronize calls to its driver. */
            struct bus_type *bus;               /* type of bus device is on */
            struct device_driver     *driver;    /* which driver has allocated this device */
            void                *platform_data; /* Platform specific data, device core doesn't touch it */
            struct dev_pm_info      power;
    #ifdef CONFIG_NUMA
            int                 numa_node;      /* NUMA node this device is close to */
    #endif
            u64                 *dma_mask;      /* dma mask (if dma'able device) */
            u64                 coherent_dma_mask;/* Like dma_mask, but for alloc_coherent mappings as not all hardware supports 64 bit ses for consistent allocations such descriptors. */
            struct device_dma_parameters *dma_parms;
            struct list_head        dma_pools;      /* dma pools (if dma'ble) */
            struct dma_coherent_mem *dma_mem; /* internal for coherent mem override */
            /* arch specific additions */
            struct dev_archdata     archdata;
    #ifdef CONFIG_OF
            struct device_node      *of_node;
    #endif
            dev_t                   devt;   /* dev_t, creates the sysfs "dev" */
            spinlock_t              devres_lock;
            struct list_head        devres_head;
            struct klist_node       knode_class;
            struct class            *class;
            const struct attribute_group **groups;  /* optional groups */
            void    (*release)(struct device *dev);
    };
    ================================================================================
    struct cdev {
            struct kobject kobj;
            struct module *owner;
            const struct file_operations *ops;
            struct list_head list;
            dev_t dev;
            unsigned int count;
    };
    ================================================================================
    struct v4l2_device {
            struct device *dev;    /* dev->driver_data points to this struct. Note: dev might be NULL if there is no parent device as is the case with e.g. ISA devices. */
            struct list_head subdevs;          /* used to keep track of the registered subdevs */
            spinlock_t lock;  /* lock this struct; can be used by the driver as well if this struct is embedded into a larger struct. */
            char name[V4L2_DEVICE_NAME_SIZE];        /* unique device name, by default the driver name + bus ID */
            void (*notify)(struct v4l2_subdev *sd, unsigned int notification, void *arg);        /* notify callback called by some sub-devices. */
            struct v4l2_ctrl_handler *ctrl_handler;        /* The control handler. May be NULL. */
            struct mutex ioctl_lock;        /* BKL replacement mutex. Temporary solution only. */
    };
    ================================================================================
    struct v4l2_ioctl_ops {
            /* ioctl callbacks */
            /* VIDIOC_QUERYCAP handler */
            int (*vidioc_querycap)(struct file *file, void *fh, struct v4l2_capability *cap);
            /* Priority handling */
            int (*vidioc_g_priority)   (struct file *file, void *fh, enum v4l2_priority *p);
            int (*vidioc_s_priority)   (struct file *file, void *fh, enum v4l2_priority p);
            /* VIDIOC_ENUM_FMT handlers */
            int (*vidioc_enum_fmt_vid_cap)     (struct file *file, void *fh, struct v4l2_fmtdesc *f);
            int (*vidioc_enum_fmt_vid_overlay) (struct file *file, void *fh, struct v4l2_fmtdesc *f);
            int (*vidioc_enum_fmt_vid_out)     (struct file *file, void *fh, struct v4l2_fmtdesc *f);
            int (*vidioc_enum_fmt_type_private)(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);
            int (*vidioc_g_fmt_vid_overlay)(struct file *file, void *fh, struct v4l2_format *f);
            int (*vidioc_g_fmt_vid_out)    (struct file *file, void *fh, struct v4l2_format *f);
            int (*vidioc_g_fmt_vid_out_overlay)(struct file *file, void *fh, struct v4l2_format *f);
            int (*vidioc_g_fmt_vbi_cap)    (struct file *file, void *fh, struct v4l2_format *f);
            int (*vidioc_g_fmt_vbi_out)    (struct file *file, void *fh, struct v4l2_format *f);
            int (*vidioc_g_fmt_sliced_vbi_cap)(struct file *file, void *fh, struct v4l2_format *f);
            int (*vidioc_g_fmt_sliced_vbi_out)(struct file *file, void *fh, struct v4l2_format *f);
            int (*vidioc_g_fmt_type_private)(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);
            int (*vidioc_s_fmt_vid_overlay)(struct file *file, void *fh, struct v4l2_format *f);
            int (*vidioc_s_fmt_vid_out)    (struct file *file, void *fh, struct v4l2_format *f);
            int (*vidioc_s_fmt_vid_out_overlay)(struct file *file, void *fh, struct v4l2_format *f);
            int (*vidioc_s_fmt_vbi_cap)    (struct file *file, void *fh, struct v4l2_format *f);
            int (*vidioc_s_fmt_vbi_out)    (struct file *file, void *fh, struct v4l2_format *f);
            int (*vidioc_s_fmt_sliced_vbi_cap)(struct file *file, void *fh, struct v4l2_format *f);
            int (*vidioc_s_fmt_sliced_vbi_out)(struct file *file, void *fh, struct v4l2_format *f);
            int (*vidioc_s_fmt_type_private)(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);
            int (*vidioc_try_fmt_vid_overlay)(struct file *file, void *fh, struct v4l2_format *f);
            int (*vidioc_try_fmt_vid_out)    (struct file *file, void *fh, struct v4l2_format *f);
            int (*vidioc_try_fmt_vid_out_overlay)(struct file *file, void *fh, struct v4l2_format *f);
            int (*vidioc_try_fmt_vbi_cap)    (struct file *file, void *fh, struct v4l2_format *f);
            int (*vidioc_try_fmt_vbi_out)    (struct file *file, void *fh, struct v4l2_format *f);
            int (*vidioc_try_fmt_sliced_vbi_cap)(struct file *file, void *fh, struct v4l2_format *f);
            int (*vidioc_try_fmt_sliced_vbi_out)(struct file *file, void *fh, struct v4l2_format *f);
            int (*vidioc_try_fmt_type_private)(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);
            int (*vidioc_overlay)    (struct file *file, void *fh, unsigned int i);
            int (*vidioc_g_fbuf)    (struct file *file, void *fh, struct v4l2_framebuffer *a);
            int (*vidioc_s_fbuf)    (struct file *file, void *fh, struct v4l2_framebuffer *a);
            /* 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);
            /* Output handling */
            int (*vidioc_enum_output)    (struct file *file, void *fh, struct v4l2_output *a);
            int (*vidioc_g_output)       (struct file *file, void *fh, unsigned int *i);
            int (*vidioc_s_output)       (struct file *file, void *fh, unsigned int i);
            /* Control handling */
            int (*vidioc_queryctrl)        (struct file *file, void *fh, struct v4l2_queryctrl *a);
            int (*vidioc_g_ctrl)           (struct file *file, void *fh, struct v4l2_control *a);
            int (*vidioc_s_ctrl)           (struct file *file, void *fh, struct v4l2_control *a);
            int (*vidioc_g_ext_ctrls)      (struct file *file, void *fh, struct v4l2_ext_controls *a);
            int (*vidioc_s_ext_ctrls)      (struct file *file, void *fh, struct v4l2_ext_controls *a);
            int (*vidioc_try_ext_ctrls)    (struct file *file, void *fh, struct v4l2_ext_controls *a);
            int (*vidioc_querymenu)        (struct file *file, void *fh, struct v4l2_querymenu *a);
            /* Audio ioctls */
            int (*vidioc_enumaudio)        (struct file *file, void *fh, struct v4l2_audio *a);
            int (*vidioc_g_audio)          (struct file *file, void *fh, struct v4l2_audio *a);
            int (*vidioc_s_audio)          (struct file *file, void *fh, struct v4l2_audio *a);
            /* Audio out ioctls */
            int (*vidioc_enumaudout)       (struct file *file, void *fh, struct v4l2_audioout *a);
            int (*vidioc_g_audout)         (struct file *file, void *fh, struct v4l2_audioout *a);
            int (*vidioc_s_audout)         (struct file *file, void *fh, struct v4l2_audioout *a);
            int (*vidioc_g_modulator)      (struct file *file, void *fh, struct v4l2_modulator *a);
            int (*vidioc_s_modulator)      (struct file *file, void *fh, struct v4l2_modulator *a);
            /* 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, struct v4l2_crop *a);
            /* Compression ioctls */
            int (*vidioc_g_jpegcomp)       (struct file *file, void *fh, struct v4l2_jpegcompression *a);
            int (*vidioc_s_jpegcomp)       (struct file *file, void *fh, struct v4l2_jpegcompression *a);
            int (*vidioc_g_enc_index)      (struct file *file, void *fh, struct v4l2_enc_idx *a);
            int (*vidioc_encoder_cmd)      (struct file *file, void *fh, struct v4l2_encoder_cmd *a);
            int (*vidioc_try_encoder_cmd)  (struct file *file, void *fh, struct v4l2_encoder_cmd *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);
            /* Tuner ioctls */
            int (*vidioc_g_tuner)          (struct file *file, void *fh, struct v4l2_tuner *a);
            int (*vidioc_s_tuner)          (struct file *file, void *fh, struct v4l2_tuner *a);
            int (*vidioc_g_frequency)      (struct file *file, void *fh, struct v4l2_frequency *a);
            int (*vidioc_s_frequency)      (struct file *file, void *fh, struct v4l2_frequency *a);
            /* Sliced VBI cap */
            int (*vidioc_g_sliced_vbi_cap) (struct file *file, void *fh, struct v4l2_sliced_vbi_cap *a);
            /* Log status ioctl */
            int (*vidioc_log_status)       (struct file *file, void *fh);
            int (*vidioc_s_hw_freq_seek)   (struct file *file, void *fh, struct v4l2_hw_freq_seek *a);
            /* Debugging ioctls */
    #ifdef CONFIG_VIDEO_ADV_DEBUG
            int (*vidioc_g_register)       (struct file *file, void *fh, struct v4l2_dbg_register *reg);
            int (*vidioc_s_register)       (struct file *file, void *fh, struct v4l2_dbg_register *reg);
    #endif
            int (*vidioc_g_chip_ident)       (struct file *file, void *fh, struct v4l2_dbg_chip_ident *chip);
            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);
            /* DV Timings IOCTLs */
            int (*vidioc_enum_dv_presets)  (struct file *file, void *fh, struct v4l2_dv_enum_preset *preset);
            int (*vidioc_s_dv_preset)      (struct file *file, void *fh, struct v4l2_dv_preset *preset);
            int (*vidioc_g_dv_preset)      (struct file *file, void *fh, struct v4l2_dv_preset *preset);
            int (*vidioc_query_dv_preset)  (struct file *file, void *fh, struct v4l2_dv_preset *qpreset);
            int (*vidioc_s_dv_timings)     (struct file *file, void *fh, struct v4l2_dv_timings *timings);
            int (*vidioc_g_dv_timings)     (struct file *file, void *fh, struct v4l2_dv_timings *timings);
            int (*vidioc_subscribe_event)  (struct v4l2_fh *fh, struct v4l2_event_subscription *sub);
            int (*vidioc_unsubscribe_event)(struct v4l2_fh *fh, struct v4l2_event_subscription *sub);
            /* For other private ioctls */
            long (*vidioc_default)         (struct file *file, void *fh, int cmd, void *arg);
    };

    ================================================================================

    ================================================================================

    ================================================================================

    static struct ov7670_format_struct {
            enum v4l2_mbus_pixelcode mbus_code;
            enum v4l2_colorspace colorspace;
            struct regval_list *regs;
            int cmatrix[CMATRIX_LEN];
        int bpp;
    } ov7670_format_struct ov7670_formats[] = {
        /* "YUYV 4:2:2"     */
            .desc        = "YUYV 4:2:2",
            .pixelformat    = V4L2_PIX_FMT_YUYV,
            .regs         = ov7670_fmt_yuv422,
            .cmatrix    = { 128, -128, 0, -34, -94, 128 },
            .bpp        = 2,
        /* "UYVY 4:2:2"     */
        /* "RGB 444"        */
        /* "RGB 565"        */
        /* "Raw RGB Bayer"  */
    }
    #define N_OV7670_FMTS ARRAY_SIZE(ov7670_formats)

    struct v4l2_fmtdesc {
            __u32               index;             /* Format number      */
            enum v4l2_buf_type  type;              /* buffer type        */
            __u32               flags;
            __u8                description[32];   /* Description string */
            __u32               pixelformat;       /* Format fourcc      */
            __u32               reserved[4];
    };
     
    //每一个subdev驱动程序实例应该创建这个结构,无论是独立或在一个更大的结构之中。
    struct v4l2_subdev {
            struct list_head list;
            struct module *owner;
            u32 flags;
            struct v4l2_device *v4l2_dev;
            const struct v4l2_subdev_ops *ops;
            /* name must be unique */
            char name[V4L2_SUBDEV_NAME_SIZE];
            /* can be used to group similar subdevs, value is driver-specific */
            u32 grp_id; 
            /* pointer to private data */
            void *priv;
    }; 
    struct v4l2_pix_format { 
            __u32                   width;
            __u32                   height;
            __u32                   pixelformat;
            enum v4l2_field         field;
            __u32                   bytesperline;   /* for padding, zero if unused */
            __u32                   sizeimage;
            enum v4l2_colorspace    colorspace;
            __u32                   priv;           /* private data, depends on pixelformat */
    };  

    struct v4l2_format { 
            enum v4l2_buf_type type;
            union { 
                    struct v4l2_pix_format          pix;     /* V4L2_BUF_TYPE_VIDEO_CAPTURE */
                    struct v4l2_window              win;     /* V4L2_BUF_TYPE_VIDEO_OVERLAY */
                    struct v4l2_vbi_format          vbi;     /* V4L2_BUF_TYPE_VBI_CAPTURE */
                    struct v4l2_sliced_vbi_format   sliced;  /* V4L2_BUF_TYPE_SLICED_VBI_CAPTURE */
                    __u8    raw_data[200];                   /* user-defined */
            } fmt;
    };

    static struct ov7670_win_size {
            int     width;
            int     height;
            unsigned char com7_bit;
            int     hstart;         /* Start/stop values for the camera.  Note */
            int     hstop;          /* that they do not always make complete */
            int     vstart;         /* sense to humans, but evidently the sensor */
            int     vstop;          /* will do the right thing... */
            struct regval_list *regs; /* Regs to tweak */
                    /* h/vref stuff */
    } ov7670_win_sizes[] = {
        /* VGA */
        /* CIF */
        /* QVGA */
        /* QCIF */
    }
    #define N_WIN_SIZES (ARRAY_SIZE(ov7670_win_sizes))

    展开全文
  • v4l2驱动框架

    2019-08-09 14:47:12
    参考:https://blog.csdn.net/duanlove/article/details/7853106
    展开全文
  • V4L2 驱动框架概览

    2019-02-08 22:08:49
    V4L2 驱动框架概览 OMAP 3 图像信号处理器 (ISP) 驱动 Chinese translated version of Documentation/video4linux/v4l2-framework.txt If you have any comment or update to the content, please contact the ...

    V4L2 驱动框架概览

    OMAP 3 图像信号处理器 (ISP) 驱动

    Chinese translated version of Documentation/video4linux/v4l2-framework.txt

    If you have any comment or update to the content, please contact the
    original document maintainer directly.  However, if you have a problem
    communicating in English you can also ask the Chinese maintainer for
    help.  Contact the Chinese maintainer if this translation is outdated
    or if there is a problem with the translation.

    Maintainer: Mauro Carvalho Chehab <mchehab@infradead.org>
    Chinese maintainer: Fu Wei <tekkamanninja@gmail.com>
    ---------------------------------------------------------------------
    Documentation/video4linux/v4l2-framework.txt 的中文翻译

    如果想评论或更新本文的内容,请直接联系原文档的维护者。如果你使用英文
    交流有困难的话,也可以向中文版维护者求助。如果本翻译更新不及时或者翻
    译存在问题,请联系中文版维护者。
    英文版维护者: Mauro Carvalho Chehab <mchehab@infradead.org>
    中文版维护者: 傅炜 Fu Wei <tekkamanninja@gmail.com>
    中文版翻译者: 傅炜 Fu Wei <tekkamanninja@gmail.com>
    中文版校译者: 傅炜 Fu Wei <tekkamanninja@gmail.com>


    以下为正文
    ---------------------------------------------------------------------
    V4L2 驱动框架概览
    ==============

    本文档描述 V4L2 框架所提供的各种结构和它们之间的关系。


    介绍
    ----

    大部分现代 V4L2 设备由多个 IC 组成,在 /dev 下导出多个设备节点,
    并同时创建非 V4L2 设备(如 DVB、ALSA、FB、I2C 和红外输入设备)。
    由于这种硬件的复杂性,V4L2 驱动也变得非常复杂。

    尤其是 V4L2 必须支持 IC 实现音视频的多路复用和编解码,这就更增加了其
    复杂性。通常这些 IC 通过一个或多个 I2C 总线连接到主桥驱动器,但也可
    使用其他总线。这些设备称为“子设备”。

    长期以来,这个框架仅限于通过 video_device 结构体创建 V4L 设备节点,
    并使用 video_buf 处理视频缓冲(注:本文不讨论 video_buf 框架)。

    这意味着所有驱动必须自己设置设备实例并连接到子设备。其中一部分要正确地
    完成是比较复杂的,使得许多驱动都没有正确地实现。

    由于框架的缺失,有很多通用代码都不可重复利用。

    因此,这个框架构建所有驱动都需要的基本结构块,而统一的框架将使通用代码
    创建成实用函数并在所有驱动中共享变得更加容易。


    驱动结构
    -------

    所有 V4L2 驱动都有如下结构:

    1) 每个设备实例的结构体--包含其设备状态。

    2) 初始化和控制子设备的方法(如果有)。

    3) 创建 V4L2 设备节点 (/dev/videoX、/dev/vbiX 和 /dev/radioX)
       并跟踪设备节点的特定数据。

    4) 特定文件句柄结构体--包含每个文件句柄的数据。

    5) 视频缓冲处理。

    以下是它们的初略关系图:

        device instances(设备实例)
          |
          +-sub-device instances(子设备实例)
          |
          \-V4L2 device nodes(V4L2 设备节点)
          |
          \-filehandle instances(文件句柄实例)


    框架结构
    -------

    该框架非常类似驱动结构:它有一个 v4l2_device 结构用于保存设备
    实例的数据;一个 v4l2_subdev 结构体代表子设备实例;video_device
    结构体保存 V4L2 设备节点的数据;将来 v4l2_fh 结构体将跟踪文件句柄
    实例(暂未尚未实现)。

    V4L2 框架也可与媒体框架整合(可选的)。如果驱动设置了 v4l2_device
    结构体的 mdev 域,子设备和视频节点的入口将自动出现在媒体框架中。


    v4l2_device 结构体
    ----------------

    每个设备实例都通过 v4l2_device (v4l2-device.h)结构体来表示。
    简单设备可以仅分配这个结构体,但在大多数情况下,都会将这个结构体
    嵌入到一个更大的结构体中。

    你必须注册这个设备实例:

        v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev);

    注册操作将会初始化 v4l2_device 结构体。如果 dev->driver_data 域
    为 NULL,就将其指向 v4l2_dev。

    需要与媒体框架整合的驱动必须手动设置 dev->driver_data,指向包含
    v4l2_device 结构体实例的驱动特定设备结构体。这可以在注册 V4L2 设备
    实例前通过 dev_set_drvdata() 函数完成。同时必须设置 v4l2_device
    结构体的 mdev 域,指向适当的初始化并注册过的 media_device 实例。

    如果 v4l2_dev->name 为空,则它将被设置为从 dev 中衍生出的值(为了
    更加精确,形式为驱动名后跟 bus_id)。如果你在调用 v4l2_device_register
    前已经设置好了,则不会被修改。如果 dev 为 NULL,则你*必须*在调用
    v4l2_device_register 前设置 v4l2_dev->name。

    你可以基于驱动名和驱动的全局 atomic_t 类型的实例编号,通过
    v4l2_device_set_name() 设置 name。这样会生成类似 ivtv0、ivtv1 等
    名字。若驱动名以数字结尾,则会在编号和驱动名间插入一个破折号,如:
    cx18-0、cx18-1 等。此函数返回实例编号。

    第一个 “dev” 参数通常是一个指向 pci_dev、usb_interface 或
    platform_device 的指针。很少使其为 NULL,除非是一个ISA设备或者
    当一个设备创建了多个 PCI 设备,使得 v4l2_dev 无法与一个特定的父设备
    关联。

    你也可以提供一个 notify() 回调,使子设备可以调用它实现事件通知。
    但这个设置与子设备相关。子设备支持的任何通知必须在
    include/media/<subdevice>.h 中定义一个消息头。

    注销 v4l2_device 使用如下函数:

        v4l2_device_unregister(struct v4l2_device *v4l2_dev);

    如果 dev->driver_data 域指向 v4l2_dev,将会被重置为 NULL。注销同时
    会自动从设备中注销所有子设备。

    如果你有一个热插拔设备(如USB设备),则当断开发生时,父设备将无效。
    由于 v4l2_device 有一个指向父设备的指针必须被清除,同时标志父设备
    已消失,所以必须调用以下函数:

        v4l2_device_disconnect(struct v4l2_device *v4l2_dev);

    这个函数并*不*注销子设备,因此你依然要调用 v4l2_device_unregister()
    函数。如果你的驱动器并非热插拔的,就没有必要调用 v4l2_device_disconnect()。

    有时你需要遍历所有被特定驱动注册的设备。这通常发生在多个设备驱动使用
    同一个硬件的情况下。如:ivtvfb 驱动是一个使用 ivtv 硬件的帧缓冲驱动,
    同时 alsa 驱动也使用此硬件。

    你可以使用如下例程遍历所有注册的设备:

    static int callback(struct device *dev, void *p)
    {
        struct v4l2_device *v4l2_dev = dev_get_drvdata(dev);

        /* 测试这个设备是否已经初始化 */
        if (v4l2_dev == NULL)
            return 0;
        ...
        return 0;
    }

    int iterate(void *p)
    {
        struct device_driver *drv;
        int err;

        /* 在PCI 总线上查找ivtv驱动。
           pci_bus_type是全局的. 对于USB总线使用usb_bus_type。 */
        drv = driver_find("ivtv", &pci_bus_type);
        /* 遍历所有的ivtv设备实例 */
        err = driver_for_each_device(drv, NULL, p, callback);
        put_driver(drv);
        return err;
    }

    有时你需要一个设备实例的运行计数。这个通常用于映射一个设备实例到一个
    模块选择数组的索引。

    推荐方法如下:

    static atomic_t drv_instance = ATOMIC_INIT(0);

    static int drv_probe(struct pci_dev *pdev, const struct pci_device_id *pci_id)
    {
        ...
        state->instance = atomic_inc_return(&drv_instance) - 1;
    }

    如果你有多个设备节点,对于热插拔设备,知道何时注销 v4l2_device 结构体
    就比较困难。为此 v4l2_device 有引用计数支持。当调用 video_register_device
    时增加引用计数,而设备节点释放时减小引用计数。当引用计数为零,则
    v4l2_device 的release() 回调将被执行。你就可以在此时做最后的清理工作。

    如果创建了其他设备节点(比如 ALSA),则你可以通过以下函数手动增减
    引用计数:

    void v4l2_device_get(struct v4l2_device *v4l2_dev);

    或:

    int v4l2_device_put(struct v4l2_device *v4l2_dev);

    由于引用技术初始化为 1 ,你也需要在 disconnect() 回调(对于 USB 设备)中
    调用 v4l2_device_put,或者 remove() 回调(例如对于 PCI 设备),否则
    引用计数将永远不会为 0 。

    v4l2_subdev结构体
    ------------------

    许多驱动需要与子设备通信。这些设备可以完成各种任务,但通常他们负责
    音视频复用和编解码。如网络摄像头的子设备通常是传感器和摄像头控制器。

    这些一般为 I2C 接口设备,但并不一定都是。为了给驱动提供调用子设备的
    统一接口,v4l2_subdev 结构体(v4l2-subdev.h)产生了。

    每个子设备驱动都必须有一个 v4l2_subdev 结构体。这个结构体可以单独
    代表一个简单的子设备,也可以嵌入到一个更大的结构体中,与更多设备状态
    信息保存在一起。通常有一个下级设备结构体(比如:i2c_client)包含了
    内核创建的设备数据。建议使用 v4l2_set_subdevdata() 将这个结构体的
    指针保存在 v4l2_subdev 的私有数据域(dev_priv)中。这使得通过 v4l2_subdev
    找到实际的低层总线特定设备数据变得容易。

    你同时需要一个从低层结构体获取 v4l2_subdev 指针的方法。对于常用的
    i2c_client 结构体,i2c_set_clientdata() 函数可用于保存一个 v4l2_subdev
    指针;对于其他总线你可能需要使用其他相关函数。

    桥驱动中也应保存每个子设备的私有数据,比如一个指向特定桥的各设备私有
    数据的指针。为此 v4l2_subdev 结构体提供主机私有数据域(host_priv),
    并可通过 v4l2_get_subdev_hostdata() 和 v4l2_set_subdev_hostdata()
    访问。

    从总线桥驱动的视角,驱动加载子设备模块并以某种方式获得 v4l2_subdev
    结构体指针。对于 i2c 总线设备相对简单:调用 i2c_get_clientdata()。
    对于其他总线也需要做类似的操作。针对 I2C 总线上的子设备辅助函数帮你
    完成了大部分复杂的工作。

    每个 v4l2_subdev 都包含子设备驱动需要实现的函数指针(如果对此设备
    不适用,可为NULL)。由于子设备可完成许多不同的工作,而在一个庞大的
    函数指针结构体中通常仅有少数有用的函数实现其功能肯定不合适。所以,
    函数指针根据其实现的功能被分类,每一类都有自己的函数指针结构体。

    顶层函数指针结构体包含了指向各类函数指针结构体的指针,如果子设备驱动
    不支持该类函数中的任何一个功能,则指向该类结构体的指针为NULL。

    这些结构体定义如下:

    struct v4l2_subdev_core_ops {
        int (*log_status)(struct v4l2_subdev *sd);
        int (*init)(struct v4l2_subdev *sd, u32 val);
        ...
    };

    struct v4l2_subdev_tuner_ops {
        ...
    };

    struct v4l2_subdev_audio_ops {
        ...
    };

    struct v4l2_subdev_video_ops {
        ...
    };

    struct v4l2_subdev_pad_ops {
        ...
    };

    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_pad_ops *video;
    };

    其中 core(核心)函数集通常可用于所有子设备,其他类别的实现依赖于
    子设备。如视频设备可能不支持音频操作函数,反之亦然。

    这样的设置在限制了函数指针数量的同时,还使增加新的操作函数和分类
    变得较为容易。

    子设备驱动可使用如下函数初始化 v4l2_subdev 结构体:

        v4l2_subdev_init(sd, &ops);

    然后,你必须用一个唯一的名字初始化 subdev->name,并初始化模块的
    owner 域。若使用 i2c 辅助函数,这些都会帮你处理好。

    若需同媒体框架整合,你必须调用 media_entity_init() 初始化 v4l2_subdev
    结构体中的 media_entity 结构体(entity 域):

        struct media_pad *pads = &my_sd->pads;
        int err;

        err = media_entity_init(&sd->entity, npads, pads, 0);

    pads 数组必须预先初始化。无须手动设置 media_entity 的 type 和
    name 域,但如有必要,revision 域必须初始化。

    当(任何)子设备节点被打开/关闭,对 entity 的引用将被自动获取/释放。

    在子设备被注销之后,不要忘记清理 media_entity 结构体:

        media_entity_cleanup(&sd->entity);

    如果子设备驱动趋向于处理视频并整合进了媒体框架,必须使用 v4l2_subdev_pad_ops
    替代 v4l2_subdev_video_ops 实现格式相关的功能。

    这种情况下,子设备驱动应该设置 link_validate 域,以提供它自身的链接
    验证函数。链接验证函数应对管道(两端链接的都是 V4L2 子设备)中的每个
    链接调用。驱动还要负责验证子设备和视频节点间格式配置的正确性。

    如果 link_validate 操作没有设置,默认的 v4l2_subdev_link_validate_default()
    函数将会被调用。这个函数保证宽、高和媒体总线像素格式在链接的收发两端
    都一致。子设备驱动除了它们自己的检测外,也可以自由使用这个函数以执行
    上面提到的检查。

    设备(桥)驱动程序必须向 v4l2_device 注册 v4l2_subdev:

        int err = v4l2_device_register_subdev(v4l2_dev, sd);

    如果子设备模块在它注册前消失,这个操作可能失败。在这个函数成功返回后,
    subdev->dev 域就指向了 v4l2_device。

    如果 v4l2_device 父设备的 mdev 域为非 NULL 值,则子设备实体将被自动
    注册为媒体设备。

    注销子设备则可用如下函数:

        v4l2_device_unregister_subdev(sd);

    此后,子设备模块就可卸载,且 sd->dev == NULL。

    注册之设备后,可通过以下方式直接调用其操作函数:

        err = sd->ops->core->g_std(sd, &norm);

    但使用如下宏会比较容易且合适:

        err = v4l2_subdev_call(sd, core, g_std, &norm);

    这个宏将会做 NULL 指针检查,如果 subdev 为 NULL,则返回-ENODEV;如果
    subdev->core 或 subdev->core->g_std 为 NULL,则返回 -ENOIOCTLCMD;
    否则将返回 subdev->ops->core->g_std ops 调用的实际结果。

    有时也可能同时调用所有或一系列子设备的某个操作函数:

        v4l2_device_call_all(v4l2_dev, 0, core, g_std, &norm);

    任何不支持此操作的子设备都会被跳过,并忽略错误返回值。但如果你需要
    检查出错码,则可使用如下函数:

        err = v4l2_device_call_until_err(v4l2_dev, 0, core, g_std, &norm);

    除 -ENOIOCTLCMD 外的任何错误都会跳出循环并返回错误值。如果(除 -ENOIOCTLCMD
    外)没有错误发生,则返回 0。

    对于以上两个函数的第二个参数为组 ID。如果为 0,则所有子设备都会执行
    这个操作。如果为非 0 值,则只有那些组 ID 匹配的子设备才会执行此操作。
    在桥驱动注册一个子设备前,可以设置 sd->grp_id 为任何期望值(默认值为
    0)。这个值属于桥驱动,且子设备驱动将不会修改和使用它。

    组 ID 赋予了桥驱动更多对于如何调用回调的控制。例如,电路板上有多个
    音频芯片,每个都有改变音量的能力。但当用户想要改变音量的时候,通常
    只有一个会被实际使用。你可以对这样的子设备设置组 ID 为(例如 AUDIO_CONTROLLER)
    并在调用 v4l2_device_call_all() 时指定它为组 ID 值。这就保证了只有
    需要的子设备才会执行这个回调。

    如果子设备需要通知它的 v4l2_device 父设备一个事件,可以调用
    v4l2_subdev_notify(sd, notification, arg)。这个宏检查是否有一个
    notify() 回调被注册,如果没有,返回 -ENODEV。否则返回 notify() 调用
    结果。

    使用 v4l2_subdev 的好处在于它是一个通用结构体,且不包含任何底层硬件
    信息。所有驱动可以包含多个 I2C 总线的子设备,但也有子设备是通过 GPIO
    控制。这个区别仅在配置设备时有关系,一旦子设备注册完成,对于 v4l2
    子系统来说就完全透明了。


    V4L2 子设备用户空间API
    --------------------

    除了通过 v4l2_subdev_ops 结构导出的内核 API,V4L2 子设备也可以直接
    通过用户空间应用程序来控制。

    可以在 /dev 中创建名为 v4l-subdevX 设备节点,以通过其直接访问子设备。
    如果子设备支持用户空间直接配置,必须在注册前设置 V4L2_SUBDEV_FL_HAS_DEVNODE
    标志。

    注册子设备之后, v4l2_device 驱动会通过调用 v4l2_device_register_subdev_nodes()
    函数为所有已注册并设置了 V4L2_SUBDEV_FL_HAS_DEVNODE 的子设备创建
    设备节点。这些设备节点会在子设备注销时自动删除。

    这些设备节点处理 V4L2 API 的一个子集。

    VIDIOC_QUERYCTRL
    VIDIOC_QUERYMENU
    VIDIOC_G_CTRL
    VIDIOC_S_CTRL
    VIDIOC_G_EXT_CTRLS
    VIDIOC_S_EXT_CTRLS
    VIDIOC_TRY_EXT_CTRLS

        这些 ioctls 控制与 V4L2 中定义的一致。他们行为相同,唯一的
        不同是他们只处理子设备的控制实现。根据驱动程序,这些控制也
        可以通过一个(或多个) V4L2 设备节点访问。

    VIDIOC_DQEVENT
    VIDIOC_SUBSCRIBE_EVENT
    VIDIOC_UNSUBSCRIBE_EVENT

        这些  ioctls 事件与 V4L2 中定义的一致。他们行为相同,唯一的
        不同是他们只处理子设备产生的事件。根据驱动程序,这些事件也
        可以通过一个(或多个) V4L2 设备节点上报。

        要使用事件通知的子设备驱动,在注册子设备前必须在 v4l2_subdev::flags
        中设置 V4L2_SUBDEV_USES_EVENTS 并在 v4l2_subdev::nevents
        中初始化事件队列深度。注册完成后,事件会在 v4l2_subdev::devnode
        设备节点中像通常一样被排队。

        为正确支持事件机制,poll() 文件操作也应被实现。

    私有 ioctls

        不在以上列表中的所有 ioctls 会通过 core::ioctl 操作直接传递
        给子设备驱动。


    I2C 子设备驱动
    -------------

    由于这些驱动很常见,所以内特提供了特定的辅助函数(v4l2-common.h)让这些
    设备的使用更加容易。

    添加 v4l2_subdev 支持的推荐方法是让 I2C 驱动将 v4l2_subdev 结构体
    嵌入到为每个 I2C 设备实例创建的状态结构体中。而最简单的设备没有状态
    结构体,此时可以直接创建一个 v4l2_subdev 结构体。

    一个典型的状态结构体如下所示(‘chipname’用芯片名代替):

    struct chipname_state {
        struct v4l2_subdev sd;
        ...  /* 附加的状态域*/
    };

    初始化 v4l2_subdev 结构体的方法如下:

        v4l2_i2c_subdev_init(&state->sd, client, subdev_ops);

    这个函数将填充 v4l2_subdev 结构体中的所有域,并保证 v4l2_subdev 和
    i2c_client 都指向彼此。

    同时,你也应该为从 v4l2_subdev 指针找到 chipname_state 结构体指针
    添加一个辅助内联函数。

    static inline struct chipname_state *to_state(struct v4l2_subdev *sd)
    {
        return container_of(sd, struct chipname_state, sd);
    }

    使用以下函数可以通过 v4l2_subdev 结构体指针获得 i2c_client 结构体
    指针:

        struct i2c_client *client = v4l2_get_subdevdata(sd);

    而以下函数则相反,通过 i2c_client 结构体指针获得 v4l2_subdev 结构体
    指针:

        struct v4l2_subdev *sd = i2c_get_clientdata(client);

    当 remove()函数被调用前,必须保证先调用 v4l2_device_unregister_subdev(sd)。
    此操作将会从桥驱动中注销子设备。即使子设备没有注册,调用此函数也是
    安全的。

    必须这样做的原因是:当桥驱动注销 i2c 适配器时,remove()回调函数
    会被那个适配器上的 i2c 设备调用。此后,相应的 v4l2_subdev 结构体
    就不存在了,所有它们必须先被注销。在 remove()回调函数中调用
    v4l2_device_unregister_subdev(sd),可以保证执行总是正确的。


    桥驱动也有一些辅组函数可用:

    struct v4l2_subdev *sd = v4l2_i2c_new_subdev(v4l2_dev, adapter,
               "module_foo", "chipid", 0x36, NULL);

    这个函数会加载给定的模块(如果没有模块需要加载,可以为 NULL),
    并用给定的 i2c 适配器结构体指针(i2c_adapter)和 器件地址(chip/address)
    作为参数调用 i2c_new_device()。如果一切顺利,则就在 v4l2_device
    中注册了子设备。

    你也可以利用 v4l2_i2c_new_subdev()的最后一个参数,传递一个可能的
    I2C 地址数组,让函数自动探测。这些探测地址只有在前一个参数为 0 的
    情况下使用。非零参数意味着你知道准确的 i2c 地址,所以此时无须进行
    探测。

    如果出错,两个函数都返回 NULL。

    注意:传递给 v4l2_i2c_new_subdev()的 chipid 通常与模块名一致。
    它允许你指定一个芯片的变体,比如“saa7114”或“saa7115”。一般通过
    i2c 驱动自动探测。chipid 的使用是在今后需要深入了解的事情。这个与
    i2c 驱动不同,较容易混淆。要知道支持哪些芯片变体,你可以查阅 i2c
    驱动代码的 i2c_device_id 表,上面列出了所有可能支持的芯片。

    还有两个辅助函数:

    v4l2_i2c_new_subdev_cfg:这个函数添加新的 irq 和 platform_data
    参数,并有‘addr’和‘probed_addrs’参数:如果 addr 非零,则被使用
    (不探测变体),否则 probed_addrs 中的地址将用于自动探测。

    例如:以下代码将会探测地址(0x10):

    struct v4l2_subdev *sd = v4l2_i2c_new_subdev_cfg(v4l2_dev, adapter,
               "module_foo", "chipid", 0, NULL, 0, I2C_ADDRS(0x10));

    v4l2_i2c_new_subdev_board 使用一个 i2c_board_info 结构体,将其
    替代 irq、platform_data 和 add r参数传递给 i2c 驱动。

    如果子设备支持 s_config 核心操作,这个操作会在子设备配置好之后以 irq 和
    platform_data 为参数调用。早期的 v4l2_i2c_new_(probed_)subdev 函数
    同样也会调用 s_config,但仅在 irq 为 0 且 platform_data 为 NULL 时。

    video_device结构体
    -----------------

    在 /dev 目录下的实际设备节点根据 video_device 结构体(v4l2-dev.h)
    创建。此结构体既可以动态分配也可以嵌入到一个更大的结构体中。

    动态分配方法如下:

        struct video_device *vdev = video_device_alloc();

        if (vdev == NULL)
            return -ENOMEM;

        vdev->release = video_device_release;

    如果将其嵌入到一个大结构体中,则必须自己实现 release()回调。

        struct video_device *vdev = &my_vdev->vdev;

        vdev->release = my_vdev_release;

    release()回调必须被设置,且在最后一个 video_device 用户退出之后
    被调用。

    默认的 video_device_release()回调只是调用 kfree 来释放之前分配的
    内存。

    你应该设置这些域:

    - v4l2_dev: 设置为 v4l2_device 父设备。

    - name: 设置为唯一的描述性设备名。

    - fops: 设置为已有的 v4l2_file_operations 结构体。

    - ioctl_ops: 如果你使用v4l2_ioctl_ops 来简化 ioctl 的维护
      (强烈建议使用,且将来可能变为强制性的!),然后设置你自己的
      v4l2_ioctl_ops 结构体.

    - lock: 如果你要在驱动中实现所有的锁操作,则设为 NULL 。否则
      就要设置一个指向 struct mutex_lock 结构体的指针,这个锁将
      在 unlocked_ioctl 文件操作被调用前由内核获得,并在调用返回后
      释放。详见下一节。

    - prio: 保持对优先级的跟踪。用于实现 VIDIOC_G/S_PRIORITY。如果
      设置为 NULL,则会使用 v4l2_device 中的 v4l2_prio_state 结构体。
      如果要对每个设备节点(组)实现独立的优先级,可以将其指向自己
      实现的 v4l2_prio_state 结构体。

    - parent: 仅在使用 NULL 作为父设备结构体参数注册 v4l2_device 时
      设置此参数。只有在一个硬件设备包含多一个 PCI 设备,共享同一个
      v4l2_device 核心时才会发生。

      cx88 驱动就是一个例子:一个 v4l2_device 结构体核心,被一个裸的
      视频 PCI 设备(cx8800)和一个 MPEG PCI 设备(cx8802)共用。由于
      v4l2_device 无法与特定的 PCI 设备关联,所有没有设置父设备。但当
      video_device 配置后,就知道使用哪个父 PCI 设备了。

    - flags:可选。如果你要让框架处理设置 VIDIOC_G/S_PRIORITY ioctls,
      请设置 V4L2_FL_USE_FH_PRIO。这要求你使用 v4l2_fh 结构体。
      一旦所有驱动使用了核心的优先级处理,最终这个标志将消失。但现在它
      必须被显式设置。

    如果你使用 v4l2_ioctl_ops,则应该在 v4l2_file_operations 结构体中
    设置 .unlocked_ioctl 指向 video_ioctl2。

    请勿使用 .ioctl!它已被废弃,今后将消失。

    某些情况下你要告诉核心:你在 v4l2_ioctl_ops 指定的某个函数应被忽略。
    你可以在 video_device_register 被调用前通过以下函数标记这个 ioctls。

    void v4l2_disable_ioctl(struct video_device *vdev, unsigned int cmd);

    基于外部因素(例如某个板卡已被使用),在不创建新结构体的情况下,你想
    要关闭 v4l2_ioctl_ops 中某个特性往往需要这个机制。

    v4l2_file_operations 结构体是 file_operations 的一个子集。其主要
    区别在于:因 inode 参数从未被使用,它将被忽略。

    如果需要与媒体框架整合,你必须通过调用 media_entity_init() 初始化
    嵌入在 video_device 结构体中的 media_entity(entity 域)结构体:

        struct media_pad *pad = &my_vdev->pad;
        int err;

        err = media_entity_init(&vdev->entity, 1, pad, 0);

    pads 数组必须预先初始化。没有必要手动设置 media_entity 的 type 和
    name 域。

    当(任何)子设备节点被打开/关闭,对 entity 的引用将被自动获取/释放。

    v4l2_file_operations 与锁
    --------------------------

    你可以在 video_device 结构体中设置一个指向 mutex_lock 的指针。通常
    这既可是一个顶层互斥锁也可为设备节点自身的互斥锁。默认情况下,此锁
    用于 unlocked_ioctl,但为了使用 ioctls 你通过以下函数可禁用锁定:

        void v4l2_disable_ioctl_locking(struct video_device *vdev, unsigned int cmd);

    例如: v4l2_disable_ioctl_locking(vdev, VIDIOC_DQBUF);

    你必须在注册 video_device 前调用这个函数。

    特别是对于 USB 驱动程序,某些命令(如设置控制)需要很长的时间,可能
    需要自行为缓冲区队列的 ioctls 实现锁定。

    如果你需要更细粒度的锁,你必须设置 mutex_lock 为 NULL,并完全自己实现
    锁机制。

    这完全由驱动开发者决定使用何种方法。然而,如果你的驱动存在长延时操作
    (例如,改变 USB 摄像头的曝光时间可能需要较长时间),而你又想让用户
    在等待长延时操作完成期间做其他的事,则你最好自己实现锁机制。

    如果指定一个锁,则所有 ioctl 操作将在这个锁的作用下串行执行。如果你
    使用 videobuf,则必须将同一个锁传递给 videobuf 队列初始化函数;如
    videobuf 必须等待一帧的到达,则可临时解锁并在这之后重新上锁。如果驱动
    也在代码执行期间等待,则可做同样的工作(临时解锁,再上锁)让其他进程
    可以在第一个进程阻塞时访问设备节点。

    在使用 videobuf2 的情况下,必须实现 wait_prepare 和 wait_finish 回调
    在适当的时候解锁/加锁。进一步来说,如果你在 video_device 结构体中使用
    锁,则必须在 wait_prepare 和 wait_finish 中对这个互斥锁进行解锁/加锁。

    热插拔的断开实现也必须在调用 v4l2_device_disconnect 前获得锁。

    video_device注册
    ---------------

    接下来你需要注册视频设备:这会为你创建一个字符设备。

        err = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
        if (err) {
            video_device_release(vdev); /* or kfree(my_vdev); */
            return err;
        }

    如果 v4l2_device 父设备的 mdev 域为非 NULL 值,视频设备实体将自动
    注册为媒体设备。

    注册哪种设备是根据类型(type)参数。存在以下类型:

    VFL_TYPE_GRABBER: 用于视频输入/输出设备的 videoX
    VFL_TYPE_VBI: 用于垂直消隐数据的 vbiX (例如,隐藏式字幕,图文电视)
    VFL_TYPE_RADIO: 用于广播调谐器的 radioX

    最后一个参数让你确定一个所控制设备的设备节点号数量(例如 videoX 中的 X)。
    通常你可以传入-1,让 v4l2 框架自己选择第一个空闲的编号。但是有时用户
    需要选择一个特定的节点号。驱动允许用户通过驱动模块参数选择一个特定的
    设备节点号是很普遍的。这个编号将会传递给这个函数,且 video_register_device
    将会试图选择这个设备节点号。如果这个编号被占用,下一个空闲的设备节点
    编号将被选中,并向内核日志中发送一个警告信息。

    另一个使用场景是当驱动创建多个设备时。这种情况下,对不同的视频设备在
    编号上使用不同的范围是很有用的。例如,视频捕获设备从 0 开始,视频
    输出设备从 16 开始。所以你可以使用最后一个参数来指定设备节点号最小值,
    而 v4l2 框架会试图选择第一个的空闲编号(等于或大于你提供的编号)。
    如果失败,则它会就选择第一个空闲的编号。

    由于这种情况下,你会忽略无法选择特定设备节点号的警告,则可调用
    video_register_device_no_warn() 函数避免警告信息的产生。

    只要设备节点被创建,一些属性也会同时创建。在 /sys/class/video4linux
    目录中你会找到这些设备。例如进入其中的 video0 目录,你会看到‘name’和
    ‘index’属性。‘name’属性值就是 video_device 结构体中的‘name’域。

    ‘index’属性值就是设备节点的索引值:每次调用 video_register_device(),
    索引值都递增 1 。第一个视频设备节点总是从索引值 0 开始。

    用户可以设置 udev 规则,利用索引属性生成花哨的设备名(例如:用‘mpegX’
    代表 MPEG 视频捕获设备节点)。

    在设备成功注册后,就可以使用这些域:

    - vfl_type: 传递给 video_register_device 的设备类型。
    - minor: 已指派的次设备号。
    - num: 设备节点编号 (例如 videoX 中的 X)。
    - index: 设备索引号。

    如果注册失败,你必须调用 video_device_release() 来释放已分配的
    video_device 结构体;如果 video_device 是嵌入在自己创建的结构体中,
    你也必须释放它。vdev->release() 回调不会在注册失败之后被调用,
    你也不应试图在注册失败后注销设备。


    video_device 注销
    ----------------

    当视频设备节点已被移除,不论是卸载驱动还是USB设备断开,你都应注销
    它们:

        video_unregister_device(vdev);

    这个操作将从 sysfs 中移除设备节点(导致 udev 将其从 /dev 中移除)。

    video_unregister_device() 返回之后,就无法完成打开操作。尽管如此,
    USB 设备的情况则不同,某些应用程序可能依然打开着其中一个已注销设备
    节点。所以在注销之后,所有文件操作(当然除了 release )也应返回错误值。

    当最后一个视频设备节点的用户退出,则 vdev->release() 回调会被调用,
    并且你可以做最后的清理操作。

    不要忘记清理与视频设备相关的媒体入口(如果被初始化过):

        media_entity_cleanup(&vdev->entity);

    这可以在 release 回调中完成。


    video_device 辅助函数
    ---------------------

    一些有用的辅助函数如下:

    - file/video_device 私有数据

    你可以用以下函数在 video_device 结构体中设置/获取驱动私有数据:

    void *video_get_drvdata(struct video_device *vdev);
    void video_set_drvdata(struct video_device *vdev, void *data);

    注意:在调用 video_register_device() 前执行 video_set_drvdata()
    是安全的。

    而以下函数:

    struct video_device *video_devdata(struct file *file);

    返回 file 结构体中拥有的的 video_device 指针。

    video_drvdata 辅助函数结合了 video_get_drvdata 和 video_devdata
    的功能:

    void *video_drvdata(struct file *file);

    你可以使用如下代码从 video_device 结构体中获取 v4l2_device 结构体
    指针:

    struct v4l2_device *v4l2_dev = vdev->v4l2_dev;

    - 设备节点名

    video_device 设备节点在内核中的名称可以通过以下函数获得

    const char *video_device_node_name(struct video_device *vdev);

    这个名字被用户空间工具(例如 udev)作为提示信息使用。应尽可能使用
    此功能,而非访问 video_device::num 和 video_device::minor 域。


    视频缓冲辅助函数
    ---------------

    v4l2 核心 API 提供了一个处理视频缓冲的标准方法(称为“videobuf”)。
    这些方法使驱动可以通过统一的方式实现 read()、mmap() 和 overlay()。
    目前在设备上支持视频缓冲的方法有分散/聚集 DMA(videobuf-dma-sg)、
    线性 DMA(videobuf-dma-contig)以及大多用于 USB 设备的用 vmalloc
    分配的缓冲(videobuf-vmalloc)。

    请参阅 Documentation/video4linux/videobuf,以获得更多关于 videobuf
    层的使用信息。

    v4l2_fh 结构体
    -------------

    v4l2_fh 结构体提供一个保存用于 V4L2 框架的文件句柄特定数据的简单方法。
    如果 video_device 的 flag 设置了 V4L2_FL_USE_FH_PRIO 标志,新驱动
    必须使用 v4l2_fh 结构体,因为它也用于实现优先级处理(VIDIOC_G/S_PRIORITY)。

    v4l2_fh 的用户(位于 V4l2 框架中,并非驱动)可通过测试
    video_device->flags 中的 V4L2_FL_USES_V4L2_FH 位得知驱动是否使用
    v4l2_fh 作为他的 file->private_data 指针。这个位会在调用 v4l2_fh_init()
    时被设置。

    v4l2_fh 结构体作为驱动自身文件句柄结构体的一部分被分配,且驱动在
    其打开函数中将 file->private_data 指向它。

    在许多情况下,v4l2_fh 结构体会嵌入到一个更大的结构体中。这钟情况下,
    应该在 open() 中调用 v4l2_fh_init+v4l2_fh_add,并在 release() 中
    调用 v4l2_fh_del+v4l2_fh_exit。

    驱动可以通过使用 container_of 宏提取他们自己的文件句柄结构体。例如:

    struct my_fh {
        int blah;
        struct v4l2_fh fh;
    };

    ...

    int my_open(struct file *file)
    {
        struct my_fh *my_fh;
        struct video_device *vfd;
        int ret;

        ...

        my_fh = kzalloc(sizeof(*my_fh), GFP_KERNEL);

        ...

        v4l2_fh_init(&my_fh->fh, vfd);

        ...

        file->private_data = &my_fh->fh;
        v4l2_fh_add(&my_fh->fh);
        return 0;
    }

    int my_release(struct file *file)
    {
        struct v4l2_fh *fh = file->private_data;
        struct my_fh *my_fh = container_of(fh, struct my_fh, fh);

        ...
        v4l2_fh_del(&my_fh->fh);
        v4l2_fh_exit(&my_fh->fh);
        kfree(my_fh);
        return 0;
    }

    以下是 v4l2_fh 函数使用的简介:

    void v4l2_fh_init(struct v4l2_fh *fh, struct video_device *vdev)

      初始化文件句柄。这*必须*在驱动的 v4l2_file_operations->open()
      函数中执行。

    void v4l2_fh_add(struct v4l2_fh *fh)

      添加一个 v4l2_fh 到 video_device 文件句柄列表。一旦文件句柄
      初始化完成就必须调用。

    void v4l2_fh_del(struct v4l2_fh *fh)

      从 video_device() 中解除文件句柄的关联。文件句柄的退出函数也
      将被调用。

    void v4l2_fh_exit(struct v4l2_fh *fh)

      清理文件句柄。在清理完 v4l2_fh 后,相关内存会被释放。


    如果 v4l2_fh 不是嵌入在其他结构体中的,则可以用这些辅助函数:

    int v4l2_fh_open(struct file *filp)

      分配一个 v4l2_fh 结构体空间,初始化并将其添加到 file 结构体相关的
      video_device 结构体中。

    int v4l2_fh_release(struct file *filp)

      从 file 结构体相关的 video_device 结构体中删除 v4l2_fh ,清理
      v4l2_fh 并释放空间。

    这两个函数可以插入到 v4l2_file_operation 的 open() 和 release()
    操作中。


    某些驱动需要在第一个文件句柄打开和最后一个文件句柄关闭的时候做些
    工作。所以加入了两个辅助函数以检查 v4l2_fh 结构体是否是相关设备
    节点打开的唯一文件句柄。

    int v4l2_fh_is_singular(struct v4l2_fh *fh)

      如果此文件句柄是唯一打开的文件句柄,则返回 1 ,否则返回 0 。

    int v4l2_fh_is_singular_file(struct file *filp)

      功能相同,但通过 filp->private_data 调用 v4l2_fh_is_singular。


    V4L2 事件机制
    -----------

    V4L2 事件机制提供了一个通用的方法将事件传递到用户空间。驱动必须使用
    v4l2_fh 才能支持 V4L2 事件机制。


    事件通过一个类型和选择 ID 来定义。ID 对应一个 V4L2 对象,例如
    一个控制 ID。如果未使用,则 ID 为 0。

    当用户订阅一个事件,驱动会为此分配一些 kevent 结构体。所以每个
    事件组(类型、ID)都会有自己的一套 kevent 结构体。这保证了如果
    一个驱动短时间内产生了许多同类事件,不会覆盖其他类型的事件。

    但如果你收到的事件数量大于同类事件 kevent 的保存数量,则最早的
    事件将被丢弃,并加入新事件。

    此外,v4l2_subscribed_event 结构体内部有可供驱动设置的 merge() 和
    replace() 回调,这些回调会在新事件产生且没有多余空间的时候被调用。
    replace() 回调让你可以将早期事件的净荷替换为新事件的净荷,将早期
    净荷的相关数据合并到替换进来的新净荷中。当该类型的事件仅分配了一个
    kevent 结构体时,它将被调用。merge() 回调让你可以合并最早的事件净荷
    到在它之后的那个事件净荷中。当该类型的事件分配了两个或更多 kevent
    结构体时,它将被调用。

    这种方法不会有状态信息丢失,只会导致中间步骤信息丢失。


    关于 replace/merge 回调的一个不错的例子在 v4l2-event.c 中:用于
    控制事件的 ctrls_replace() 和 ctrls_merge() 回调。

    注意:这些回调可以在中断上下文中调用,所以它们必须尽快完成并退出。

    有用的函数:

    void v4l2_event_queue(struct video_device *vdev, const struct v4l2_event *ev)

      将事件加入视频设备的队列。驱动仅负责填充 type 和 data 域。
      其他域由 V4L2 填充。

    int v4l2_event_subscribe(struct v4l2_fh *fh,
                 struct v4l2_event_subscription *sub, unsigned elems,
                 const struct v4l2_subscribed_event_ops *ops)

      video_device->ioctl_ops->vidioc_subscribe_event 必须检测驱动能
      产生特定 id 的事件。然后调用 v4l2_event_subscribe() 来订阅该事件。

      elems 参数是该事件的队列大小。若为 0,V4L2 框架将会(根据事件类型)
      填充默认值。

      ops 参数允许驱动指定一系列回调:
      * add:     当添加一个新监听者时调用(重复订阅同一个事件,此回调
                 仅被执行一次)。
      * del:     当一个监听者停止监听时调用。
      * replace: 用‘新’事件替换‘早期‘事件。
      * merge:   将‘早期‘事件合并到‘新’事件中。
      这四个调用都是可选的,如果不想指定任何回调,则 ops 可为 NULL。

    int v4l2_event_unsubscribe(struct v4l2_fh *fh,
                   struct v4l2_event_subscription *sub)

      v4l2_ioctl_ops 结构体中的 vidioc_unsubscribe_event 回调函数。
      驱动程序可以直接使用 v4l2_event_unsubscribe() 实现退订事件过程。

      特殊的 V4L2_EVENT_ALL 类型,可用于退订所有事件。驱动可能在特殊
      情况下需要做此操作。

    int v4l2_event_pending(struct v4l2_fh *fh)

      返回未决事件的数量。有助于实现轮询(poll)操作。

    事件通过 poll 系统调用传递到用户空间。驱动可用
    v4l2_fh->wait (wait_queue_head_t 类型)作为参数调用 poll_wait()。

    事件分为标准事件和私有事件。新的标准事件必须使用可用的最小事件类型
    编号。驱动必须从他们本类型的编号起始处分配事件。类型的编号起始为
    V4L2_EVENT_PRIVATE_START + n * 1000 ,其中 n 为可用最小编号。每个
    类型中的第一个事件类型编号是为以后的使用保留的,所以第一个可用事件
    类型编号是‘class base + 1’。

    V4L2 事件机制的使用实例可以在 OMAP3 ISP 的驱动
    (drivers/media/video/omap3isp)中找到。
     

    展开全文
  • 深入学习Linux摄像头(二)v4l2驱动框架

    千次阅读 多人点赞 2019-08-16 16:32:54
    深入学习Linux摄像头(二)v4l2驱动框架 深入学习Linux摄像头(三)虚拟摄像头驱动分析 深入学习Linux摄像头(五)三星平台fimc驱动详解一 深入学习Linux摄像头(六)三星平台fimc驱动详解二 深入学习Linux摄像头...

    深入学习Linux摄像头系列

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

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

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

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

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

    一、V4L2 框架

    1.1 相关对象

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

    • video_device

      一个字符设备,为用户空间提供设备节点(/dev/videox),提供系统调用的相关操作(open、ioctl…)

    • v4l2_device

      嵌入到video_device中,表示一个v4l2设备的实例

    • v4l2_subdev

      依附在v4l2_device之下,并表示一个v4l2设备的子设备,一个v4l2_devide下可以有多个sub_device

    • videobuf

      v4l2驱动的缓存管理

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

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

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

    • CMOS摄像头

      对于一款CMOS摄像头来说,有两个接口,一个是摄像头接口,一个是I2C接口

      摄像头接口负责传输图像数据,I2C接口负责传输控制信息,所以又可以将CMOS摄像头看作是一个I2C模块

      如下图所示

      在这里插入图片描述

    • 芯片片上资源

      在一款芯片上面,摄像头相关的有摄像头控制器摄像头接口I2C总线

      SOC上可以有多个摄像头控制器,多个摄像头接口,多个I2C总线

      摄像头控制器负责接收和处理摄像头数据,摄像头接口负责传输图像数据,I2C总线负责传输控制信息

      如下图所示

      在这里插入图片描述

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

    在这里插入图片描述

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

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

    我们回到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驱动框架前,我们先回顾一下简单的字符设备的编写

    • 分配一个字符设备(cdev)
    • 设置一个fops
    • 注册字符设备

    复杂的字符设备

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

    核心层会帮你完成字符设备的分配,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主要对象的数据结构

    • video_device

      struct video_device
      {
          /* character device */
      	struct cdev *cdev;
          
          /* v4l2_device parent */
      	struct v4l2_device *v4l2_dev;
      
         	/* device ops */
      	const struct v4l2_file_operations *fops;
          
      	/* ioctl callbacks */
      	const struct v4l2_ioctl_ops *ioctl_ops;
      };
      

      可以看到video_device中含有一个cdev还有v4l2_device,此外还有fops和ioctl_ops,从应用层进行系统调用会经过v4l2的核心层回调到这里

      其中v4l2_file_operationsv4l2_ioctl_ops如下

      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 *);
      	unsigned int (*poll) (struct file *, struct poll_table_struct *);
      	long (*ioctl) (struct file *, unsigned int, unsigned long);
      	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
      	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 *);
      };
      

      熟悉v4l2应用编程的应该都知道v4l2有很多ioctl操作,具体实现都在这里

      struct v4l2_ioctl_ops {
      	int (*vidioc_querycap)(struct file *file, void *fh, struct v4l2_capability *cap);
      	/* 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);
         	...
      };
      
    • v4l2_device

      struct v4l2_device {
      	/* used to keep track of the registered subdevs */
      	struct list_head subdevs;
          ...
      };
      

      可以看到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;
      };
      

      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;
      };
      
      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);
      	...
      };
      
      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);
      	...
      };
      

    2.2 V4L2提供的注册接口

    • video_device

      注册

      int video_register_device(struct video_device *vdev, int type, int nr);
      

      注销

      void video_unregister_device(struct video_device *vdev);
      
    • v4l2_device

      注册

      int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev);
      

      注销

      void v4l2_device_unregister(struct v4l2_device *v4l2_dev);
      
    • v4l2_subdev

      注册

      int v4l2_device_register_subdev(struct v4l2_device *v4l2_dev,
      						struct v4l2_subdev *sd);
      

      注销

      void v4l2_device_unregister_subdev(struct v4l2_subdev *sd);
      

    三、源码剖析

    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);
    

    如果你熟悉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);
        ...
    }
    

    从源码中可以看出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;
    }
    

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

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

    static struct video_device *video_device[VIDEO_NUM_DEVICES];
    

    保存着注册的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,
    };
    

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

    • v4l2_open

      static int v4l2_open(struct inode *inode, struct file *filp)
      {
          struct video_device *vdev;
          
          /* 根据次设备获得video_device */
          vdev = video_devdata(filp);
          
          /* 回调video_device的fops */
      	if (vdev->fops->open)
      		ret = vdev->fops->open(filp); //回调video
      }
      

      从这个函数可以看到,发生系统调用首先来到v4l2核心层的字符设备,然后再回调到对应的video_device,video_device在前面已经实现了v4l2_file_operationsv4l2_ioctl_ops一系列回调

    • v4l2_ioctl

      V4L2的应用编程会有非常多的ioctl,会先调用到此处

      static int v4l2_ioctl(struct inode *inode, struct file *filp,
      		unsigned int cmd, unsigned long arg)
      {
      	struct video_device *vdev = video_devdata(filp);
      
          /* 回调到video_device中 */
      	return vdev->fops->ioctl(filp, cmd, arg);
      }
      

      下面来看一看video_device怎么实现ioctl

      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,
      };
      

      从上面驱动程序的编写,我们可以知道video_device对应ioctl就是video_ioctl2,这个函数是内核提供的,我们看一看这个函数的内容

      long video_ioctl2(struct file *file,
      	       unsigned int cmd, unsigned long arg)
      {
          __video_do_ioctl(file, cmd, parg);
      }
      
      static long __video_do_ioctl(struct file *file,
      		unsigned int cmd, void *arg)
      {
          /* 获取video_device */
          struct video_device *vfd = video_devdata(file);
          
          /* 获取video_device的ioctl_ops */
          const struct v4l2_ioctl_ops *ops = vfd->ioctl_ops;
      
          switch (cmd) {
      	case VIDIOC_QUERYCAP:
              ops->vidioc_querycap(file, fh, cap);
          case VIDIOC_ENUM_FMT:
               ops->vidioc_enum_fmt_vid_cap(file, fh, f);
          ...
          }
      }
      

      可以看出,最终会调用到video_device实现的v4l2_ioctl_ops

      /* 实现各种系统调用 */
      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,
      };
      

      所以系统调用最先都会调用到字符设备的fops,然后经过v4l2核心层最终调用到video_device这里

      关于v4l2的驱动框架就分析到这里,关于更加详细的实现v4l2驱动,将在后续文章中通过实例讲解

    展开全文
  • 平台V4L2设备驱动的工作:根据平台自身的特性实现与平台相关的V4L2驱动部分,最主要的是包括设置并注册video_device和v4l2_device。 来看看v4l2_device结构体,定义如下: struct v4l2_device { struct device *...
  • 参考: 1. YellowMax2001 ...2. linux Kernel source code: 4.4 3. Documentation/zh_CN/video4linux/v4l2-framework.txt 4. 杨柳 《Andorid 驱动开发权威指南》v4l2框架简介​ ...
  • v4l2驱动框架分析-1

    千次阅读 2018-11-07 08:00:17
    (1) cimutils应用程序维护了哪些结构体,v4l2驱动框架维护了哪些结构体 (2)/dev/video0 这个节点怎么创建的 (3)应用层open 设备节点/dev/video0 的时候,内核中的调用关系和具体干的工作 (4)应用层ioctl 操作后,...
  • v4l2驱动框架简介

    2020-11-05 17:16:22
    v4l2全称是video for linux version2,是linux针对视频的一套驱动框架,包括视频输入(如摄像头),视频输出(如VGA输出等),视频编解码;本章节主要针对视频输入设备(如摄像头); 摄像头数据流 摄像头数据从...
  • 学习目标:学习V4L2V4L2:vidio for linux version 2)摄像头驱动框架,分析vivi.c(虚拟视频硬件相关)驱动源码程序,总结V4L2硬件相关的驱动的步骤; 一、V4L2架构 1. 字符类驱动 V4L2V4L2:vidio for linux ...
  • S5PV210 FIMC驱动和v4l2驱动框架学习

    千次阅读 2016-07-16 17:14:36
    最近没什么事,所以想看看FIMC驱动和v4l2驱动框架,下面做一下笔记,现在学的不是很透,有错误的地方希望大家批评指正,相互学习,下面先推荐几篇博文,个人觉得挺好的 1、刘老师的《FS_S5PC100平台上Linux ...
  • V4L2架构设计之初是只针对视频设备的,那时的V4L2被限制只能在structvideo_device结构体里面创建,并且用video_buf控制视频缓存。但随着硬件的变化也越来越复杂,现在大部分设备里面包含了多个子设备IC,比较常见的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 607
精华内容 242
关键字:

v4l2驱动框架