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. 應用層具體流程框架:

我們使用一張圖來體現吧:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM