Linux V4L2子系统分析(一)
1.概述
Linux系統(tǒng)上的Video設備多種多樣,如通過Camera Host控制器接口連接的攝像頭,通過USB總線連接的攝像頭等。為了兼容更多的硬件,Linux內核抽象了V4L2(Video for Linux Two)子系統(tǒng)。V4L2子系統(tǒng)是Linux內核中關于Video(視頻)設備的API接口,是V4L(Video for Linux)子系統(tǒng)的升級版本。V4L2子系統(tǒng)向上為虛擬文件系統(tǒng)提供了統(tǒng)一的接口,應用程序可通過虛擬文件系統(tǒng)訪問Video設備。V4L2子系統(tǒng)向下給Video設備提供接口,同時管理所有Video設備。Video設備又分為主設備和從設備,對于Camera來說,Camera Host控制器為主設備,負責圖像數(shù)據的接收和傳輸,從設備為Camera Sensor,一般為I2C接口,可通過從設備控制Camera采集圖像的行為,如圖像的大小、圖像的FPS等。主設備可通過v4l2_subdev_call的宏調用從設備提供的方法,反過來從設備可以調用主設備的notify方法通知主設備某些事件發(fā)生了。
2.V4L2子系統(tǒng)
V4L(Video for Linux)是Linux內核中關于視頻設備的API接口,涉及視頻設備的音頻和視頻信息采集及處理、視頻設備的控制。V4L出現(xiàn)于Linux內核2.1版本,經過修改bug和添加功能,Linux內核2.5版本推出了V4L2(Video for Linux Two)子系統(tǒng),功能更多且更穩(wěn)定。V4L2的主設備號是81,次設備號范圍0~255,這些次設備號又分為多類設備,如視頻設備(次設備號范圍0-63)、Radio(收音機)設備(次設備號范圍64-127)、Teletext設備(次設備號范圍192-223)、VBI設備(次設備號范圍224-255)。V4L2設備對應的設備節(jié)點有/dev/videoX、/dev/vbiX、/dev/radioX。這里只討論視頻設備,視頻設備對應的設備節(jié)點是/dev/videoX。視頻設備以高頻頭或Camera為輸入源,Linux內核驅動該類設備,接收相應的視頻信息并處理。
2.1.V4L2主設備數(shù)據結構
V4L2主設備實例使用struct v4l2_device結構體表示,v4l2_device是V4L2子系統(tǒng)的入口,管理著V4L2子系統(tǒng)的主設備和從設備。簡單設備可以僅分配這個結構體,但在大多數(shù)情況下,都會將這個結構體嵌入到一個更大的結構體中。需要與媒體框架整合的驅動必須手動設置dev->driver_data,指向包含v4l2_device結構體實例的驅動特定設備結構體。這可以在注冊V4L2設備實例前通過dev_set_drvdata()函數(shù)完成。同時必須設置v4l2_device結構體的mdev域,指向適當?shù)某跏蓟⒆赃^的media_device實例。
對于視頻設備,Camera控制器可以視為主設備,接在Camera控制器上的攝像頭可以視為從設備。V4L2子系統(tǒng)使用v4l2_device結構體管理設備,設備的具體操作方法根據設備類型決定,若是視頻設備,則需要注冊video_device結構體,并提供相應的操作方法。
使用v4l2_device_register注冊v4l2_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等名字。若驅動名以數(shù)字結尾,則會在編號和驅動名間插入一個破折號,如:cx18-0、cx18-1等。dev參數(shù)通常是一個指向pci_dev、usb_interface或platform_device的指針,很少使其為NULL,除非是一個ISA設備或者當一個設備創(chuàng)建了多個PCI設備,使得v4l2_dev無法與一個特定的父設備關聯(lián)。
使用v4l2_device_unregister卸載v4l2_device結構體。如果dev->driver_data域指向 v4l2_dev,將會被重置為NULL。主設備注銷的同時也會自動注銷所有子設備。如果你有一個熱插拔設備(如USB設備),則當斷開發(fā)生時,父設備將無效。由于v4l2_device有一個指向父設備的指針必須被清除,同時標志父設備
已消失,所以必須調用v4l2_device_disconnect函數(shù)清理v4l2_device中指向父設備的dev指針。v4l2_device_disconnect并不注銷主設備,因此依然要調用v4l2_device_unregister函數(shù)注銷主設備。
同一個硬件的情況下。如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;}有時你需要一個設備實例的運行計數(shù)。這個通常用于映射一個設備實例到一個模塊選擇數(shù)組的索引。推薦方法如下:
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;}如果有多個設備節(jié)點,對于熱插拔設備,知道何時注銷v4l2_device結構體就比較困難。為此v4l2_device有引用計數(shù)支持。當調用video_register_device時增加引用計數(shù),而設備節(jié)點釋放時減小引用計數(shù)。當引用計數(shù)為零,則v4l2_device的release回調將被執(zhí)行。可以在此時做最后的清理工作。如果創(chuàng)建了其他設備節(jié)點(比如ALSA),則你可以通過以下函數(shù)手動增減引用計數(shù):
[include/media/v4l2-device.h]// 增加引用計數(shù)void v4l2_device_get(struct v4l2_device *v4l2_dev);// 減少引用計數(shù)int v4l2_device_put(struct v4l2_device *v4l2_dev);由于引用技術初始化為1,需要在disconnect回調(對于USB設備)中調用v4l2_device_put,或者remove回調(例如對于PCI設備),否則引用計數(shù)將永遠不會為0。
2.2.V4L2從設備數(shù)據結構
V4L2從設備使用struct v4l2_subdev結構體表示。一個V4L2主設備可能對應多個V4L2從設備,所有主設備對應的從設備都掛到v4l2_device結構體的subdevs鏈表中。對于視頻設備,從設備就是攝像頭,通常情況下是I2C設備,主設備可通過I2C總線控制從設備,例如控制攝像頭的焦距、閃光燈等。struct v4l2_subdev結構體重要成員示意圖如下圖所示。struct v4l2_subdev_video_ops將在《Linux V4L2子系統(tǒng)-Video設備框架分析(二)》中介紹。
[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) // 從設備需要設備節(jié)點#define V4L2_SUBDEV_FL_HAS_EVENTS (1U << 3) // 從設備會產生事件struct v4l2_subdev {#if defined(CONFIG_MEDIA_CONTROLLER) // 多媒體配置選項struct media_entity entity;#endifstruct list_head list; // 子設備串聯(lián)鏈表struct module *owner; // 屬于那個模塊,一般指向i2c_lient驅動模塊bool owner_v4l2_dev;// 標志位,確定該設備屬于那種設備,由V4L2_SUBDEV_FL_IS_XX宏確定u32 flags;// 指向主設備的v4l2_device結構體struct v4l2_device *v4l2_dev;// v4l2子設備的操作函數(shù)集合const struct v4l2_subdev_ops *ops;// 提供給v4l2框架的操作函數(shù),只有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;// 從設備私有數(shù)據指針,一般指向i2c_client的設備結構體devvoid *dev_priv;// 主設備私有數(shù)據指針,一般指向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,用于主設備和從設備的異步關聯(lián)struct v4l2_async_notifier *notifier;/* common part of subdevice platform data */struct v4l2_subdev_platform_data *pdata;};// 提供給v4l2框架的操作函數(shù),只有v4l2框架會調用,驅動不使用struct v4l2_subdev_internal_ops {// v4l2_subdev注冊時回調此函數(shù),使v4l2_dev指向主設備的v4l2_device結構體int (*registered)(struct v4l2_subdev *sd);// v4l2_subdev卸載時回調此函數(shù)void (*unregistered)(struct v4l2_subdev *sd);// 應用調用open打開從設備節(jié)點時調用此函數(shù)int (*open)(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh);// 應用調用close時調用此函數(shù)int (*close)(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh);};使用v4l2_subdev_init初始化v4l2_subdev結構體。然后必須用一個唯一的名字初始化subdev->name,同時初始化模塊的owner域。若從設備是I2C設備,則可使用v4l2_i2c_subdev_init函數(shù)進行初始化,該函數(shù)內部會調用v4l2_subdev_init,同時設置flags、owner、dev、name等成員。
[include/media/v4l2-subdev.h]// 初始化v4l2_subdev結構體// ops-v4l2子設備的操作函數(shù)集合指針,保存到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子設備的操作函數(shù)集合指針,保存到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數(shù)組必須預先初始化。無須手動設置media_entity的type和name域,但如有必要,revision域必須初始化。當(任何)從設備節(jié)點被打開/關閉,對entity的引用將被自動獲取/釋放。在從設備被注銷之后,使用media_entity_cleanup清理media_entity結構體。如果從設備驅動趨向于處理視頻并整合進了媒體框架,必須使用v4l2_subdev_pad_ops替代v4l2_subdev_video_ops實現(xiàn)格式相關的功能。這種情況下,子設備驅動應該設置link_validate域,以提供它自身的鏈接驗證函數(shù)。鏈接驗證函數(shù)應對管道(兩端鏈接的都是V4L2從設備)中的每個鏈接調用。驅動還要負責驗證子設備和視頻節(jié)點間格式配置的正確性。如果link_validate操作沒有設置,默認的v4l2_subdev_link_validate_default函數(shù)將會被調用。這個函數(shù)保證寬、高和媒體總線像素格式在鏈接的收發(fā)兩端都一致。子設備驅動除了它們自己的檢測外,也可以自由使用這個函數(shù)以執(zhí)行上面提到的檢查。
[include/media/media-entity.h]// 初始化v4l2_subdev結構體中的media_entity結構體// entity-要初始化的media_entity結構體指針// num_pads-源pad的數(shù)量,與驅動子設備結構相關// pads-media_pad結構體數(shù)組,通常pad被嵌入到驅動自定義的結構體里面,// 數(shù)組地址被傳遞給該參數(shù),pad需提前初始化// extra_links-函數(shù)會根據num_pads分配media_link數(shù)目,該參數(shù)則指明除了預分配的數(shù)量之外還需要多少額外的media_link數(shù)目// 返回值 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層級的操作函數(shù)[include/media/v4l2-subdev.h]struct v4l2_subdev_pad_ops {// ioctl VIDIOC_SUBDEV_ENUM_MBUS_CODE命令處理函數(shù)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命令處理函數(shù)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命令處理函數(shù)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命令處理函數(shù)int (*get_fmt)(struct v4l2_subdev *sd,struct v4l2_subdev_pad_config *cfg,struct v4l2_subdev_format *format);// ioctl VIDIOC_SUBDEV_S_FMT命令處理函數(shù)int (*set_fmt)(struct v4l2_subdev *sd,struct v4l2_subdev_pad_config *cfg,struct v4l2_subdev_format *format);// ioctl VIDIOC_SUBDEV_G_SELECTION命令處理函數(shù)int (*get_selection)(struct v4l2_subdev *sd,struct v4l2_subdev_pad_config *cfg,struct v4l2_subdev_selection *sel);// ioctl VIDIOC_SUBDEV_S_SELECTION命令處理函數(shù)int (*set_selection)(struct v4l2_subdev *sd,struct v4l2_subdev_pad_config *cfg,struct v4l2_subdev_selection *sel);// ioctl VIDIOC_SUBDEV_G_EDID命令處理函數(shù)int (*get_edid)(struct v4l2_subdev *sd, struct v4l2_edid *edid);// ioctl VIDIOC_SUBDEV_S_EDID命令處理函數(shù)int (*set_edid)(struct v4l2_subdev *sd, struct v4l2_edid *edid);// ioctl VIDIOC_SUBDEV_DV_TIMINGS_CAP命令處理函數(shù)int (*dv_timings_cap)(struct v4l2_subdev *sd, struct v4l2_dv_timings_cap *cap);// ioctl VIDIOC_SUBDEV_ENUM_DV_TIMINGS命令處理函數(shù)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 */// 獲取當前低級媒體總線幀參數(shù)int (*get_frame_desc)(struct v4l2_subdev *sd, unsigned int pad,struct v4l2_mbus_frame_desc *fd); // 設置低級媒體總線幀參數(shù)int (*set_frame_desc)(struct v4l2_subdev *sd, unsigned int pad,struct v4l2_mbus_frame_desc *fd);};從設備必須向V4L2子系統(tǒng)注冊v4l2_subdev結構體,使用v4l2_device_register_subdev注冊,使用v4l2_device_unregister_subdev注銷。
[include/media/v4l2-device.h]// 向V4L2子系統(tǒng)注冊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子系統(tǒng)注銷v4l2_subdev結構體// sd-從設備v4l2_subdev結構體指針 void v4l2_device_unregister_subdev(struct v4l2_subdev *sd);V4L2從設備驅動都必須有一個v4l2_subdev結構體。這個結構體可以單獨代表一個簡單的從設備,也可以嵌入到一個更大的結構體中,與更多設備狀態(tài)信息保存在一起。通常有一個下級設備結構體(比如:i2c_client)包含了內核創(chuàng)建的設備數(shù)據。建議使用v4l2_set_subdevdata()將這個結構體的指針保存在v4l2_subdev的私有數(shù)據域(dev_priv)中。這使得通過v4l2_subdev找到實際的低層總線特定設備數(shù)據變得容易。同時也需要一個從低層結構體獲取v4l2_subdev指針的方法。對于常用的i2c_client結構體,i2c_set_clientdata函數(shù)可用于保存一個v4l2_subdev指針,i2c_get_clientdata可以獲取一個v4l2_subdev指針;對于其他總線可能需要使用其他相關函數(shù)。
[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_subdevstatic inline void *i2c_get_clientdata(const struct i2c_client *dev){return dev_get_drvdata(&dev->dev);}主設備驅動中也應保存每個子設備的私有數(shù)據,比如一個指向特定主設備的各設備私有數(shù)據的指針。為此v4l2_subdev結構體提供主設備私有數(shù)據域(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都包含子設備驅動需要實現(xiàn)的函數(shù)指針(如果對此設備不適用,可為NULL),具體在v4l2_subdev_ops結構體當中。由于子設備可完成許多不同的工作,而在一個龐大的函數(shù)指針結構體中通常僅有少數(shù)有用的函數(shù)實現(xiàn)其功能肯定不合適。所以,函數(shù)指針根據其實現(xiàn)的功能被分類,每一類都有自己的函數(shù)指針結構體,如v4l2_subdev_core_ops、v4l2_subdev_audio_ops、v4l2_subdev_video_ops等等。v4l2_subdev_video_ops在視頻設備中(第二章中)詳細介紹。頂層函數(shù)指針結構體包含了指向各類函數(shù)指針結構體的指針,如果子設備驅動不支持該類函數(shù)中的任何一個功能,則指向該類結構體的指針為NULL。
[include/media/v4l2-subdev.h]/* v4l2從設備的操作函數(shù)集合,從設備根據自身設備類型選擇實現(xiàn),其中core函數(shù)集通常可用于所有子設備,其他類別的實現(xiàn)依賴于子設備。如視頻設備可能不支持音頻操作函數(shù),反之亦然。這樣的設置在限制了函數(shù)指針數(shù)量的同時,還使增加新的操作函數(shù)和分類變得較為容易。 */struct v4l2_subdev_ops {// 從設備的通用操作函數(shù)集合,進行初始化、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從設備的操作函數(shù)集合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);// 設置從設備的電源狀態(tài),0-省電模式,1-正常操作模式int (*s_power)(struct v4l2_subdev *sd, int on);// 中斷函數(shù),被主設備的中斷函數(shù)調用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回調函數(shù)由主設備提供。
[include/media/v4l2-device.h]// 從設備通知主設備,最終回調到v4l2_device的notify函數(shù)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控制。這個區(qū)別僅在配置設備時有關系,一旦子設備注冊完成,對于v4l2子系統(tǒng)來說就完全透明了。
2.3.V4L2主設備和從設備匹配過程分析
V4L2主設備和從設備采用異步的匹配方法。首先介紹一下異步匹配用到的方法。主設備使用v4l2_async_notifier_register函數(shù)進行異步匹配,匹配到從設備,則調用v4l2_device_register_subdev函數(shù)注冊從設備,使用v4l2_async_notifier_unregister函數(shù)異步取消匹配。從設備使用v4l2_async_register_subdev函數(shù)異步匹配主設備,若匹配到主設備,則調用v4l2_device_register_subdev函數(shù)注冊從設備,使用v4l2_async_unregister_subdev函數(shù)異步取消匹配。
匹配的方法由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等。聯(lián)合體match中包含了具體的匹配信息,根據匹配方式進行設置。v4l2_async_notifier管理整個匹配過程,未匹配的v4l2_async_subdev結構體被掛到waiting鏈表,匹配完成的掛到done鏈表同時調用bound函數(shù)進行綁定。
以imx6ull為例分析主設備和從設備的匹配過程。v4L2主設備和從設備的匹配過程可通過分析v4l2_async_notifier_register函數(shù)得到。可總結如下:
(1)首先初始化需要匹配的v4l2_async_notifier結構體,主要設備匹配方式、bound函數(shù)和指向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函數(shù),也就是使主設備mx6s_csi_dev結構體中的sd成員指向了從設備的v4l2_subdev結構體,從而完成了主設備和從設備進行綁定。
(8)將匹配成功的從設備V4l2_subdev結構體從全局的subdev_list鏈表中移除,同時添加到v4l2_async_notifier的done鏈表中。
(9)調用v4l2_device_register_subdev注冊從設備。
參考資料
總結
以上是生活随笔為你收集整理的Linux V4L2子系统分析(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 移动端 Retina屏 各大主流网站1p
- 下一篇: BigBrother的大数据之旅 Day