用於通用 FPGA 介面的 Xillybus 驅動程式¶
- 作者:
Eli Billauer,Xillybus 有限公司 (http://xillybus.com)
- 電子郵件:
eli.billauer@gmail.com 或按 Xillybus 網站上公佈的地址。
簡介¶
背景¶
FPGA(現場可程式設計門陣列)是一種邏輯硬體,可以程式設計為幾乎任何通常作為專用晶片組出現的東西:例如,顯示介面卡、網絡卡,甚至帶外設的處理器。FPGA 是硬體中的樂高積木:基於某些構建塊,您可以按照自己喜歡的方式製作自己的“玩具”。重新實現市場上已有的晶片組通常沒有意義,因此 FPGA 主要用於需要特殊功能且生產量相對較低(因此不值得開發 ASIC)的情況。
FPGA 面臨的挑戰是所有東西都以非常低的級別實現,甚至比組合語言更低。為了讓 FPGA 設計人員能夠專注於他們的特定專案,而不是一次又一次地重複造輪子,通常會使用預先設計的構建塊,即 IP 核。這些是 FPGA 庫函式的並行概念。IP 核可以實現某些數學函式、功能單元(例如 USB 介面)、整個處理器(例如 ARM)或任何可能方便的東西。把它們想象成一個構建塊,側面懸掛著電線用於連線其他塊。
FPGA 設計中一項艱鉅的任務是與成熟的作業系統(實際上是與執行它的處理器)進行通訊:實現低階匯流排協議以及與主機(暫存器、中斷、DMA 等)的更高層次的介面本身就是一個專案。當 FPGA 的功能是眾所周知的(例如影片介面卡卡或網絡卡)時,專門為專案設計 FPGA 的介面邏輯可能是有意義的。然後編寫一個特殊的驅動程式,將 FPGA 作為眾所周知的介面呈現給核心和/或使用者空間。在這種情況下,沒有理由將 FPGA 與總線上的任何裝置區別對待。
然而,常見的情況是所需的資料通訊不適合任何已知的外設功能。此外,為資料交換設計一個優雅的抽象的工作量通常被認為太大。在這些情況下,會尋求一種更快且可能不那麼優雅的解決方案:驅動程式實際上被編寫為使用者空間程式,只留下核心空間部分進行基本的資料傳輸。這仍然需要為 FPGA 設計一些介面邏輯,併為核心編寫一個簡單的臨時驅動程式。
Xillybus 概述¶
Xillybus 是一個 IP 核和一個 Linux 驅動程式。它們共同構成了一個用於 FPGA 和主機之間基本資料傳輸的套件,提供管道式資料流和直觀的使用者介面。它旨在為混合 FPGA-主機專案提供低投入的解決方案,對於這類專案,將驅動程式的專案特定部分作為使用者空間程式執行是合理的。
由於通訊要求可能因不同的 FPGA 專案而顯著不同(每個方向所需資料管道的數量及其屬性),因此沒有一個特定的邏輯塊作為 Xillybus IP 核。相反,IP 核是根據其終端使用者提供的規範進行配置和構建的。
Xillybus 向用戶呈現獨立的資料流,類似於管道或 TCP/IP 通訊。在主機側,字元裝置檔案就像任何管道檔案一樣使用。在 FPGA 側,硬體 FIFO 用於流式傳輸資料。這與透過固定大小緩衝區進行通訊的常見方法不同(儘管 Xillybus 在底層使用了這種緩衝區)。單個 IP 核上可能有超過一百個這樣的流,但也可能只有一個,具體取決於配置。
為了簡化 Xillybus IP 核的部署,它包含一個簡單的資料結構,完整定義了核心的配置。Linux 驅動程式在其初始化過程中獲取此資料結構,並相應地設定 DMA 緩衝區和字元裝置。因此,單個驅動程式可以與任何 Xillybus IP 核開箱即用。
剛才提到的資料結構不應與 PCI 的配置空間或扁平裝置樹混淆。
用法¶
使用者介面¶
在主機上,所有與 Xillybus 的介面都透過 /dev/xillybus_* 裝置檔案完成,這些檔案在驅動程式載入時自動生成。這些檔案的名稱取決於 FPGA 中載入的 IP 核(參見下面的“探測”)。要與 FPGA 通訊,開啟與您要傳送或接收資料的硬體 FIFO 對應的裝置檔案,並像使用普通管道一樣,使用普通的 write() 或 read() 呼叫。特別是,以下做法完全合理:
$ cat mydata > /dev/xillybus_thisfifo
$ cat /dev/xillybus_thatfifo > hisdata
可能在某個階段按下 CTRL-C,儘管 xillybus_* 管道能夠傳送 EOF(但可能不使用它)。
驅動程式和硬體被設計成以合理的方式作為管道工作,包括:
支援非阻塞 I/O(透過在 open() 上設定 O_NONBLOCK)。
支援 poll() 和 select()。
在負載下具有頻寬效率(使用 DMA),但也能透過自動重新整理處理傳送的小資料塊(如 TCP/IP)。
裝置檔案可以是隻讀、只寫或雙向的。雙向裝置檔案被視為兩個獨立的管道(除了在實現程式碼中共享一個“通道”結構)。
同步¶
Xillybus 管道(在 IP 核上)被配置為同步或非同步。對於同步管道,write() 僅在某些資料已提交併被 FPGA 確認後才成功返回。這會減慢批次資料傳輸,並且幾乎不可能用於需要恆定速率資料流的場景:在 write() 呼叫之間沒有資料傳輸到 FPGA,尤其是在程序失去 CPU 時。
當管道配置為非同步時,如果緩衝區中有足夠的空間來儲存任何資料,write() 就會返回。
對於 FPGA 到主機的管道,只要開啟相應的裝置檔案,非同步管道就允許從 FPGA 傳輸資料,無論資料是否已透過 read() 呼叫請求。在同步管道上,只傳輸 read() 呼叫請求的資料量。
總而言之,對於同步管道,主機和 FPGA 之間的資料僅為滿足驅動程式當前處理的 read() 或 write() 呼叫而傳輸,並且這些呼叫會等待傳輸完成才返回。
請注意,同步屬性與 read() 或 write() 完成的位元組數少於請求的可能性無關。有一個單獨的配置標誌(“allowpartial”)決定是否允許這種部分完成。
可定址管道¶
同步管道可以配置為將其流的位置暴露給 FPGA 上的使用者邏輯。這種管道在主機 API 上也是可定址的。透過此功能,可以在 FPGA 側將記憶體或暫存器介面連線到可定址流。透過定址到所需地址並根據需要呼叫 read() 或 write() 來讀寫附加記憶體中的特定地址。
內部原理¶
原始碼組織¶
Xillybus 驅動程式由一個核心模組 xillybus_core.c 和依賴於特定匯流排介面的模組(xillybus_of.c 和 xillybus_pcie.c)組成。
匯流排特定模組是核心找到合適的裝置時探測到的模組。由於 DMA 對映和同步函式本質上是匯流排相關的,並且被核心模組使用,因此在初始化時會將一個 xilly_endpoint_hardware 結構傳遞給核心模組。此結構填充了指向包裝函式的指標,這些函式在總線上執行 DMA 相關操作。
管道屬性¶
每個管道都有多個屬性,這些屬性在構建 FPGA 元件(IP 核)時設定。它們由 xillybus_core.c 中的 xilly_setupchannels() 從 IDT(定義核心配置的資料結構,參見下面的“探測”)獲取,如下所示:
is_writebuf:管道的方向。非零值表示它是 FPGA 到主機的管道(FPGA “寫入”)。
channelnum:管道在主機和 FPGA 之間通訊中的識別號。
format:底層資料寬度。參見下面的“資料粒度”。
allowpartial:非零值表示 read() 或 write()(視情況而定)可能返回的位元組數少於請求的位元組數。通常選擇非零值,以匹配標準的 UNIX 行為。
synchronous:非零值表示管道是同步的。參見上面的“同步”。
bufsize:每個 DMA 緩衝區的大小。始終是 2 的冪。
bufnum:為該管道分配的緩衝區數量。始終是 2 的冪。
exclusive_open:非零值強制關聯裝置檔案的獨佔開啟。如果裝置檔案是雙向的,並且已經只在一個方向上開啟,則另一個方向可以被開啟一次。
seekable:非零值表示管道是可定址的。參見上面的“可定址管道”。
supports_nonempty:非零值(這是典型的)表示硬體將傳送支援此管道的 select() 和 poll() 所需的訊息。
主機從不從 FPGA 讀取¶
儘管 PCI Express 通常支援熱插拔,但典型主機板並不期望卡突然消失。但由於 PCIe 卡基於可重程式設計邏輯,在主機執行時,FPGA 意外重程式設計可能導致其突然從匯流排消失。實際上,在這種情況下不會立即發生任何事情。但如果主機嘗試從對映到 PCI Express 裝置的地址讀取,這會導致某些主機板系統立即凍結,即使 PCIe 標準要求優雅恢復。
為了避免這些凍結,Xillybus 驅動程式完全避免從裝置的暫存器空間讀取。從 FPGA 到主機的所有通訊都透過 DMA 完成。特別是,中斷服務例程在被呼叫時並不遵循檢查狀態暫存器的常見做法。相反,FPGA 準備一個包含短訊息的小緩衝區,這些訊息告知主機中斷的原因。
出於統一性考慮,此機制也用於非 PCIe 匯流排。
通道、管道和訊息通道¶
呈現給使用者的每個(可能是雙向的)管道都在 FPGA 和主機之間分配一個數據通道。通道和管道之間的區別僅因為通道 0 而有必要,通道 0 用於來自 FPGA 的中斷相關訊息,並且沒有管道連線到它。
資料流傳輸¶
儘管在兩端都向使用者呈現非分段資料流,但實現依賴於為每個通道分配的一組 DMA 緩衝區。為了說明,讓我們以 FPGA 到主機方向為例:當資料流向 FPGA 中相應通道的介面時,Xillybus IP 核將其寫入其中一個 DMA 緩衝區。當緩衝區已滿時,FPGA 會通知主機(透過將 XILLYMSG_OPCODE_RELEASEBUF 訊息附加到通道 0 並在必要時傳送中斷)。主機透過使資料透過字元裝置可讀來響應。當所有資料都被讀取後,主機向 FPGA 的緩衝區控制暫存器寫入,允許緩衝區被覆蓋。兩端都存在流控制機制以防止下溢和上溢。
這不足以建立類似 TCP/IP 的流:如果資料流在 DMA 緩衝區填充之前暫時停止,直觀的預期是緩衝區中的部分資料無論如何都會到達,儘管緩衝區尚未完成。這透過在 XILLYMSG_OPCODE_RELEASEBUF 訊息中新增一個欄位來實現,FPGA 透過該欄位不僅通知提交了哪個緩衝區,還通知它包含多少資料。
但是,FPGA 只會在主機指示時才提交部分填充的緩衝區。這種情況發生在 read() 方法阻塞了 XILLY_RX_TIMEOUT jiffies(目前為 10 毫秒)之後,此時主機命令 FPGA 儘快提交 DMA 緩衝區。這種超時機制在匯流排頻寬效率(防止傳送大量部分填充的緩衝區)和資料尾部保持較低延遲之間取得了平衡。
在主機到 FPGA 方向也使用了類似的設定。然而,部分 DMA 緩衝區的處理方式有所不同。使用者可以透過發出位元組計數設定為零的 write() 呼叫,告訴驅動程式將其緩衝區中的所有資料提交給 FPGA。這類似於重新整理請求,但它不會阻塞。還有一種自動重新整理機制,它在大約最後一次 write() 呼叫後 XILLY_RX_TIMEOUT jiffies 觸發一個等效的重新整理。這使得使用者可以不必關心底層的緩衝機制,同時仍能享受到類似流的介面。
請注意,對於“synchronous”屬性不為零的管道,部分緩衝區重新整理的問題是不相關的,因為同步管道無論如何都不允許資料在 read() 和 write() 之間停留在 DMA 緩衝區中。
資料粒度¶
資料以 8、16 或 32 位寬的字形式到達或傳送到 FPGA,具體由“format”屬性配置。只要可能,當管道以不同於其自然對齊方式訪問時,驅動程式會嘗試隱藏這一點。例如,從具有 32 位粒度的管道讀取單個位元組沒有問題。向具有 16 或 32 位粒度的管道寫入單個位元組也有效,但驅動程式無法將部分完成的字傳送到 FPGA,因此最多一個字的傳輸可能會被延遲,直到它完全被使用者資料佔用。
這在某種程度上使主機到 FPGA 流的處理變得複雜,因為當緩衝區被重新整理時,它可能包含多達 3 個位元組無法在 FPGA 中形成一個字,因此無法傳送。為了防止資料丟失,這些剩餘位元組需要被移動到下一個緩衝區。xillybus_core.c 中以某種方式提及“leftovers”(剩餘物)的部分與此複雜性有關。
探測¶
如前所述,驅動程式載入時建立的管道數量及其屬性取決於 FPGA 中的 Xillybus IP 核。在驅動程式初始化期間,一個包含配置資訊的 blob,即介面描述表(IDT),從 FPGA 傳送到主機。引導過程分為三個階段:
獲取 IDT 的長度,以便為其分配緩衝區。這透過向裝置傳送 quiesce 命令來完成,因為此命令的確認包含 IDT 的緩衝區長度。
獲取 IDT 本身。
根據 IDT 建立介面。
緩衝區分配¶
為了簡化防止 PCIe 資料包非法跨越邊界的邏輯,適用以下規則:如果緩衝區小於 4kB,則它不得跨越 4kB 邊界。否則,它必須是 4kB 對齊的。xilly_setupchannels() 函式透過向核心請求完整頁面並根據需要將其劃分為 DMA 緩衝區來分配這些緩衝區。由於所有緩衝區的大小都是 2 的冪,因此可以打包任何此類緩衝區集合,最多浪費一頁記憶體。
所有緩衝區都在驅動程式載入時分配。這是必要的,因為有時會請求大型連續物理記憶體段,這些記憶體在系統剛啟動時更有可能可用。
緩衝區記憶體的分配按照它們在 IDT 中出現的順序進行。驅動程式依賴於 IDT 中管道按緩衝區大小遞減排序的規則。如果請求的緩衝區大於或等於一頁,則從核心請求所需數量的頁,並將其用於此緩衝區。如果請求的緩衝區小於一頁,則從核心請求單個頁,並且該頁被部分使用。或者,如果手頭已經有一個部分使用的頁,則將緩衝區打包到該頁中。可以證明,透過這種方式,從核心請求的所有頁(除了最後一個之外)都得到了 100% 的利用。
“非空”訊息(支援 poll)¶
為了支援“poll”方法(以及因此的 select()),FPGA 到主機方向有一個小問題:FPGA 可能已用一些資料填充了 DMA 緩衝區,但尚未提交該緩衝區。如果主機等待 FPGA 提交緩衝區,則可能出現 FPGA 端已傳送資料,但 select() 呼叫仍然會阻塞的情況,因為主機尚未收到有關此事的任何通知。這透過 FPGA 在通道從完全空變為包含一些資料時傳送 XILLYMSG_OPCODE_NONEMPTY 訊息來解決。
這些訊息僅用於支援 poll() 和 select()。IP 核可以配置為不傳送它們,以略微減少頻寬。