<code>Read the fucking source code!</code> --By 魯迅
<code>A picture is worth a thousand words.</code> --By 高爾基
說明:
Kernel版本:4.14
ARM64處理器,Contex-A53,雙核
使用工具:Source Insight 3.5, Visio
<code>V4L2(Video for Linux 2)</code>:Linux核心中關于視訊裝置驅動的架構,對上向應用層提供統一的接口,對下支援各類複雜硬體的靈活擴充;
<code>V4L2</code>架構,主要包括<code>v4l2-core</code>、<code>meida framework</code>、<code>videobuf2</code>等子產品,這也是本文将要展開的内容,僅提綱挈領;
開始吧。
先從應用的角度來看如何使用<code>v4l2</code>吧:

假如要進行視訊資料采集,大體的步驟如上圖左側所示:
打開裝置檔案<code>/dev/videoX</code>;
根據打開的裝置,查詢裝置能力集;
設定視訊資料的格式、參數等;
配置設定buffer,這個buffer可以是使用者态配置設定的,也可以是從核心中擷取的;
開始視訊流采集工作;
将buffer enqueue到v4l2架構,底層負責将視訊資料填充後,應用層再将buffer dequeue以便擷取資料,然後再将buffer enqueue,如此循環往複;
上圖右側是v4l2-core的大體架構,右側是對硬體的抽象,要想了解好它,可以先看一下較常見的硬體拓撲結構:
通常一個camera的模組如圖所示,通常包括Lens、Sensor、CSI接口等,其中CSI接口用于視訊資料的傳輸;
SoC的Mipi接口對接Camera,并通過I2C/SPI控制camera模組;
Camera模組中也可以包含ISP子產品,用于對圖像進行處理,有的SoC中也內建了ISP的IP,接收camera的raw資料後,進行圖像處理;
如果以上圖的硬體為例,對攝像頭的硬體該怎麼來抽象呢?沒錯,就是以<code>v4l2_device</code>和<code>v4l2_subdev</code>來進行抽象,以<code>v4l2_device</code>來代表整個輸入裝置,以<code>v4l2_subdev</code>來代表子子產品,比如<code>CSI</code>、<code>Sensor</code>等;
<code>v4l2_device</code>:對視訊裝置的整體進行抽象,可以看成是一個紐帶,将各個子裝置聯系在一起,通常它會嵌入在其他結構體中以提供<code>v4l2</code>架構的功能,比如<code>strcut isp_device</code>;
<code>v4l2_subdev</code>:對子裝置進行抽象,該結構體中包含的<code>struct v4l2_subdev_ops</code>是一個完備的操作函數集,用于對接各種不同的子裝置,比如video、audio、sensor等,同時還有一個核心的函數集<code>struct v4l2_subdev_core_ops</code>,提供更通用的功能。子裝置驅動根據裝置特點實作該函數集中的某些函數即可;
<code>video_device</code>:用于向系統注冊字元裝置節點,以便使用者空間可以進行互動,包括各類設定以及資料buffer的擷取等,在該結構體中也能看到<code>struct v4l2_ioctl_ops</code>和<code>struct vb2_queue</code>結構體字段,這些與上文中的應用層代碼編寫息息相關;
如果子裝置不需要與應用層互動,<code>struct v4l2_subdev</code>中内嵌的<code>video_device</code>也可以不向系統注冊字元裝置;
<code>video_device</code>結構體,可以内嵌在其他結構體中,以便提供使用者層互動的功能,比如<code>struct isp_video</code>;
針對圖中回調函數集,<code>v4l2-core</code>提供了一些實作,是以driver在實作時,非特殊情況下可以不用重複造輪子;
來進一步看一下内部的注冊,及調用流程吧:
在驅動實作中,驅動結構體中内嵌<code>struct video_device</code>,同時實作<code>struct v4l2_file_operations</code>結構體中的函數,最終通過<code>video_register_device</code>向提供注冊;
<code>v4l2_register_device</code>函數通過<code>cdev_add</code>向系統注冊字元裝置,并指定了<code>file_operations</code>,使用者空間調用<code>open/read/write/ioctl</code>等接口,便可回調到驅動實作中;
<code>v4l2_register_device</code>函數中,通過<code>device_register</code>向系統注冊裝置,會在<code>/sys</code>檔案系統下建立節點;
完成注冊後,使用者空間便可通過檔案描述符來進行通路,從應用層看,大部分都是通過<code>ioctl</code>接口來完成,流程如下:
使用者層的<code>ioctl</code>回調到<code>__video_do_ioctl</code>中,該函數會對系統提供的<code>struct v4l2_ioctl_info v4l2_ioctls[]</code>表進行查詢,找到對應的項後進行調用;
驅動做的工作就是填空題,實作對應的回調,在合适的時候被調用;
下一個小節,讓我們看看更複雜一點的情況。
為了更好的描述,本節以<code>omap3isp</code>為例,先看一下它的硬體構成:
CSI:camera接口,接收圖像資料,RGB/YUV/JPEG等;
CCDC:視訊處理前端,CCDC為圖像傳感器和數字視訊源提供接口,并處理圖像資料;
Preview/Resizer:視訊處理後端,Preview提供預覽功能,可針對不同類型的傳感器進行定制,Resizer提供将輸入圖像資料按所需的顯示或視訊編碼分辨率調整大小的方法;
H3A/HIST:靜态統計子產品,H3A支援AF、AWB、AE的回路控制,HIST根據輸入資料,提供各種3A算法所需的統計資料;
上述硬體子產品,可以對應到驅動結構體<code>struct isp_device</code>中的各個字段。
omap3isp的硬體子產品,支援多種資料流通路,它并不是唯一的,以RGB為例,如下圖:
Raw RGB資料進入ISP子產品後,可以在運作過程中,根據實際的需求進行通路設定;
是以,重點是:它需要動态設定路徑!
那麼,軟體該如何滿足這種需求呢?
沒錯,pipeline架構的引入可以解決這個問題。說來很巧,我曾經也實作過一個類似的架構,在閱讀media framework時有一種似曾相識的感覺,核心的思想大體一緻。
子產品之間互相獨立,通過<code>struct media_entity</code>來進行抽象,通常會将<code>struct media_entity</code>嵌入到其他結構中,以支援<code>media framework</code>功能;
子產品包含<code>struct media_pad</code>,pad可以認為是端口,與其他子產品進行聯系的媒介,針對特定子產品來說它是确定的;
pad通過<code>struct media_link</code>來建立連接配接,指定source和sink,即可将通路建立起來;
各個子產品之間最終建立一條資料流,便是一條pipeline了,同一條pipeline中的子產品,可以根據前一個子產品查找到下一個子產品,是以也可以很友善進行周遊,并做進一步的設定操作;
是以,隻需要将<code>struct media_entity</code>嵌入到特定子子產品中,最終便可以将子子產品串聯起來,構成資料流。是以,<code>omap3isp</code>的驅動中,資料流就如下圖所示:
<code>video devnode</code>代表<code>video device</code>,也就是前文中提到的導出到使用者空間的節點,用于與使用者進行控制及資料互動;
每個子產品分别有source pad和sink pad,從連接配接圖就可以看出,資料通路靈活多變;
至于資料通路選擇問題,可以在驅動初始化的時候進行連結建立,比如<code>isp_create_links</code>;
還是看一下資料結構吧:
<code>media_device</code>:與<code>v4l2_device</code>類似,也是負責将各個子子產品集中進行管理,同時在注冊的時候,會向系統注冊裝置節點,友善使用者層進行操作;
<code>media_entity</code>、<code>media_pad</code>、<code>media_link</code>等結構體的功能在上文中描述過,注意,這幾個結構體會添加到<code>media_device</code>的連結清單中,同時它們結構體的開始字段都需是<code>struct media_gobj</code>,該結構中的<code>mdev</code>将會指向它所屬的<code>media_device</code>。這種設計友善結構之間的查找;
<code>media_entity</code>中包含多個<code>media_pad</code>,同時<code>media_pad</code>又會指向它所屬的<code>media_entity</code>;
<code>media_graph</code>和<code>media_pipeline</code>是<code>media_entity</code>的集合,直覺來了解,就是由一些子產品構成的一條資料通路,由一個統一的資料結構來組織管理;
羅列一下常見的幾個接口吧,細節不表了:
将<code>media framework</code>和<code>v4l2_device</code>及<code>v4l2_subdev</code>結合起來,就可以将各個子裝置建構pipeline,完美!
架構可以分成兩個部分看:控制流+資料流,上文已經大概描述了控制流,資料流的部分就是<code>video buffer</code>了。
<code>V4L2</code>的buffer管理是通過<code>videobuf2</code>來完成的,它充當使用者空間和驅動之間的中間層,并提供low-level,子產品化的記憶體管理功能;
上圖大體包含了videobuf2的架構;
<code>vb2_queue</code>:核心的資料結構,用于描述buffer的隊列,其中<code>struct vb2_buffer *bufs[]</code>是存放buffer節點的數組,該數組中的成員代表了<code>vb2 buffer</code>,并将在<code>queued_list</code>和<code>done_list</code>兩個隊列中進行流轉;
<code>struct vb2_buf_ops</code>:buffer的操作函數集,由驅動來實作,并由架構通過<code>call_bufop</code>宏來對特定的函數進行調用;
<code>struct vb2_mem_ops</code>:記憶體buffer配置設定函數接口,buffer類型分為三種:1)虛拟位址和實體位址都分散,可以通過dma-sg來完成;2)實體位址分散,虛拟位址連續,可以通過vmalloc配置設定;3)實體位址連續,可以通過dma-contig來完成;三種類型也vb2架構中都有實作,架構可以通過<code>call_memop</code>來進行調用;
<code>struct vb2_ops</code>:vb2隊列操作函數集,由驅動來實作對應的接口,并在架構中通過<code>call_vb_qop</code>宏被調用;
本節以<code>omap3isp</code>為例進行簡要分析,感覺直接看圖就可以了:
buffer申請
buffer enqueue
buffer dequeue
stream on
行文至此,主體講完了,相信看完本文應該有個大概的輪廓了,還有一些細節未進一步描述,就此打住。
https://lwn.net/Articles/416649/
《OMAP35x Technical Reference Manual (Rev. Y).pdf》
歡迎關注公衆号,不定期分享技術文章
作者:LoyenWang
出處:https://www.cnblogs.com/LoyenWang/
公衆号:<b>LoyenWang</b>
版權:本文版權歸作者和部落格園共有
轉載:歡迎轉載,但未經作者同意,必須保留此段聲明;必須在文章中給出原文連接配接;否則必究法律責任