Linux V4L2子系统分析(一)_video for linux two api

// v4l2\_dev-v4l2\_device结构体指针
void v4l2\_device\_disconnect(struct v4l2_device \*v4l2_dev);

同一个硬件的情况下。如`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),则你可以通过以下函数手动增减引用计数:



[include/media/v4l2-device.h]
// 增加引用计数
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。


##### 2.2.V4L2从设备数据结构


V4L2从设备使用`struct v4l2_subdev`结构体表示。一个V4L2主设备可能对应多个V4L2从设备,所有主设备对应的从设备都挂到`v4l2_device`结构体的`subdevs`链表中。对于视频设备,从设备就是摄像头,通常情况下是I2C设备,主设备可通过I2C总线控制从设备,例如控制摄像头的焦距、闪光灯等。`struct v4l2_subdev`结构体重要成员示意图如下图所示。`struct v4l2_subdev_video_ops`将在《Linux V4L2子系统-Video设备框架分析(二)》中介绍。


![v4l2_subdev结构体示意](https://img-blog.csdnimg.cn/20210409220123324.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTEwMzc1OTM=,size_16,color_FFFFFF,t_70#pic_center)



[include/media/v4l2-subdev.h]
#define V4L2\_SUBDEV\_FL\_IS\_I2C (1U << 0) // 从设备是I2C设备
#define V4L2\_SUBDEV\_FL\_IS\_SPI (1U << 1) // 从设备是SPI设备
#define V4L2\_SUBDEV\_FL\_HAS\_DEVNODE (1U << 2) // 从设备需要设备节点
#define V4L2\_SUBDEV\_FL\_HAS\_EVENTS (1U << 3) // 从设备会产生事件

struct v4l2_subdev {
#if defined(CONFIG\_MEDIA\_CONTROLLER) // 多媒体配置选项
    struct media_entity entity;
#endif
    struct list_head list;  // 子设备串联链表
    struct module \*owner;  // 属于那个模块,一般指向i2c\_lient驱动模块
    bool owner_v4l2_dev;
    // 标志位,确定该设备属于那种设备,由V4L2\_SUBDEV\_FL\_IS\_XX宏确定
    u32 flags;
    // 指向主设备的v4l2\_device结构体
    struct v4l2_device \*v4l2_dev;
    // v4l2子设备的操作函数集合
    const struct v4l2_subdev_ops \*ops;
    // 提供给v4l2框架的操作函数,只有v4l2框架会调用,驱动不使用
    const struct v4l2_subdev_internal_ops \*internal_ops;
    // 从设备的控制接口
    struct v4l2_ctrl_handler \*ctrl_handler;
    // 从设备的名称,必须独一无二
    char name[V4L2_SUBDEV_NAME_SIZE];
    // 从设备组的ID,由驱动定义,相似的从设备可以编为一组,
    u32 grp_id;
    // 从设备私有数据指针,一般指向i2c\_client的设备结构体dev
    void \*dev_priv;
    // 主设备私有数据指针,一般指向v4l2\_device嵌入的结构体
    void \*host_priv;
    // 指向video设备结构体
    struct video_device \*devnode;
    // 指向物理设备
    struct device \*dev;
    // 将所有从设备连接到全局subdev\_list链表或notifier->done链表
    struct list_head async_list;
    // 指向struct v4l2\_async\_subdev,用于异步事件
    struct v4l2_async_subdev \*asd;
    // 指向管理的notifier,用于主设备和从设备的异步关联
    struct v4l2_async_notifier \*notifier;
    /\* common part of subdevice platform data \*/
    struct v4l2_subdev_platform_data \*pdata;
};
// 提供给v4l2框架的操作函数,只有v4l2框架会调用,驱动不使用
struct v4l2_subdev_internal_ops {
    // v4l2\_subdev注册时回调此函数,使v4l2\_dev指向主设备的v4l2\_device结构体
    int (\*registered)(struct v4l2_subdev \*sd);
    // v4l2\_subdev卸载时回调此函数
    void (\*unregistered)(struct v4l2_subdev \*sd);
    // 应用调用open打开从设备节点时调用此函数
    int (\*open)(struct v4l2_subdev \*sd, struct v4l2_subdev_fh \*fh);
    // 应用调用close时调用此函数
    int (\*close)(struct v4l2_subdev \*sd, struct v4l2_subdev_fh \*fh);
};

使用`v4l2_subdev_init`初始化`v4l2_subdev`结构体。然后必须用一个唯一的名字初始化`subdev->name`,同时初始化模块的`owner`域。若从设备是I2C设备,则可使用`v4l2_i2c_subdev_init`函数进行初始化,该函数内部会调用`v4l2_subdev_init`,同时设置`flags`、`owner`、`dev`、`name`等成员。



[include/media/v4l2-subdev.h]
// 初始化v4l2\_subdev结构体
// ops-v4l2子设备的操作函数集合指针,保存到v4l2\_subdev结构体的ops成员中
void v4l2\_subdev\_init(struct v4l2_subdev \*sd,
	    const struct v4l2_subdev_ops \*ops);

[include/media/v4l2-common.h]
// 初始化V4L2从设备为I2C设备的v4l2\_subdev结构体
// sd-v4l2\_subdev结构体指针
// client-i2c\_client结构体指针
// ops-v4l2子设备的操作函数集合指针,保存到v4l2\_subdev结构体的ops成员中
void v4l2\_i2c\_subdev\_init(struct v4l2_subdev \*sd, 
    struct i2c_client \*client,
	const struct v4l2_subdev_ops \*ops);

若需同媒体框架整合,必须调用`media_entity_init`初始化`v4l2_subdev`结构体中的`media_entity`结构体(entity域)。`pads`数组必须预先初始化。无须手动设置`media_entity`的`type`和`name`域,但如有必要,`revision`域必须初始化。当(任何)从设备节点被打开/关闭,对`entity`的引用将被自动获取/释放。在从设备被注销之后,使用`media_entity_cleanup`清理`media_entity`结构体。如果从设备驱动趋向于处理视频并整合进了媒体框架,必须使用`v4l2_subdev_pad_ops`替代`v4l2_subdev_video_ops`实现格式相关的功能。这种情况下,子设备驱动应该设置`link_validate`域,以提供它自身的链接验证函数。链接验证函数应对管道(两端链接的都是V4L2从设备)中的每个链接调用。驱动还要负责验证子设备和视频节点间格式配置的正确性。如果`link_validate`操作没有设置,默认的`v4l2_subdev_link_validate_default`函数将会被调用。这个函数保证宽、高和媒体总线像素格式在链接的收发两端都一致。子设备驱动除了它们自己的检测外,也可以自由使用这个函数以执行上面提到的检查。



[include/media/media-entity.h]
// 初始化v4l2\_subdev结构体中的media\_entity结构体
// entity-要初始化的media\_entity结构体指针
// num\_pads-源pad的数量,与驱动子设备结构相关
// pads-media\_pad结构体数组,通常pad被嵌入到驱动自定义的结构体里面,
// 数组地址被传递给该参数,pad需提前初始化
// extra\_links-函数会根据num\_pads分配media\_link数目,该参数则指明除了预分
               配的数量之外还需要多少额外的media_link数目
// 返回值 0-成功,小于0-失败
int media\_entity\_init(struct media_entity \*entity, u16 num_pads,
        struct media_pad \*pads, u16 extra_links)

// 清理media\_entity结构体
// entity-media\_entity结构体指针
void media\_entity\_cleanup(struct media_entity \*entity)

// v4l2-subdev pad层级的操作函数
[include/media/v4l2-subdev.h]
struct v4l2_subdev_pad_ops {
	// ioctl VIDIOC\_SUBDEV\_ENUM\_MBUS\_CODE命令处理函数
	int (\*enum_mbus_code)(struct v4l2_subdev \*sd,
			      struct v4l2_subdev_pad_config \*cfg,
			      struct v4l2_subdev_mbus_code_enum \*code);
	// ioctl VIDIOC\_SUBDEV\_ENUM\_FRAME\_SIZE命令处理函数
	int (\*enum_frame_size)(struct v4l2_subdev \*sd,
			       struct v4l2_subdev_pad_config \*cfg,
			       struct v4l2_subdev_frame_size_enum \*fse);
	// ioctl VIDIOC\_SUBDEV\_ENUM\_FRAME\_INTERVAL命令处理函数
	int (\*enum_frame_interval)(struct v4l2_subdev \*sd,
				   struct v4l2_subdev_pad_config \*cfg,
				   struct v4l2_subdev_frame_interval_enum \*fie);
	// ioctl VIDIOC\_SUBDEV\_G\_FMT命令处理函数
	int (\*get_fmt)(struct v4l2_subdev \*sd,
		       struct v4l2_subdev_pad_config \*cfg,
		       struct v4l2_subdev_format \*format);
	// ioctl VIDIOC\_SUBDEV\_S\_FMT命令处理函数
	int (\*set_fmt)(struct v4l2_subdev \*sd,
		       struct v4l2_subdev_pad_config \*cfg,
		       struct v4l2_subdev_format \*format);
	// ioctl VIDIOC\_SUBDEV\_G\_SELECTION命令处理函数
	int (\*get_selection)(struct v4l2_subdev \*sd,
			     struct v4l2_subdev_pad_config \*cfg,
			     struct v4l2_subdev_selection \*sel);
	// ioctl VIDIOC\_SUBDEV\_S\_SELECTION命令处理函数
	int (\*set_selection)(struct v4l2_subdev \*sd,
			     struct v4l2_subdev_pad_config \*cfg,
			     struct v4l2_subdev_selection \*sel);
	// ioctl VIDIOC\_SUBDEV\_G\_EDID命令处理函数
	int (\*get_edid)(struct v4l2_subdev \*sd, struct v4l2_edid \*edid);
	// ioctl VIDIOC\_SUBDEV\_S\_EDID命令处理函数
	int (\*set_edid)(struct v4l2_subdev \*sd, struct v4l2_edid \*edid);
	// ioctl VIDIOC\_SUBDEV\_DV\_TIMINGS\_CAP命令处理函数
	int (\*dv_timings_cap)(struct v4l2_subdev \*sd, struct v4l2_dv_timings_cap \*cap);
	// ioctl VIDIOC\_SUBDEV\_ENUM\_DV\_TIMINGS命令处理函数
	int (\*enum_dv_timings)(struct v4l2_subdev \*sd, struct v4l2_enum_dv_timings \*timings);
#ifdef CONFIG\_MEDIA\_CONTROLLER
	// 用于多媒体控制器检查属于管道的链接是否可以用于流
	int (\*link_validate)(struct v4l2_subdev \*sd, struct media_link \*link,
			     struct v4l2_subdev_format \*source_fmt,
			     struct v4l2_subdev_format \*sink_fmt);
#endif /\* CONFIG\_MEDIA\_CONTROLLER \*/
	// 获取当前低级媒体总线帧参数
	int (\*get_frame_desc)(struct v4l2_subdev \*sd, unsigned int pad,
			      struct v4l2_mbus_frame_desc \*fd);	      
	// 设置低级媒体总线帧参数
	int (\*set_frame_desc)(struct v4l2_subdev \*sd, unsigned int pad,
			      struct v4l2_mbus_frame_desc \*fd);
};

从设备必须向V4L2子系统注册`v4l2_subdev`结构体,使用`v4l2_device_register_subdev`注册,使用`v4l2_device_unregister_subdev`注销。



[include/media/v4l2-device.h]
// 向V4L2子系统注册v4l2\_subdev结构体
// v4l2\_dev-主设备v4l2\_device结构体指针
// sd-从设备v4l2\_subdev结构体指针
// 返回值 0-成功,小于0-失败
int v4l2\_device\_register\_subdev(struct v4l2_device \*v4l2_dev,
                struct v4l2_subdev \*sd)

// 从V4L2子系统注销v4l2\_subdev结构体
// sd-从设备v4l2\_subdev结构体指针 
void v4l2\_device\_unregister\_subdev(struct v4l2_subdev \*sd);

V4L2从设备驱动都必须有一个`v4l2_subdev`结构体。这个结构体可以单独代表一个简单的从设备,也可以嵌入到一个更大的结构体中,与更多设备状态信息保存在一起。通常有一个下级设备结构体(比如:`i2c_client`)包含了内核创建的设备数据。建议使用`v4l2_set_subdevdata()`将这个结构体的指针保存在`v4l2_subdev`的私有数据域(`dev_priv`)中。这使得通过`v4l2_subdev`找到实际的低层总线特定设备数据变得容易。同时也需要一个从低层结构体获取`v4l2_subdev`指针的方法。对于常用的`i2c_client`结构体,`i2c_set_clientdata`函数可用于保存一个`v4l2_subdev`指针,`i2c_get_clientdata`可以获取一个`v4l2_subdev`指针;对于其他总线可能需要使用其他相关函数。



[include/media/v4l2-subdev.h]
// 将i2c\_client的指针保存到v4l2\_subdev结构体的dev\_priv成员中
static inline void v4l2\_set\_subdevdata(struct v4l2_subdev \*sd, void \*p)
{
    sd->dev_priv = p;
}

[include/linux/i2c.h]
// 可以将v4l2\_subdev结构体指针保存到i2c\_client中dev成员的driver\_data中
static inline void i2c\_set\_clientdata(struct i2c_client \*dev, void \*data)
{
    dev\_set\_drvdata(&dev->dev, data);
}
// 获取i2c\_client结构体中dev成员的driver\_data,一般指向v4l2\_subdev
static inline void \*i2c\_get\_clientdata(const struct i2c_client \*dev)
{
    return dev\_get\_drvdata(&dev->dev);
}

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



[include/media/v4l2-subdev.h]
static inline void \*v4l2\_get\_subdev\_hostdata(const struct v4l2_subdev \*sd)
{
    return sd->host_priv;
}
static inline void v4l2\_set\_subdev\_hostdata(struct v4l2_subdev \*sd, void \*p)
{
    sd->host_priv = p;
}

每个`v4l2_subdev`都包含子设备驱动需要实现的函数指针(如果对此设备不适用,可为NULL),具体在`v4l2_subdev_ops`结构体当中。由于子设备可完成许多不同的工作,而在一个庞大的函数指针结构体中通常仅有少数有用的函数实现其功能肯定不合适。所以,函数指针根据其实现的功能被分类,每一类都有自己的函数指针结构体,如`v4l2_subdev_core_ops`、`v4l2_subdev_audio_ops`、`v4l2_subdev_video_ops`等等。`v4l2_subdev_video_ops`在视频设备中(第二章中)详细介绍。顶层函数指针结构体包含了指向各类函数指针结构体的指针,如果子设备驱动不支持该类函数中的任何一个功能,则指向该类结构体的指针为NULL。



[include/media/v4l2-subdev.h]
/\* v4l2从设备的操作函数集合,从设备根据自身设备类型选择实现,

其中core函数集通常可用于所有子设备,其他类别的实现依赖于
子设备。如视频设备可能不支持音频操作函数,反之亦然。这样的
设置在限制了函数指针数量的同时,还使增加新的操作函数和分类
变得较为容易。 */
struct v4l2_subdev_ops {
// 从设备的通用操作函数集合,进行初始化、reset、控制等操作
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; // VBI设备
const struct v4l2_subdev_ir_ops *ir;
const struct v4l2_subdev_sensor_ops *sensor;
const struct v4l2_subdev_pad_ops *pad;
};
// 适用于所有v4l2从设备的操作函数集合
struct v4l2_subdev_core_ops {
// IO引脚复用配置
int (*s_io_pin_config)(struct v4l2_subdev *sd, size_t n,
struct v4l2_subdev_io_pin_config *pincfg);
// 初始化从设备的某些寄存器,使其恢复默认
int (*init)(struct v4l2_subdev *sd, u32 val);
// 加载固件
int (*load_fw)(struct v4l2_subdev *sd);
// 复位
int (*reset)(struct v4l2_subdev *sd, u32 val);
// 设置GPIO引脚输出值
int (*s_gpio)(struct v4l2_subdev *sd, u32 val);
// 设置从设备的电源状态,0-省电模式,1-正常操作模式
int (*s_power)(struct v4l2_subdev *sd, int on);
// 中断函数,被主设备的中断函数调用
int (*interrupt_service_routine)(struct v4l2_subdev *sd,
u32 status, bool *handled);

};


使用`v4l2_device_register_subdev`注册从设备后,就可以调用`v4l2_subdev_ops`中的方法了。可以通过`v4l2_subdev`直接调用,也可以使用内核提供的宏定义`v4l2_subdev_call`间接调用某一个方法。若要调用多个从设备的同一个方法,则可使用`v4l2_device_call_all`宏定义。



// 直接调用
err = sd->ops->video->g\_std(sd, &norm);

// 使用宏定义调用,这个宏将会做NULL指针检查,如果su为NULL,则返回-ENODEV;
// 如果sd->ops->video或sd->ops->video->g\_std为NULL,则返回-ENOIOCTLCMD;
// 否则将返回sd->ops->video->g\_std的调用的实际结果
err = v4l2\_subdev\_call(sd, video, g_std, &norm);

[include/media/v4l2-subdev.h]
#define v4l2\_subdev\_call(sd, o, f, args...) \

(!(sd) ? -ENODEV : (((sd)->ops->o && (sd)->ops->o->f) ?
(sd)->ops->o->f((sd) , ##args) : -ENOIOCTLCMD))

v4l2\_device\_call\_all(v4l2_dev, 0, video, g_std, &norm);
[include/media/v4l2-device.h]
#define v4l2\_device\_call\_all(v4l2\_dev, grpid, o, f, args...) \

do {
struct v4l2_subdev *__sd;
__v4l2_device_call_subdevs_p(v4l2_dev, __sd,
!(grpid) || __sd->grp_id == (grpid), o, f ,
##args);
} while (0)


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



[include/media/v4l2-device.h]
// 从设备通知主设备,最终回调到v4l2\_device的notify函数
static inline void v4l2\_subdev\_notify(struct v4l2_subdev \*sd,
                    unsigned int notification, void \*arg)
{
    if (sd && sd->v4l2_dev && sd->v4l2_dev->notify)
        sd->v4l2_dev->notify(sd, notification, arg);
}

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


##### 2.3.V4L2主设备和从设备匹配过程分析


V4L2主设备和从设备采用异步的匹配方法。首先介绍一下异步匹配用到的方法。主设备使用`v4l2_async_notifier_register`函数进行异步匹配,匹配到从设备,则调用`v4l2_device_register_subdev`函数注册从设备,使用`v4l2_async_notifier_unregister`函数异步取消匹配。从设备使用`v4l2_async_register_subdev`函数异步匹配主设备,若匹配到主设备,则调用`v4l2_device_register_subdev`函数注册从设备,使用`v4l2_async_unregister_subdev`函数异步取消匹配。  
 匹配的方法由`v4l2_async_subdev`结构体决定,主设备可以有多个`v4l2_async_subdev`结构体,也说明主设备有多种匹配从设备的方法。`match_type`表示匹配方式,由枚举`v4l2_async_match_type`定义,具体有使用设备名称匹配-`V4L2_ASYNC_MATCH_DEVNAME`、使用I2C adapter ID and address进行匹配-`V4L2_ASYNC_MATCH_I2C`等。联合体`match`中包含了具体的匹配信息,根据匹配方式进行设置。`v4l2_async_notifier`管理整个匹配过程,未匹配的`v4l2_async_subdev`结构体被挂到`waiting`链表,匹配完成的挂到`done`链表同时调用`bound`函数进行绑定。



[include/media/v4l2-async.h]
// 主设备和从设备的匹配方式 
enum v4l2_async_match_type {
    // 传统的匹配方式,使用v4l2\_async\_subdev的match方法进行匹配
    V4L2_ASYNC_MATCH_CUSTOM,  
    // 使用设备名称进行匹配
    V4L2_ASYNC_MATCH_DEVNAME,
    // 使用I2C adapter ID and address进行匹配
    V4L2_ASYNC_MATCH_I2C,
    // 使用firmware node 进行匹配
    V4L2_ASYNC_MATCH_OF,
};
struct v4l2_async_subdev {
    // 匹配方式
    enum v4l2_async_match_type match_type;
    union {
        struct {
            // 设备树匹配方式
            const struct device_node \*node;
        } of;
        struct {
            // 设备名称匹配方式
            const char \*name;
        } device_name;
        struct {
            // 使用I2C adapter ID and address进行匹配
            int adapter_id;
            unsigned short address;
        } i2c;
        struct {
            // 传统的匹配方式
            bool (\*match)(struct device \*,
                    struct v4l2_async_subdev \*);
            void \*priv;
        } custom;
    } match;
    // v4l2-async核心层使用,将此结构体挂入到notifier的waiting链表,驱动不可使用
    struct list_head list;
};

// 主设备注册一个notifier,用于异步的和从设备进行匹配
int v4l2\_async\_notifier\_register(struct v4l2_device \*v4l2_dev,
                struct v4l2_async_notifier \*notifier);
// 主设备注销notifier
void v4l2\_async\_notifier\_unregister(struct v4l2_async_notifier \*notifier);

// 从设备异步注册v4l2\_subdev,用于异步的和主设备进行匹配
int v4l2\_async\_register\_subdev(struct v4l2_subdev \*sd);
// 从设备注销
void v4l2\_async\_unregister\_subdev(struct v4l2_subdev \*sd);

struct v4l2_async_notifier {
    unsigned int num_subdevs;  // subdevices的数量
    // 指针数组,指向subdevice descriptors
    struct v4l2_async_subdev \*\*subdevs;  
    // 指向struct v4l2\_device
    struct v4l2_device \*v4l2_dev;
    // v4l2\_async\_subdev的链表,等待匹配drivers
    struct list_head waiting;
    // 已经probed的v4l2\_subdev链表
    struct list_head done;
    // 挂在全局的notifiers链表上
    struct list_head list;
    // 驱动匹配到从设备后调用此函数
    int (\*bound)(struct v4l2_async_notifier \*notifier,
            struct v4l2_subdev \*subdev,
            struct v4l2_async_subdev \*asd);
    // 所有从设备被probed成功,调用此函数
    int (\*complete)(struct v4l2_async_notifier \*notifier);
    // 从设备注销时调用此函数
    void (\*unbind)(struct v4l2_async_notifier \*notifier,
            struct v4l2_subdev \*subdev,
            struct v4l2_async_subdev \*asd);
};

以imx6ull为例分析主设备和从设备的匹配过程。v4L2主设备和从设备的匹配过程可通过分析`v4l2_async_notifier_register`函数得到。可总结如下:  
 (1)首先初始化需要匹配的`v4l2_async_notifier`结构体,主要设备匹配方式、`bound`函数和指向`v4l2_async_subdev`结构体的指针。  
 (2)设置`v4l2_async_notifier`的`v4l2_dev`指针指向主设备的`v4l2_device`结构体。  
 (3)遍历`subdevs`指向的`v4l2_async_subdev`结构体,全部挂入`waiting`链表。  
 (4)将`v4l2_async_notifier`结构体挂入到全局的`notifier_list`链表。  
 (5)遍历`subdev_list`链表(所有的从设备都挂到`subdev_list`链表中),和`waiting`链表中的每一个`v4l2_async_subdev`结构体进行匹配。  
 (6)若匹配成功,则将`v4l2_async_subdev`从`waiting`链表中删除,设置从设备`V4l2_subdev`结构体的`notifier`和`asd`成员,使其指向`mx6s_csi_dev`结构体中的`v4l2_async_notifier`和`v4l2_async_subdev`结构体。  
 (7)**调用`bound`函数,也就是使主设备`mx6s_csi_dev`结构体中的`sd`成员指向了从设备的`v4l2_subdev`结构体,从而完成了主设备和从设备进行绑定。**  
 (8)将匹配成功的从设备`V4l2_subdev`结构体从全局的`subdev_list`链表中移除,同时添加到`v4l2_async_notifier`的`done`链表中。  
 (9)调用`v4l2_device_register_subdev`注册从设备。


![V4L2设备匹配过程](https://img-blog.csdnimg.cn/20210403131641701.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTEwMzc1OTM=,size_16,color_FFFFFF,t_70#pic_center)



v4l2_async_notifier_register
    // num\_subdevs为0或num\_subdevs大于128,返回错误
    if (!notifier->num_subdevs || notifier->num_subdevs > V4L2_MAX_SUBDEVS)
	    return -EINVAL;
    // notifier->v4l2\_dev指向v4l2\_device结构体指针v4l2\_dev
    notifier->v4l2_dev = v4l2_dev;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值