日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

Linux V4L2子系统分析(一)

發(fā)布時間:2023/12/20 linux 19 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux V4L2子系统分析(一) 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

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結構體,并提供相應的操作方法。

[include/media/v4l2-device.h]struct v4l2_device {struct device *dev; // 父設備指針#if defined(CONFIG_MEDIA_CONTROLLER) // 多媒體設備配置選項// 用于運行時數(shù)據流的管理,struct media_device *mdev;#endif// 注冊的子設備的v4l2_subdev結構體都掛載此鏈表中struct list_head subdevs;// 同步用的自旋鎖spinlock_t lock;// 獨一無二的設備名稱,默認使用driver name + bus IDchar name[V4L2_DEVICE_NAME_SIZE];// 被一些子設備回調的通知函數(shù),但這個設置與子設備相關。子設備支持的任何通知必須在// include/media/<subdevice>.h 中定義一個消息頭。void (*notify)(struct v4l2_subdev *sd, unsigned int notification, void *arg);// 提供子設備(主要是video和ISP設備)在用戶空間的特效操作接口,// 比如改變輸出圖像的亮度、對比度、飽和度等等struct v4l2_ctrl_handler *ctrl_handler;// 設備優(yōu)先級狀態(tài)struct v4l2_prio_state prio;/* BKL replacement mutex. Temporary solution only. */struct mutex ioctl_lock;// struct v4l2_device結構體的引用計數(shù),等于0時才釋放struct kref ref;// 引用計數(shù)ref為0時,調用release函數(shù)進行釋放資源和清理工作void (*release)(struct v4l2_device *v4l2_dev);};

使用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ù)注銷主設備。

[include/media/v4l2-device.h]// 注冊v4l2_device結構體,并初始化v4l2_device結構體// dev-父設備結構體指針,若為NULL,在注冊之前設備名稱name必須被設置,// v4l2_dev-v4l2_device結構體指針// 返回值-0成功,小于0-失敗int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)// 卸載注冊的v4l2_device結構體// v4l2_dev-v4l2_device結構體指針void v4l2_device_unregister(struct v4l2_device *v4l2_dev)// 設置設備名稱,填充v4l2_device結構體中的name成員// v4l2_dev-v4l2_device結構體指針// basename-設備名稱基本字符串// instance-設備計數(shù),調用v4l2_device_set_name后會自加1// 返回值-返回設備計數(shù)自加1的值int v4l2_device_set_name(struct v4l2_device *v4l2_dev, const char *basename, atomic_t *instance)// 熱插拔設備斷開時調用此函數(shù)// 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;}

有時你需要一個設備實例的運行計數(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ù)進行綁定。

[include/media/v4l2-async.h]// 主設備和從設備的匹配方式 enum v4l2_async_match_type {// 傳統(tǒng)的匹配方式,使用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 {// 傳統(tǒng)的匹配方式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);// 主設備注銷notifiervoid 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的數(shù)量// 指針數(shù)組,指向subdevice descriptorsstruct v4l2_async_subdev **subdevs; // 指向struct v4l2_devicestruct v4l2_device *v4l2_dev;// v4l2_async_subdev的鏈表,等待匹配driversstruct list_head waiting;// 已經probed的v4l2_subdev鏈表struct list_head done;// 掛在全局的notifiers鏈表上struct list_head list;// 驅動匹配到從設備后調用此函數(shù)int (*bound)(struct v4l2_async_notifier *notifier,struct v4l2_subdev *subdev,struct v4l2_async_subdev *asd);// 所有從設備被probed成功,調用此函數(shù)int (*complete)(struct v4l2_async_notifier *notifier);// 從設備注銷時調用此函數(shù)void (*unbind)(struct v4l2_async_notifier *notifier,struct v4l2_subdev *subdev,struct v4l2_async_subdev *asd);};

以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注冊從設備。

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_devnotifier->v4l2_dev = v4l2_dev;for (i = 0; i < notifier->num_subdevs; i++) {asd = notifier->subdevs[i];switch (asd->match_type) {case V4L2_ASYNC_MATCH_CUSTOM:case V4L2_ASYNC_MATCH_DEVNAME:case V4L2_ASYNC_MATCH_I2C:case V4L2_ASYNC_MATCH_OF:break;......}// 將指針數(shù)組subdevs指向的v4l2_async_subdev結構體掛入// notifier的waiting鏈表list_add_tail(&asd->list, &notifier->waiting);}mutex_lock(&list_lock); // 加鎖,保護全局鏈表// 將v4l2_async_notifier掛到全局鏈表notifier_listlist_add(&notifier->list, &notifier_list); // 遍歷subdev_list鏈表,所有從設備的v4l2_subdev結構體都掛到subdev_list鏈表list_for_each_entry_safe(sd, tmp, &subdev_list, async_list) {int ret;// 判斷子設備的v4l2_subdev是否和主設備的notifier匹配,// 匹配則返回v4l2_async_subdev結構體asd = v4l2_async_belongs(notifier, sd);if (!asd)continue;// 匹配成功,調用bound函數(shù),注冊從設備的v4l2_subdev結構體ret = v4l2_async_test_notify(notifier, sd, asd);if (ret < 0) {mutex_unlock(&list_lock);return ret;}}mutex_unlock(&list_lock);return 0;v4l2_async_belongs// 定義匹配的函數(shù)指針bool (*match)(struct device *, struct v4l2_async_subdev *);// 遍歷waiting鏈表,取出v4l2_async_subdev結構體list_for_each_entry(asd, &notifier->waiting, list) {// 確認匹配方式switch (asd->match_type) {case V4L2_ASYNC_MATCH_CUSTOM:match = asd->match.custom.match;if (!match)return asd;break;case V4L2_ASYNC_MATCH_DEVNAME:match = match_devname; // 設備名稱匹配方法break;case V4L2_ASYNC_MATCH_I2C: // I2C匹配方法match = match_i2c;break;case V4L2_ASYNC_MATCH_OF: // 設備樹的匹配方法match = match_of;break;......}// 根據匹配方式,調用相應的匹配方法if (match(sd->dev, asd))return asd;}return NULL;// 根據設備名稱匹配,static bool match_devname(struct device *dev, struct v4l2_async_subdev *asd){return !strcmp(asd->match.device_name.name, dev_name(dev));}// 對比i2c_client的adapter_id和addressstatic bool match_i2c(struct device *dev, struct v4l2_async_subdev *asd){#if IS_ENABLED(CONFIG_I2C)struct i2c_client *client = i2c_verify_client(dev);return client &&asd->match.i2c.adapter_id == client->adapter->nr &&asd->match.i2c.address == client->addr;#elsereturn false;#endif}// 對比設備樹節(jié)點static bool match_of(struct device *dev, struct v4l2_async_subdev *asd){return dev->of_node == asd->match.of.node;}v4l2_async_test_notify// 從waiting鏈表中移除list_del(&asd->list);sd->asd = asd;sd->notifier = notifier;// bound非空,則調用bound函數(shù),bound函數(shù)的主要作用是設置主設備的v4l2_subdev指針,// 使其指向匹配的從設備的v4l2_subdev結構體,從而完成主設備到從設備的綁定if (notifier->bound) {ret = notifier->bound(notifier, sd, asd);......}// 將subdevice從async_list鏈表中移除后掛到done鏈表中list_move(&sd->async_list, &notifier->done);// 注冊從設備v4l2_subdev結構體v4l2_device_register_subdev(notifier->v4l2_dev, sd)// 如果waiting鏈表無等待的v4l2_async_subdev,且complete非空if (list_empty(&notifier->waiting) && notifier->complete)return notifier->complete(notifier); // 調用完成函數(shù)

參考資料

  • https://blog.csdn.net/kickxxx/article/details/8484498
  • https://blog.csdn.net/weixin_44139476/article/details/107021395
  • 內核文檔-v4l2-framework_zh_CN.txt
  • Linux內核4.1版本源碼
  • Android驅動開發(fā)權威指南
  • Android驅動開發(fā)與移植實戰(zhàn)詳解
  • https://linuxtv.org/downloads/v4l-dvb-apis/driver-api/v4l2-subdev.html
  • 總結

    以上是生活随笔為你收集整理的Linux V4L2子系统分析(一)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。