Fork me on GitHub

V4L2 driver -整体架构

我的uvc开源地址:gitee-uvc

  • 字符设备驱动程序核心:V4L2本身就是一个字符设备,具有字符设备所有的特性,暴露接口给用户空间。
  • V4L2 驱动核心:主要是构建一个内核中标准视频设备驱动的框架,为视频操作提供统一的接口函数。
  • 平台V4L2设备驱动:在V4L2框架下,根据平台自身的特性实现与平台相关的V4L2驱动部分,包括注册video_device和v4l2_dev。
  • 具体的sensor驱动:主要上电、提供工作时钟、视频图像裁剪、流IO开启等,实现各种设备控制方法供上层调用并注册v4l2_subdev。

1 从字符设备开始:

熟悉v4l2用户空间编程的都知道, v4l2编程主要是调用一系列的ioctl函数去对v4l2设备进行打开, 关闭, 查询, 设置等操作. v4l2设备是一个字符设备, 而且其驱动的主要工作就是实现各种各样的ioctl.

v4l2的整体框架如下图所示:

V4L2 :video for linux version 2 ,是 linux 里一套标准的视频驱动。本文来分析一下它的核心框架。

在v4l2的核心中对这个file_operations的实现如下:

static const struct file_operations v4l2_fops = {
    .owner = THIS_MODULE,
    .read = v4l2_read,
    .write = v4l2_write,
    .open = v4l2_open,
    .get_unmapped_area = v4l2_get_unmapped_area,
    .mmap = v4l2_mmap,
    .unlocked_ioctl = v4l2_ioctl,
#ifdef CONFIG_COMPAT
    .compat_ioctl = v4l2_compat_ioctl32,
#endif
    .release = v4l2_release,
    .poll = v4l2_poll,
    .llseek = no_llseek,
};

这个v4l2_fops函数最终绑定在一个cdev上, 并注册到系统中。

v4l2_open为例(代码在kernel\drivers\media\v4l2-core中):

/* Override for the open function */
static int v4l2_open(struct inode *inode, struct file *filp)
{
	struct video_device *vdev;
	int ret = 0;

	/* Check if the video device is available */
	mutex_lock(&videodev_lock);
	vdev = video_devdata(filp);
	/* return ENODEV if the video device has already been removed. */
	if (vdev == NULL || !video_is_registered(vdev)) {
		mutex_unlock(&videodev_lock);
		return -ENODEV;
	}
	/* and increase the device refcount */
	video_get(vdev);
	mutex_unlock(&videodev_lock);
	if (vdev->fops->open) {
		if (video_is_registered(vdev))
		    //这里就是调用了file_operations的open函数
			ret = vdev->fops->open(filp);
		else
			ret = -ENODEV;
	}

	if (vdev->debug)
		printk(KERN_DEBUG "%s: open (%d)\n",
			video_device_node_name(vdev), ret);
	/* decrease the refcount in case of an error */
	if (ret)
		video_put(vdev);
	return ret;
}

2. video_device结构体:

video_device结构体用于在/dev目录下生成设备节点文件,把操作设备的接口暴露给用户空间。

我们是使用video_device来操作的,看看video_device这个结构体:

struct video_device
{
#if defined(CONFIG_MEDIA_CONTROLLER)
	struct media_entity entity;
#endif
	/* 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;

	/* 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;

	/* device info */
	char name[32];
	int vfl_type;	/* device type */
	int vfl_dir;	/* receiver, transmitter or m2m */
	/* '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;
	DECLARE_BITMAP(valid_ioctls, BASE_VIDIOC_PRIVATE);

	/* serialization lock */
	DECLARE_BITMAP(disable_locking, BASE_VIDIOC_PRIVATE);
	struct mutex *lock;
};

3. V4L2_device结构体:

这个结构体中包含了一个非常重要的结构体v4l2_device

struct v4l2_device {
	/* 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 device *dev;
#if defined(CONFIG_MEDIA_CONTROLLER)
	struct media_device *mdev;
#endif
	/* used to keep track of the registered subdevs */
	struct list_head subdevs;
	/* lock this struct; can be used by the driver as well if this
	   struct is embedded into a larger struct. */
	spinlock_t lock;
	/* unique device name, by default the driver name + bus ID */
	char name[V4L2_DEVICE_NAME_SIZE];
	/* notify callback called by some sub-devices. */
	void (*notify)(struct v4l2_subdev *sd,
			unsigned int notification, void *arg);
	/* The control handler. May be NULL. */
	struct v4l2_ctrl_handler *ctrl_handler;
	/* Device's priority state */
	struct v4l2_prio_state prio;
	/* BKL replacement mutex. Temporary solution only. */
	struct mutex ioctl_lock;
	/* Keep track of the references to this struct. */
	struct kref ref;
	/* Release function that is called when the ref count goes to 0. */
	void (*release)(struct v4l2_device *v4l2_dev);
};

3.1 v4l2_device的注册和注销:

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

static void v4l2_device_release(struct kref *ref)

4. v4l2_subdev结构体

V4l2_subdev代表子设备,包含了子设备的相关属性和操作。先来看下结构体原型:

struct v4l2_subdev {
 
         struct v4l2_device *v4l2_dev;  //指向父设备
 
         //提供一些控制v4l2设备的接口
 
         const struct v4l2_subdev_ops *ops;
 
         //向V4L2框架提供的接口函数
 
         const struct v4l2_subdev_internal_ops *internal_ops;
 
         //subdev控制接口
 
         struct v4l2_ctrl_handler *ctrl_handler;
 
         /* name must be unique */
 
         charname[V4L2_SUBDEV_NAME_SIZE];
 
         /*subdev device node */
 
         struct video_device *devnode;  
 
};

其中 list 域作为链表节点链接至 v4l2_dev 指向的 v4l2_device 结构中,这个结构中最重要的成员就是 struct v4l2_subdev_ops *ops,该域包含了 v4l2 设备支持的所有操作,定义如下:

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;  /* 视频操作合集 */
};

v4l2_subdev_core_ops 包含的操作合集是各种类型设备通用的:

struct v4l2_subdev_core_ops {
	int (*g_chip_ident)(struct v4l2_subdev *sd, struct v4l2_dbg_chip_ident *chip);  /* 获取设备id */
	int (*log_status)(struct v4l2_subdev *sd);                                      /* 状态消息 */
	int (*s_config)(struct v4l2_subdev *sd, int irq, void *platform_data);          /* 设置配置信息 */
	int (*init)(struct v4l2_subdev *sd, u32 val);                                   /* 初始化设备 */
	int (*load_fw)(struct v4l2_subdev *sd);                                         /* 加载firmware */
	int (*reset)(struct v4l2_subdev *sd, u32 val);                                  /* 重置设备 */
	int (*s_gpio)(struct v4l2_subdev *sd, u32 val);                                 /* 设置gpio */
	int (*queryctrl)(struct v4l2_subdev *sd, struct v4l2_queryctrl *qc);            /* 查询设备支持的操作 */
	int (*g_ctrl)(struct v4l2_subdev *sd, struct v4l2_control *ctrl);               /* 获取当前命令值 */
	int (*s_ctrl)(struct v4l2_subdev *sd, struct v4l2_control *ctrl);               /* 设置当前命令值 */
	int (*g_ext_ctrls)(struct v4l2_subdev *sd, struct v4l2_ext_controls *ctrls);    /* 获取外置命令值 */
	int (*s_ext_ctrls)(struct v4l2_subdev *sd, struct v4l2_ext_controls *ctrls);    /* 设置外置命令值 */
	int (*try_ext_ctrls)(struct v4l2_subdev *sd, struct v4l2_ext_controls *ctrls);
	int (*querymenu)(struct v4l2_subdev *sd, struct v4l2_querymenu *qm);            /* 查询操作菜单 */
	int (*s_std)(struct v4l2_subdev *sd, v4l2_std_id norm);                         /* 设置数据标准 */
	long (*ioctl)(struct v4l2_subdev *sd, unsigned int cmd, void *arg);             /* 处理特殊命令 */
#ifdef CONFIG_VIDEO_ADV_DEBUG
	int (*g_register)(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg);       /* 获取寄存器值 */
	int (*s_register)(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg);       /* 设置寄存器值 */
#endif
};

v4l2_subdev_tuner_ops 包含的操作合集则是调谐器独有的:

struct v4l2_subdev_tuner_ops {
	int (*s_mode)(struct v4l2_subdev *sd, enum v4l2_tuner_type);               /* 设置调谐器模式 */
	int (*s_radio)(struct v4l2_subdev *sd);                                    /* 设置无线设备信息 */
	int (*s_frequency)(struct v4l2_subdev *sd, struct v4l2_frequency *freq);   /* 设置频率 */
	int (*g_frequency)(struct v4l2_subdev *sd, struct v4l2_frequency *freq);   /* 获取频率 */
	int (*g_tuner)(struct v4l2_subdev *sd, struct v4l2_tuner *vt);             /* 获取调谐器信息 */
	int (*s_tuner)(struct v4l2_subdev *sd, struct v4l2_tuner *vt);             /* 设置调谐器信息 */
	int (*g_modulator)(struct v4l2_subdev *sd, struct v4l2_modulator *vm);     /* 获取调幅器信息 */
	int (*s_modulator)(struct v4l2_subdev *sd, struct v4l2_modulator *vm);     /* 设置调幅器信息 */
	int (*s_type_addr)(struct v4l2_subdev *sd, struct tuner_setup *type);      /* 安装调谐器 */
	int (*s_config)(struct v4l2_subdev *sd, const struct v4l2_priv_tun_config *config);   /* 设置配置信息 */
	int (*s_standby)(struct v4l2_subdev *sd);                                  /* 设置标准 */
};

v4l2_subdev_audio_ops 包含的操作合集则是音频部分独有的:

struct v4l2_subdev_audio_ops {
	int (*s_clock_freq)(struct v4l2_subdev *sd, u32 freq);       /* 设置音频设备频率 */
	int (*s_i2s_clock_freq)(struct v4l2_subdev *sd, u32 freq);   /* 设置i2s总线频率 */
	int (*s_routing)(struct v4l2_subdev *sd, u32 input, u32 output, u32 config);   /* 设置音频路由 */
};

v4l2_subdev_video_ops 包含的操作合集则是视频部分独有的:

struct v4l2_subdev_video_ops {
	int (*s_routing)(struct v4l2_subdev *sd, u32 input, u32 output, u32 config);             /* 设置视频路由 */
	int (*s_crystal_freq)(struct v4l2_subdev *sd, u32 freq, u32 flags);                      /* 设置设备频率 */
	int (*decode_vbi_line)(struct v4l2_subdev *sd, struct v4l2_decode_vbi_line *vbi_line);   /* 消隐区信息解码 */
	int (*s_vbi_data)(struct v4l2_subdev *sd, const struct v4l2_sliced_vbi_data *vbi_data);  /* 设置消隐区数据 */
	int (*g_vbi_data)(struct v4l2_subdev *sd, struct v4l2_sliced_vbi_data *vbi_data);        /* 获取消隐区数据 */
	int (*g_sliced_vbi_cap)(struct v4l2_subdev *sd, struct v4l2_sliced_vbi_cap *cap);
	int (*s_std_output)(struct v4l2_subdev *sd, v4l2_std_id std);                            /* 设置标准输出 */
	int (*querystd)(struct v4l2_subdev *sd, v4l2_std_id *std);                               /* 查询标准 */
	int (*g_input_status)(struct v4l2_subdev *sd, u32 *status);                              /* 获取输入状态 */
	int (*s_stream)(struct v4l2_subdev *sd, int enable);                                     /* 设置数据流 */
	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);                    /* 设置参数 */
	int (*enum_framesizes)(struct v4l2_subdev *sd, struct v4l2_frmsizeenum *fsize);          /* 枚举帧大小 */
	int (*enum_frameintervals)(struct v4l2_subdev *sd, struct v4l2_frmivalenum *fival);      /* 枚举帧间隔 */
};

4.1 subdev的注册和注销

当我们把v4l2_subdev需要实现的成员都已经实现,就可以调用以下函数把子设备注册到V4L2核心层:

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

当卸载子设备时,可以调用以下函数进行注销:

void v4l2_device_unregister_subdev(struct v4l2_subdev*sd)

5. 应用层具体流程框架:

我们使用一张图来体现吧:

posted @ 2018-08-16 12:06  yooooooo  阅读(6409)  评论(2编辑  收藏  举报