撰寫基本驅動程式庫
程式碼結構應遵循 fx create 驅動程式庫 goldens 範本。
Meta 目錄
每個驅動程式庫目錄都必須有 meta 子目錄,其中包含下列檔案:
- 定義驅動程式庫
bind規則的bind檔案 - 元件資訊清單
驅動程式原始碼
Rust 驅動程式庫原始碼應位於 src 子目錄中名為 lib.rs 的檔案。Rust 原始碼應使用 fdf_component 程式庫定義驅動程式庫。
模組結構應使用平面檔模組到達網頁 (例如 src/foo.rs),而非資料夾中的 mod.rs (例如 src/foo/mod.rs)。子模組應放在同層資料夾中 (例如 src/foo/bar.rs)。
驅動程式必須定義為實作 fdf_component::Driver 特徵的結構體。實作的 start 方法會接收 DriverContext 結構體,其中包含連線至驅動程式庫並提供通訊協定和記錄所需的結構體。此外,驅動程式庫程式碼必須使用 driver_register!() 巨集,向驅動程式架構註冊驅動程式庫。
元件資訊清單
由於 Rust 驅動程式必須是非同步,因此元件資訊清單不應將主要調度器宣告為 allow_sync_calls。
初始化驅動程式庫
所有初始化邏輯都應放在 start 方法中,且必須將 DriverContext 的節點控制代碼儲存至 Driver 結構體。初始化邏輯包含:
- 使用 DriverContext::take_node 取得並儲存驅動程式庫的 Node 物件,直到關機為止。捨棄
Node物件會導致驅動程式庫關閉,無論是否為刻意。在大多數情況下,這不會用到,但仍應儲存在Driver物件中 (做為_node,以關閉未使用的警告)。這樣一來,驅動程式庫關閉時,系統就會正確捨棄Node。 - 正在擷取及設定所有驅動程式庫資源。
- 建立服務連線。
- 將驅動程式庫自己的服務新增至傳出目錄。
- 新增子節點 (在資源設定和提供自有服務後執行)。
- 從隔離區釋出 BTI。
關閉驅動程式庫
在大多數情況下,Driver stop() 實作項目都是空白。除非驅動程式庫程式功能明確需要清除作業,例如執行正常硬體關機或取消固定 DMA,否則請勿新增其他項目。
如果驅動程式庫控制關閉時間,則應將 Node 物件儲存為可捨棄的物件,例如在 Option<Node> 中,或視需要放在 mutex 後方。捨棄 Node 物件會啟動驅動程式庫關機程序。
建構檔案
所有 Rust 驅動程式都必須使用 GN 進行建構程序,且必須包含下列目標:
fuchsia_driver_bind_bytecodefuchsia_rust_driverfuchsia_driver_component
建議所有 Rust 驅動程式庫目標在 GN 定義中加入下列設定,強制執行嚴格的 Lint:
configs += [
"//build/config/rust/lints:clippy_warn_all",
]
與駕駛溝通
驅動程式會透過 FIDL 服務與上層驅動程式通訊。
提供服務
請參考 examples/drivers/transport/driver/rust_next/parent 和 examples/drivers/transport/zircon/rust_next/parent/,瞭解如何使用 rust_next 繫結。Fuchsia 建議將其用於一般 FIDL 傳輸服務,並要求用於驅動程式庫程式傳輸服務 (因為舊繫結不支援)。
提供 FIDL 服務的驅動程式必須將 FIDL 服務實作為特徵。初始化期間,它需要在傳出目錄中新增 ServiceOffer,然後使用 serve_outgoing() 函式將目錄提供給 DriverContext。目錄提供服務後,請使用 fuchsia_async Scope 程式庫產生工作,在 ServiceFs 事件迴圈中執行。
CML 檔案必須在能力中指定服務,並從 self 公開服務。
使用服務
請參考 examples/drivers/transport/driver/rust_next/child 和 examples/drivers/transport/zircon/rust_next/child/,瞭解如何使用 rust_next 繫結。Fuchsia 建議將其用於一般 FIDL 傳輸服務,並要求用於驅動程式庫程式傳輸服務 (因為舊繫結不支援)。
如要使用服務,駕駛人應將服務納入 bind 規則,並在 CML 的 uses 區段中指定服務。連線至服務時,請使用服務能力,而非通訊協定。
新增孩子
新增子節點的主要原因,是驅動程式庫需要為其他驅動程式庫提供服務或資源。請避免無故新增子項。
除非必要,否則應在 start() 函式中新增子節點,做為初始化邏輯的一部分。
子節點應使用 Rust 包裝函式 Node::add_child 新增。所有子節點都應為無主節點,除非是用於支援 devfs,但 Rust 驅動程式不支援此功能。因此不應有擁有的子項。
記錄
所有記錄都應使用標準 Rust Logging API。記錄 FIDL 錯誤等失敗情形時,請按照 Fuchsia 記錄指南使用 warning 或 error 記錄層級。
Zircon 資源
針對 Vmo 和 Interrupt 等資源,使用 Zircon 核心繫結。
中斷
請避免將中斷儲存為原始 zx::Handle 物件,因為這需要在等待作業期間使用 unsafe 程式碼。請改用 Zircon 中斷繫結 (sdk/rust/zx/src/interrupt.rs),將裝置結構定義為 K: zx::InterruptKind (例如 Device<K>) 的泛型,並使用該結構將控制代碼包裝及儲存在 zx::Interrupt<K> 物件中。
使用 fasync::OnInterrupt 建立中斷串流,並在非同步工作中處理,而不是使用 std::thread::spawn 產生專屬執行緒,並在 irq.wait() 上封鎖。
DMA
MMIO 控制碼必須使用 VmoMapping::map() (sdk/lib/驅動程式庫/mmio/rust/) 對應至 MmioRegion 物件。存取 MMIO 暫存器時,驅動程式絕不能對原始整數執行手動位元運算 (例如 val |= 1 << 5;)。請改用 mmio::register! 和 mmio::register_block! 巨集
檢查來自驅動程式庫外部的長度、偏移、索引和 ID 範圍,然後再寫入暫存器、做為 DMA 傳輸大小或 VMO 索引,並確認用戶端 ID 是由該用戶端註冊 (VMO 註冊)。mmio::register! setter 會自動截斷超出範圍的值。
原子 (Ordering::Release) 會排序 CPU 之間的記憶體存取,而非裝置的存取,且 MmioRegion 儲存作業不包含屏障:請在告知裝置的儲存作業之前,明確排序 DMA 描述元寫入作業。
在轉移完成、失敗或取消時,以及裝置停止運作後關機時,請呼叫 Pmt::unpin();如果無法確認,請將其釘選。請勿在 Drop 中取消固定,因為該方法無法得知裝置是否已停止。如果未取消固定就捨棄 zx::Pmt,系統會隔離頁面,直到 zx_bti_release_quarantine() 為止。
時鐘
時鐘是透過 fuchsia.hardware.clock FIDL 服務控制。駕駛必須在所有依賴的時鐘上呼叫 Enable(),並在不再需要時鐘信號時呼叫 Disable()。驅動程式必須先啟用時鐘,才能呼叫 Disable()。
非同步程式碼
請勿使用 .detach() 卸載工作。請改為在驅動程式庫的 start 方法中初始化 fuchsia_async::Scope,並用來產生並行工作。駕駛人必須保留 Scope 的擁有權。
優先使用 fuchsia_sync Crate 中的 Mutex 和 RwLock,而非 std::sync。
測試
確認建構目標包含必要的驅動程式庫測試。
單元測試
您可以在一般單元測試中測試大部分的驅動程式程式碼,也應該這麼做。如要「單元測試」某個項目,同時納入驅動程式庫啟動和關閉作業,可以使用 fdf_component::testing::TestHarness 啟動驅動程式並與其互動。
如果驅動程式庫宣告的 output_name 為 my_driver,則驅動程式的 GN 目標會是 my_driver_test。如需範例,請參閱 //examples/drivers 中任何 Rust 範例驅動程式的 GN 規則。
整合測試
使用 DriverTestRealm 程式庫編寫整合測試,就像使用 C++ 驅動程式庫一樣。
一般測試建議
測試使用 MmioRegion 的驅動程式時:
- 建議使用實際 VMO:如果是簡單的用途,請使用實際 VMO 備份記憶體區域,不要模擬
read/write方法。 - 進階用法:在複雜情境中模擬
read/write是合理的做法,建議使用MockMemoryOps或類似項目。 - 使用 VMO 插入:在測試中,建立
zx::Vmo,使用VmoMapping::map()對應,並將產生的MmioRegion傳遞給驅動程式庫。