原文連接node
本文開啓 linux 內核 V4L2 框架部分的學習之旅,本文僅先對 V4L2 的框架作一個綜述性的歸納介紹,而後接下來的文章中會對 V4L2 框架的各個子模塊進行一個全面的介紹,包括每一部分的實現原理,如何使用,用在什麼地方等等。預計接下來的文章大概有5篇(不帶本篇)。坑已經挖好了,開始吧。
<!-- more -->linux
導讀:V4L2 是專門爲 linux 設備設計的一套視頻框架,其主體框架在 linux 內核,能夠理解爲是整個 linux 系統上面的視頻源捕獲驅動框架。其普遍應用在嵌入式設備以及移動端、我的電腦設備上面,市面上的編碼產品類如:SDV、手機、IPC、行車記錄儀都會用到這個框架來進行視頻採集,固然,有比較厲害的廠家直接就使用本身實現的一套視頻採集框架,這種屬因而廠家中戰鬥機了。下文主要參考linux-4.4內核文檔對V4L2框架進行一次全局的介紹。編程
幾乎全部的設備都有多個 IC 模塊,它們多是實體的(例如 USB 攝像頭裏麪包含 ISP、sensor 等)、也多是抽象的(如 USB 設備裏面的抽象拓撲結構),它們在 /dev
目錄下面生成了多個設備節點,而且這些 IC 模塊還建立了一些非 v4l2 設備:DVB、ALSA、FB、I2C 和輸入設備。正是因爲硬件的複雜性,v4l2 的驅動也變得很是複雜。數組
特別是 v4l2 驅動要支持 IC 模塊來進行音/視頻的混合/編解碼操做,這就更加使得 v4l2 驅動變得異常複雜。一般狀況下,有些IC模塊經過一個或者多個 I2C 總線鏈接到主橋驅動上面,同時其它的總線仍然可用,這些 IC 就稱爲 ‘sub-devices’,好比攝像頭設備裏面的 sensor 傳感器就是使用 I2C 來進行命令溝通,同時使用 MIPI 或者 LVDS 等接口進行圖像數據傳輸。安全
在很長一段時間內,該框架(指老舊的 V4L2 框架)僅限於經過 video_device 結構體建立 v4l 設備節點和 video_buf 來處理視頻數據。這意味着全部的驅動都必須對設備實例進行設置並將其映射到子設備上。有些時候這些操做步驟十分複雜,很難正確完成,而且有些驅動程序歷來沒有正確的按照這些操做步驟編寫。因爲缺乏一個框架,有不少通用代碼就沒有辦法被重構,從而致使這部分代碼被重複編寫,效率比較低下。網絡
所以,本框架抽象構建了全部驅動都須要的代碼並封裝爲一個個的模塊,簡化了設備驅動通用代碼的重構。v4l2-pci-skeleton.c 是個很是好的參考例程,它是一個PCI採集卡的驅動框架。該例程演示瞭如何使用 v4l2 驅動框架,而且該例程能夠做爲一個 PCI 視頻採集卡的驅動模板使用。在最開始的時候也能夠參照這個代碼編寫方式進行聯繫,固然最適合的代碼仍是 <font color='red'>drivers/media/video/omap3isp</font> 文件夾裏面的代碼,這個代碼基本上能夠做爲一個完整的輸入設備實例代碼(由於它包含了 ISP、CSI、video 等設備,而且有着一個完整的數據流 pipeline,幾乎用到了 V4L2 框架的方方面面,參考價值極大)來進行參考編寫本身的設備驅動代碼。app
這是一張很是大的圖,可是我只選取了其中的一個,這張圖對 V4L2 裏面的子模塊進行簡化(簡化到只有子模塊的名字,沒有內部實現的介紹),大圖以下:
<center>V4L2 設備拓撲</center>框架
這張圖怎麼看呢?它有如下幾個關鍵因素:異步
v4l2_device
:這個是整個輸入設備的總結構體,能夠認爲它是整個 V4L2 框架的入口,充當驅動的管理者以及入口監護人。由該結構體引伸出來 v4l2_subdev
。用於視頻輸入設備總體的管理,有多少輸入設備就有多少個v4l2_device
抽象(好比一個USB攝像頭總體就能夠看做是一個 V4L2 device)。再往下分是輸入子設備,對應的是例如 ISP、CSI、MIPI 等設備,它們是從屬於一個 V4L2 device 之下的。media_device
:用於運行時數據流的管理,嵌入在 V4L2 device 內部,運行時的意思就是:一個 V4L2 device 下屬可能有很是多同類型的子設備(兩個或者多個 sensor、ISP 等),那麼在設備運行的時候我怎麼知道個人數據流須要用到哪個類型的哪個子設備呢。這個時候就輪到 media_device
出手了,它爲這一坨的子設備創建一條虛擬的連線,創建起來一個運行時的 pipeline(管道),而且能夠在運行時動態改變、管理接入的設備。v4l2_ctrl_handler
:控制模塊,提供子設備(主要是 video 和 ISP 設備)在用戶空間的特效操做接口,好比你想改變下輸出圖像的亮度、對比度、飽和度等等,均可以經過這個來完成。vb2_queue
:提供內核與用戶空間的 buffer 流轉接口,輸入設備產生了一坨圖像數據,在內核裏面應該放在哪裏呢?能放幾個呢?是整段連續的仍是仍是分段連續的又或者是物理不連續的?用戶怎麼去取用呢?都是它在管理。custom_v4l2_dev
,它是由用戶定義的一個結構體,重要的不是它怎麼定義的,重要的是它裏面有一個 v4l2_device
結構體,上文說到,這個結構體總覽全局,指揮若定,至關於中央管理處的位置,那麼中央決定了,它就是整個輸入設備總體的抽象(好比整個 USB 攝像頭輸入設備,好比整個 IPC 攝像頭輸入設備)。它還有一個 media_device
結構體,上文也說道,,它是管數據流線路的,屬於搞結構路線規劃管理的。v4l2_device
裏面有一個鏈表,它維護了一個巨大的子設備鏈,全部的子設備都經過內核的雙向循環鏈表結構以 v4l2_device
爲中心牢牢團結在一塊兒。另外 media_device
在往裏面去就是一個個的 media_entity
(如今不須要了解它的具體含義,只須要知道它就是相似電路板上面的元器件同樣的抽象體),media_entity
之間創建了本身的小圈子,在它們這個小圈子裏面數據流按照必定的順序暢通無阻,恣意遨遊。/dev/videoX
設備節點,這個就是外交部的角色,它負責提供了一個內核與用戶空間的交流樞紐。須要注意的是,該設備節點的本質仍是一個字符設備,其內部的一套操做與字符設備是同樣的,只不過是進行了一層封裝而已。
<center>V4L2 層次結構</center>async
全部的 V4L2 驅動都有如下結構體類型:
custom_v4l2_dev
),裏面包含了設備的狀態;v4l2_subdev
)的方法;media_device
)保持跟蹤;v4l2_fh
文件句柄與句柄結構體一一對應);vb2_queue
);media_device
)與驅動結構體很是相似,參考上面的解釋,這裏再也不贅述。v4l2 框架也能夠整合到 media framework 裏面。若是驅動程序設置了 v4l2_device
的 mdev
成員,那麼子設備與 video 節點都會被自動看成 media framework 裏的 entitiy 抽象。
v4l2_device
結構體每個設備實例都被抽象爲一個 v4l2_device
結構體。一些簡單的設備能夠僅分配一個 v4l2_device
結構體便可,可是
大多數狀況下須要將該結構體嵌入到一個更大的結構體(custom_v4l2_dev
)裏面。必須用 v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev);
來註冊設備實例。該函數會初始化傳入的 v4l2_device
結構體,若是 dev->driver_data
成員爲空的話,該函數就會設置其指向傳入的 v4l2_dev
參數。
若是驅動想要集成 media framework 的話,就須要人爲地設置 dev->driver_data
指向驅動適配的結構體(該結構體由
驅動自定義- custom_v4l2_dev
,裏面嵌入 v4l2_device
結構體)。在註冊 v4l2_device
以前就須要調用 dev_set_drvdata
來完成設置。而且必須設置 v4l2_decice
的 mdev
成員指向註冊的 media_device
結構體實例。
若是 v4l2_device
的 name
成員爲空的話,就按照 dev
成員的名稱來命名,若是 dev
成員也爲空的話,就必須在註冊 v4l2_device
以前設置它的 name
成員。可使用 v4l2_device_set_name
函數來設置 name
成員,該函數會基於驅動名以及驅動實例的索引號來生成 name
成員的名稱,相似於 ivtv0、ivtv1 等等,若是驅動名的最後一個字母是整數的話,生成的名稱就相似於cx18-0、cx18-1等等,該函數的返回值是驅動實例的索引號。
還能夠提供一個 notify()
回調函數給 v4l2_device
接收來自子設備的事件通知。固然,是否須要設置該回調函數取決於子設備是否有向主設備發送通知事件的需求。v4l2_device
的卸載需調用到 v4l2_device_unregister
函數。在該函數被調用以後,若是 dev->driver_data
指向 v4l2_device
的話,該指針將會被設置爲NULL。該函數會將全部的子設備所有卸載掉。若是設備是熱拔插屬性的話,當 disconnect 發生的時候,父設備就會失效,同時 v4l2_device
指向父設備的指針也必須被清除,能夠調用 v4l2_device_disconnect
函數來清除指針,該函數並不卸載子設備,子設備的卸載仍是須要調用到 v4l2_device_unregister
來完成。若是不是熱拔插設備的話,就沒必要關注這些。
有些時候須要對驅動的全部設備進行迭代,這種狀況一般發生在多個設備驅動使用同一個硬件設備的狀況下,好比 ivtvfb 驅動就是個 framebuffer 驅動,它用到了 ivtv 這個硬件設備。可使用如下方法來迭代全部的已註冊設備:
static int callback(struct device *dev, void *p) { struct v4l2_device *v4l2_dev = dev_get_drvdata(dev); /* test if this device was inited */ if (v4l2_dev == NULL) return 0; ... return 0; } int iterate(void *p) { struct device_driver *drv; int err; /* Find driver 'ivtv' on the PCI bus. * pci_bus_type is a global. For USB busses use usb_bus_type. */ drv = driver_find("ivtv", &pci_bus_type); /* iterate over all ivtv device instances */ 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; }
若是一個熱拔插設備有不少個設備節點(好比一個USB攝像頭能夠產生多路視頻輸出,雖然它的視頻源是一個),那麼很難知道在何時纔可以安全地卸載 v4l2_device
設備。基於以上問題, v4l2_device
引入了引用計數機制,當 video_register_device
函數被調用的時候,引用計數會加一,當 video_device
被釋放的時候,引用計數會減一,直到 v4l2_device
的引用計數到0的時候,v4l2_device
的 release
回調函數就會被調用,能夠在該回調函數裏面作一些清理工做。當其它的設備(alsa,由於這個不屬於 video 設備,因此也就不能使用上面的 video 函數進行計數的加減操做)節點被建立的時候,能夠人爲調用如下函數對引用計數進行增減操做:
void v4l2_device_get(struct v4l2_device *v4l2_dev); int v4l2_device_put(struct v4l2_device *v4l2_dev);
須要注意的是,v4l2_device_register
函數將引用計數初始化爲1,因此須要在remove
或者disconnect
回調方法裏面調用v4l2_device_put
來減小引用計數,不然引用計數將永遠不會達到0。
v4l2_subdev
結構體不少設備都須要與子設備進行交互,一般狀況下子設備用於音視頻的編解碼以及混合處理,對於網絡攝像機來講子設備就是 sensors 和 camera 控制器。一般狀況下它們都是 I2C 設備,但也有例外。v4l2_subdev
結構體被用於子設備管理。
每個子設備驅動都必須有一個 v4l2_subdev
結構體,這個結構體能夠做爲獨立的簡單子設備存在,也能夠嵌入到更大的結構體(自定義的子設備結構體)裏面。一般會有一個由內核設置的低層次結構體(i2c_client
,也就是上面說的 i2c 設備),它包含了一些設備數據,要調用 v4l2_set_subdevdata
來設置子設備私有數據指針指向它,這樣的話就能夠很方便的從 subdev
找到相關的 I2C 設備數據(這個要編程實現的時候纔可以瞭解它的用意)。另外也須要設置低級別結構的私有數據指針指向 v4l2_subdev
結構體,方便從低級別的結構體訪問 v4l2_subdev
結構體,達到雙向訪問的目的,對於 i2c_client
來講,能夠用 i2c_set_clientdata
函數來設置,其它的需使用與之相應的函數來完成設置。
橋驅動器須要存儲每個子設備的私有數據,v4l2_subdev
結構體提供了主機私有數據指針成員來實現此目的,使用如下函數能夠對主機私有數據進行訪問控制:
v4l2_get_subdev_hostdata(); v4l2_set_subdev_hostdata();
從橋驅動器的角度來看,咱們加載子設備模塊以後能夠用某種方式獲取子設備指針。對於 i2c 設備來講,調用 i2c_get_clientdata
函數便可完成,其它類型的設備也有與之類似的操做,在內核裏面提供了很多的幫助函數來協助完成這部分工做,編程時能夠多多使用。
每一個 v4l2_subdev
結構體都包含有一些函數指針,指向驅動實現的回調函數,內核對這些回調函數進行了分類以免出現定義了一個巨大的回調函數集,可是裏面只有那麼幾個用得上的尷尬狀況。最頂層的操做函數結構體內部包含指向各個不一樣類別操做函數結構體的指針成員,以下所示:
struct v4l2_subdev_core_ops { int (*log_status)(struct v4l2_subdev *sd); int (*init)(struct v4l2_subdev *sd, u32 val); ... }; struct v4l2_subdev_tuner_ops { ... }; struct v4l2_subdev_audio_ops { ... }; struct v4l2_subdev_video_ops { ... }; struct v4l2_subdev_pad_ops { ... }; 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; const struct v4l2_subdev_vbi_ops *vbi; const struct v4l2_subdev_ir_ops *ir; const struct v4l2_subdev_sensor_ops *sensor; const struct v4l2_subdev_pad_ops *pad; };
<font color='red'>這部分的設計我我的以爲是很是實用的,linux 要想支持大量的設備的同時又要保持代碼的精簡就必須得這樣去實現。</font>core ops
成員對於全部的子設備來講都是通用的,其他的成員不一樣的驅動會有選擇的去使用,例如:video 設備就不須要支持 audio 這個 ops 成員。子設備驅動的初始化使用 v4l2_subdev_init
函數來完成(該函數只是初始化一些 v4l2_subdev
的成員變量,內容比較簡單),在初始化以後須要設置子設備結構體的 name
和 owner
成員(若是是 i2c 設備的話,這個在 i2c helper 函數裏面就會被設置)。該部分 ioctl 能夠直接經過用戶空間的 ioctl 命令訪問到(前提是該子設備在用戶空間生成了子設備節點,這樣的話就能夠操做子設備節點來進行 ioctl)。內核裏面可使用 v4l2_subdev_call
函數來對這些回調函數進行調用,這個在 pipeline 管理的時候十分受用。
若是須要與 media framework 進行集成,必須初始化 media_entity
結構體並將其嵌入到 v4l2_subdev
結構體裏面,操做以下所示:
struct media_pad *pads = &my_sd->pads; int err; err = media_entity_init(&sd->entity, npads, pads, 0);
其中 pads 結構體變量必須提早初始化,media_entity
的 flags
、name
、type
、ops
成員須要設置。entity 的引用計數在子設備節點被打開/關閉的時候會自動地增減。在銷燬子設備的時候需使用 media_entity_cleanup
函數對 entity 進行清理。若是子設備須要處理 video 數據,就須要實現 v4l2_subdev_video_ops
成員,若是要集成到 media_framework 裏面,就必需要實現 v4l2_subdev_pad_ops
成員,此時使用 pad_ops 中與 format
有關的成員代替 v4l2_subdev_video_ops
中的相關成員。
子設備驅動須要設置 link_validation
成員來提供本身的 link validation 函數,該回調函數用來檢查 pipeline 上面的全部的 link 是否有效(是否有效由本身來作決定),該回調函數在 media_entity_pipeline_start
函數裏面被循環調用。若是該成員沒有被設置,那麼 v4l2_subdev_link_validate_default
將會做爲默認的回調函數被使用,該函數確保 link 的 source pad 和 sink pad 的寬、高、media 總線像素碼是一致的,不然就會返回錯誤。
有兩種方法能夠註冊子設備(注意是設備,不是設備驅動,經常使用的方式是經過設備樹來註冊),第一種(舊的方法,好比使用 platform_device_register
來進行註冊)是使用橋驅動去註冊設備。這種狀況下,橋驅動擁有鏈接到它的子設備的完整信息,而且知道什麼時候去註冊子設備,內部子設備一般屬於這種狀況。好比 SOC 內部的 video 數據處理單元,鏈接到 USB 或 SOC 的相機傳感器。另外一種狀況是子設備必須異步地被註冊到橋驅動上,好比基於設備樹的系統,此時全部的子設備信息都獨立於橋驅動器。使用這兩種方法註冊子設備的區別是 probing 的處理方式不一樣。也就是一種是設備信息結構體由驅動自己持有並註冊,一種是設備信息結構體由設備樹持有並註冊。
設備驅動須要用 v4l2_device
信息來註冊 v4l2_subdev
,以下所示:
int err = v4l2_device_register_subdev(v4l2_dev, sd);
若是子設備模塊在註冊以前消失的話,該操做就會失敗,若是成功的話就會使得 subdev->dev
指向 v4l2_device
。若是 v4l2_device
父設備的 mdev
成員不爲空的話,子設備的 entity 就會自動地被註冊到 mdev 指向的 media_device
裏面。在子設備須要被卸載而且 sd->dev
變爲NULL以後,使用以下函數來卸載子設備:
v4l2_device_unregister_subdev(sd);
若是子設備被註冊到上層的 v4l2_device
父設備中,那麼 v4l2_device_unregister
函數就會自動地把全部子設備卸載掉。但爲了以防萬一以及保持代碼的風格統一,須要註冊與卸載結對使用。能夠用如下方式直接調用ops成員:err = sd->ops->core->g_std(sd, &norm);
使用下面的宏定義能夠簡化書寫:err = v4l2_subdev_call(sd, core, g_std, &norm);
該操做會檢查 sd->dev
指針是否爲空,若是是,返回 -ENODEV
,同時若是 ops->core
或者 ops->core->g_std
爲空,則返回 -ENOIOCTLCMD
。也能夠經過如下函數調用來對 V4l2 下面掛載的全部子設備進行回調:
v4l2_device_call_all(v4l2_dev, 0, core, g_std, &norm);
該函數會跳過全部不支持該 ops 的子設備,而且全部的錯誤信息也被忽略,若是想捕獲錯誤信息,可使用下面的函數:
err = v4l2_device_call_until_err(v4l2_dev, 0, core, g_std, &norm);
該函數的第二個參數若是爲 0,則全部的子設備都會被訪問,若是非 0,則指定組的子設備會被訪問。
組ID使得橋驅動可以更加精確的去調用子設備操做函數,例如:在一個單板上面有不少個聲卡,每一個都可以改變音量,可是一般狀況下只訪問一個,這時就能夠設置子設備的組 ID 爲 AUDIO_CONTROLLER 並指定它的值,這時 v4l2_device_call_all
函數就會只去訪問指定組的子設備,提升效率。
若是子設備須要向 v4l2_device
父設備發送事件通知的話,就能夠調用 v4l2_subdev_notify
宏定義來回調 v4l2->notify
成員(前文有提到過)。
使用 v4l2_subdev
的優勢是不包含任何底層硬件的信息,它是對底層硬件的一個抽象,所以一個驅動可能包含多個使用同一條 I2C 總線的子設備,也可能只包含一個使用 GPIO 管腳控制的子設備,只有在驅動設置的時候纔有這些差異,而一旦子設備被註冊以後,底層硬件對驅動來講就是徹底透明的。
不清楚異步模式的用途
在異步模式下,子設備 probing 能夠被獨立地被調用以檢查橋驅動是否可用,子設備驅動必須確認全部的 probing 請求是否成功,若是有任意一個請求條件沒有知足,驅動就會返回-EPROBE_DEFER
來繼續下一次嘗試,一旦全部的請求條件都被知足,子設備就須要調用v4l2_async_register_subdev
函數來進行註冊(用v4l2_async_unregister_subdev
卸載)。橋驅動反過來得註冊一個 notifier 對象(v4l2_async_notifier_register
),該函數的第二個參數類型是v4l2_async_notifier
類型的結構體,裏面包含有一個指向指針數組的指針成員,指針數組每個成員都指向v4l2_async_subdev
類型結構體。v4l2 核心層會利用上述的異步子設備結構體描述符來進行子設備的匹配,若是成功匹配,.bound()notifier
回調函數將會被調用,當全部的子設備所有被加載完畢以後,.complete()
回調函數就會被調用,子設備被移除的時候.unbind()
函數就會被調用。
另外子設備還提供了一組內部操做函數,該內部函數的調用時機在下面有描述,原型以下所示:
struct v4l2_subdev_internal_ops { int (*registered)(struct v4l2_subdev *sd); void (*unregistered)(struct v4l2_subdev *sd); int (*open)(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh); int (*close)(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh); };
這些函數僅供 v4l2 framework 使用,驅動程序不該該顯式的去調用這些回調
v4l2_device_register_subdev
)/反註冊的時候被調用。能夠在 /dev
文件夾下建立 v4l-subdevX
設備節點以供用戶直接操做子設備硬件。若是須要在用戶空間建立設備節點的話,就須要在子設備節點註冊以前設置 V4L2_SUBDEV_FL_HAS_DEVNODE
標誌,而後調用 v4l2_device_register_subdev_nodes()
函數,就能夠在用戶空間建立設備節點,設備節點會在子設備卸載的時候自動地被銷燬。
VIDIOC_QUERYCTRL VIDIOC_QUERYMENU VIDIOC_G_CTRL VIDIOC_S_CTRL VIDIOC_G_EXT_CTRLS VIDIOC_S_EXT_CTRLS VIDIOC_TRY_EXT_CTRLS
上述 ioctls 能夠經過設備節點訪問,也能夠直接在子設備驅動裏面調用。
VIDIOC_DQEVENT VIDIOC_SUBSCRIBE_EVENT VIDIOC_UNSUBSCRIBE_EVENT
要使用上述事件,就必須設置 v4l2_subdev
的 V4L2_SUBDEV_USES_EVENTS
標誌位,實現 core_ops
的 subscribe
相關的回調函數,回調函數裏面須要初始化 events,而後註冊 v4l2_subdev
。一些私有的 ioctls 能夠在 v4l2_subdev
的 ops->core->ioctl
裏面實現。
要想在 I2C 驅動裏面添加 v4l2_subdev
支持,就須要把 v4l2_subdev
結構體嵌入到每一個 I2C 實例結構體裏面,有一些比較簡單的 I2C 設備不須要自定義的狀態結構體,此時只須要建立一個單獨的 v4l2_subdev
結構體便可。一個典型的驅動自定義狀態結構體以下所示:
struct chipname_state { struct v4l2_subdev sd; ... /* additional state fields */ };
使用 v4l2_i2c_subdev_init
去初始化一個 I2C 子設備,該函數會填充 v4l2_subdev
的全部成員並確保 v4l2_subdev
與 i2c_client
互相指向對方。也能夠添加內聯函數來從 v4l2_subdev
的指針獲取到 i2c_client
結構體:
struct i2c_client *client = v4l2_get_subdevdata(sd); 也能夠從i2c_client結構體指針獲取到v4l2_subdev結構體: struct v4l2_subdev *sd = i2c_get_clientdata(client); 橋驅動可使用如下幫助函數來建立一個I2C子設備: struct v4l2_subdev *sd = v4l2_i2c_new_subdev (v4l2_dev, adapter,"module_foo", "chipid", 0x36, NULL);
該函數會加載給定的模塊(能夠爲空)而且調用 i2c_new_device
根據傳入的參數建立子設備結構體,最後註冊 v4l2_subdev
。
video_device
結構體video_device
能夠動態的分配:
struct video_device *vdev = video_device_alloc(); if (vdev == NULL) return -ENOMEM; vdev->release = video_device_release;
若是須要將 video_device
結構體嵌入到更大的結構體裏面的話,就須要設置 vdev
的 release
成員。內核提供了兩個默認的 release
回調函數,以下:
video_device_release() // 僅僅調用kfree釋放分配的內存,用於動態分配狀況下 video_device_release_empty() // 不作任何事情,靜態變量
如下的函數成員必須被設置:
若是想忽略 ioctl_ops
中某個 ioctls 的話能夠調用下面的函數:
void v4l2_disable_ioctl(struct video_device *vdev, unsigned int cmd);
若是要集成到 media_framework 裏面,就須要設置 video_device
裏面的 media_entity
成員,同時須要提供 media_pad
:
struct media_pad *pad = &my_vdev->pad; int err; err = media_entity_init(&vdev->entity, 1, pad, 0);
video_device
的註冊video_device
的註冊函數以下:
err = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
該段代碼會註冊一個字符設備驅動程序並在用戶空間生成一個設備節點。若是 v4l2_device
父設備的 mdev
成員不爲空的話,video_device
的 entity
會被自動的註冊到 media framework 裏面。函數最後一個參數是設備節點索引號,若是是 -1 的話就取用第一個內核中可用的索引號值。註冊的設備類型以及用戶空間中的節點名稱取決於如下標識:
VFL_TYPE_GRABBER: videoX 輸入輸出設備 VFL_TYPE_VBI: vbiX VFL_TYPE_RADIO: radioX 硬件定義的音頻調諧設備 VFL_TYPE_SDR: swradioX 軟件定義的音頻調諧設備
當一個設備節點被建立時,相關屬性也會被建立,能夠在 /sys/class/video4linux
裏面看到這些設備文件夾,在文件夾裏面能夠看到 'name','dev_debug','index','uevent'
等屬性,可使用 cat
命令查看。'dev_debug' 能夠用於 video 設備調試,每一個 video 設備都會建立一個 'dev_debug' 屬性,該屬性以文件夾的形式存在與 /sys/class/video4linux/<devX>/
下面以供使能 log file operation。'dev_debug'是一個位掩碼,如下位能夠被設置:
0x01:記錄ioctl名字與錯誤碼。設置0x08位能夠只記錄VIDIOC_(D)QBUF 0x02:記錄ioctl的參數與錯誤碼。設置0x08位能夠只記錄VIDIOC_(D)QBUF 0x04:記錄file ops操做。設置0x08位能夠只記錄read&write成員的操做 0x08:如上所示 0x10:記錄poll操做
當以上的位被設置的時候,發生相關的調用或者操做的時候內核就會打印出來相關的調用信息到終端上面。相似於
[173881.402120] video4: VIDIOC_DQEVENT: error -2 [173884.906633] video4: VIDIOC_UNSUBSCRIBE_EVENT
當 video 設備節點須要被移除或者USB設備斷開時,須要執行如下函數:
video_unregister_device(vdev);
來進行設備的卸載,該函數會移除 /dev
下的設備節點文件,同時不要忘記調用 media_entity_cleanup
來清理 entity。
V4L 核心層提供了可選的鎖服務,最主要的就是 video_device
裏面的鎖,用來進行 ioctls 的同步。若是使用了 videobuf2
框架,那麼 video_device->queue->lock
鎖也會被用來作 queue 相關的 ioctls 同步。使用不一樣的鎖有不少優勢,好比一些設置相關的 ioctls 花費的時間比較長,若是使用獨立的鎖,VIDIOC_DQBUF
就不用等待設置操做的完成就能夠執行,這個在網絡攝像機驅動中很常見。固然,也能夠徹底由驅動自己去完成鎖操做,這時能夠設置全部的鎖成員爲NULL並實現一個驅動本身的鎖。
若是使用舊的 videobuf,須要將 video_device
的鎖傳遞給 videobuf queue 初始化函數,若是 videobuf 正在等待一幀數據的到達,此時會將鎖暫時釋放,等數據到達以後再次加鎖,不然別的處理程序就沒法訪問。因此不推薦使用舊的 videobuf。若是是在 videobuf2 框架下,須要實現 wait_prepare
與 wait_finish
回調函數去釋放或者獲取鎖,若是使用了 queue->lock
,可使用 V4L2 提供的回調 vb2_ops_wait_prepare/finish
幫助函數來完成加鎖與解鎖的操做,它們會使用 queue->lock
這個鎖(此時必定要將該鎖初始化)。
v4l2_fh
結構體該結構體提供了一種簡單的保存文件句柄特定數據的方法。v4l2_fh
的使用者-v4l2 framework 能夠經過檢查 video_device->flags
的 V4L2_FL_USES_V4L2_FH
位來知道驅動是否使用 v4l2_fh
做爲 file->private_data
指針,該標誌位經過調用函數 v4l2_fh_init
來設置。
v4l2_fh
結構體做爲驅動本身的文件句柄存在,而且在驅動的 open
函數裏面設置 file->private_data
指向它,v4l2_fh
有多個的時候會做爲一個鏈表存在於 file->private_data
中,能夠遍歷訪問。在大多數狀況下 v4l2_fh
結構體都被嵌入到更大的結構體裏面,此時須要在 open
函數裏面調用 v4l2_fh_init+v4l2_fh_add
進行添加,在 release
函數裏面調用 v4l2_fh_del+v4l2_fh_exit
進行退出。驅動可使用 container_of
來訪問本身的文件句柄結構體,以下所示:
struct my_fh { int blah; struct v4l2_fh fh; }; int my_open(struct file *file) { struct my_fh *my_fh; struct video_device *vfd; int ret; my_fh = kzalloc(sizeof(*my_fh), GFP_KERNEL); v4l2_fh_init(&my_fh->fh, vfd); file->private_data = &my_fh->fh; v4l2_fh_add(&my_fh->fh); return 0; } int my_release(struct file *file) { struct v4l2_fh *fh = file->private_data; struct my_fh *my_fh = container_of(fh, struct my_fh, fh); v4l2_fh_del(&my_fh->fh); v4l2_fh_exit(&my_fh->fh); kfree(my_fh); return 0; }
如以上代碼所示,因爲 open
函數可能會被多個應用 app 所調用,因此 fh 也會有多個,可是 file->private
永遠指向最新的一個 v4l2_fh
,經過這個 v4l2_fh
能夠找到整個 v4l2_fh
鏈表中的全部元素。一些驅動須要在第一個文件句柄打開後以及最後一個文件句柄關閉前的時候作一些其它的工做,下面兩個幫助函數能夠檢查 v4l2_fh
結構體是否只剩下一個 entry:
int v4l2_fh_is_singular(struct v4l2_fh *fh) 若是是隻有一個entry,返回1,不然返回0,若是fh爲空也返回0。 int v4l2_fh_is_singular_file(struct file *filp) 和上面差很少,可是使用 filp->private_data 這一數據源,實際上它是指向最新的一個v4l2_fh的。
V4L2 events 提供一種通用的方法來傳遞 events 到用戶空間,驅動程序必須使用 v4l2_fh
(設置 video_device
的 flags
位)纔可以實現對 V4L2 events 的支持。events 用類型和 ID 做爲區分標識,沒有使用到的 events 的ID就是0。
當用戶訂閱 event 時,用戶空間會相應地爲每一個 event 分配一個 kevent 結構體(若是 elems 參數爲0的話只有一個,不爲0就按照指定的數量分配),因此每一個 event 都有一個或多個屬於本身的 kevent 結構體,這就保證了若是驅動短期內生成了很是多的 events 也不會覆蓋到其它的同類型 events,能夠看做是分了好幾個籃子來放不一樣類型的水果。event 結構體是 v4l2_subscribed_event
結構體的最後一個成員,以數組的形式存在,而且是一個柔性數組(struct v4l2_kevent events[]
),也就是說在分配 v4l2_subscribed_event
結構體空間的時候,events 並不佔用空間,須要額外爲指定數量的 events 分配空間,kzalloc(siezof(struct v4l2_subscribed_event) + sizeof(struct v4l2_kevent) * num, GFP_KERNEL);
在使用的時候,徹底能夠按照數組的方式去對 kevent 進行尋址,很方便。
若是得到的 event 數量比 kevent 的還要多,那麼舊的 events 就會被丟棄。能夠設置結構體 v4l2_subscribed_event
的 merge、replace
回調函數(其實默認的函數就足夠用了),它們會在 event 被捕獲而且沒有更多的空間來存放 event 時被調用。在 v4l2_event.c
裏面有一個很好的關於 replace/merge
的例子,ctrls_replace()與ctrls_merge()
被做爲回調函數使用。因爲這兩個函數能夠在中斷上下文被調用,所以必須得快速執行完畢並返回。
關於events的循環是一個比較有意思的操做,入隊時:三個變量(first-下一個準備被dequeue的eventm,elems-總kevent數量,in_use-已經使用的kevent數量)
- 若elems == in_use,說明隊列成員已經用完。
- 取出第一個kevent,從available隊列中刪掉,first指向數組的下一個成員,in_use --。
- 找到上一步中(first指向數組的下一個成員),將上一步(取出第一個kevent)的changes位進行合併賦值給前者。
由於後者比前者更新,因此數值徹底能夠覆蓋前者,同時又保留了前者的變化。
- 取出第in_use + first >= elems ? in_use + first - elems : in_use + first;個數組kevent項做爲新的填充項。
- in_use ++
一些有用的函數:
int v4l2_event_subscribe(struct v4l2_fh *fh, struct v4l2_event_subscription *sub, unsigned elems, const struct v4l2_subscribed_event_ops *ops)
當用戶空間經過 ioctl 發起訂閱請求以後,video_device->ioctl_ops->vidioc_subscribe_event
須要檢查是否支持請求的 event,若是支持的話就調用上面的函數進行訂閱。通常能夠將 video 的相關 ioctl 指向內核默認的 v4l2_ctrl_subscribe_event()
函數。
int v4l2_event_unsubscribe(struct v4l2_fh *fh, struct v4l2_event_subscription *sub) 取消一個事件的訂閱,V4L2_EVENT_ALL類型能夠用於取消全部事件的訂閱。通常能夠將video的相關ioctl指向該函數。 void v4l2_event_queue(struct video_device *vdev, const struct v4l2_event *ev) 該函數用做events入隊操做(由驅動完成),驅動只須要設置type以及data成員,其他的交由V4L2來完成。 int v4l2_event_dequeue(struct v4l2_fh *fh, struct v4l2_event *event, int nonblocking) events出隊操做,發生於用戶空間的VIDIOC_DQEVENT調用,做用是從available隊列中取出一個events。
v4l2_subscribed_event_ops
參數容許驅動程序設置如下四個回調函數成員:
events 經過 poll 系統調用傳遞到用戶空間,驅動能夠將 v4l2_fh->wait
做爲 poll_wait()
的參數。子設備能夠直接經過 notify 函數向 v4l2_device
發送 events(使用V4L2_DEVICE_NOTIFY_EVENT
)。drivers/media/platform/omap3isp
給出瞭如何使用event的實例。
注意事項:
- 注意v4l2_event_subscribe的elems參數,若是爲0,則內核就默認分配爲1,不然按照指定的參數值分配。
- 最好不要使用內核默認的v4l2_subscribed_event_ops,由於它的add函數會嘗試在v4l2_ctrl裏面查找相應id的ctrl,若是
是自定義的event id的話,有可能找不到相關的ctrl項,這樣的話用戶空間的VIDIOC_SUBSCRIBE_EVENT就會返回失敗。
- 用戶空間dqevent以後沒必要關心還回的操做,由於內核會自動獲取用過的kevent,用柔性數組去管理而不是分散的鏈表。
- 子設備能夠經過v4l2_subdev_notify_event函數調用來入隊一個event並通知v4l2設備的notify回調。
- v4l2_event_queue函數會遍歷video_device上面全部的v4l2_fh,將event入隊到每個fh的列表當中。fh由用戶打開video
設備節點的時候產生,每個用戶打開video節點時都會爲其分配一個單獨的v4l2_fh。
- file->private永遠指向最新的一個v4l2_fh,經過這個v4l2_fh能夠找到整個v4l2_fh鏈表中的全部元素。
- v4l2_fh_release函數會將全部掛載該fh上面的事件所有取消訂閱。
寫到這裏,本文就算結束了,這部分會發現不少東西都是點到即撤,沒有深刻去解釋,深刻的這部分放在後面來完成,還有一個就是可能會感受裏面有不少東西看着可能知道是什麼,可是反應到實際代碼裏面,實際應用裏面就不知道是什麼了,這個時候就必須結合代碼來進行實際操做實驗纔可以確切瞭解。還有一種狀況就是可能需求比較簡單,一些特性永遠用不到,這個時候也不要緊,那就用到的時候再去翻看就好。
<center>想作的事就去作吧</center>
<center><img src="http://www.yellowmax2001.com/...;>