Camera V4L2框架的驱动层分析

发布于:2022-12-16 ⋅ 阅读:(220) ⋅ 点赞:(0)

1.V4L2 框架

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驱动中,使用v4l2_device来表示摄像头控制器

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

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

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

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

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

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

硬件相关层需要分配一个video_device并设置它,然后向核心层注册,核心层会为其注册字符设备

并且创建设备节点(/dev/videox)。同时硬件相关层还需要分配和设置相应的v4l2_device和

v4l2_subdev,其中v4l2_device的一个比较重要的意义就是管理v4l2_subdev,当然有一些驱动并

不需要实现v4l2_subdev,此时v4l2_device的意义就不是很大了。

当应用层通过/dev/video来操作设备的时候,首先会来到V4L2的核心层,核心层通过注册进的

video_device的回调函数调用相应的操作函数,video_device可以直接操作硬件或者是通过

v4l2_subdev来操作硬件。

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.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操作。

在上面的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,应用层发生系统调用会率先调用到这里,我们来好好看一看这些调用。

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这里。

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到