C++ 驅動程式庫評分標準

撰寫基本驅動程式庫

程式碼結構應遵循 fx 驅動程式庫黃金範本。

Meta 目錄

每個驅動程式庫目錄都必須有 meta 子目錄,其中包含下列檔案:

驅動程式原始碼

使用 Driver Component 程式庫 (sdk/lib/driver/component/) 編寫驅動程式。驅動程式應從標頭 <lib/driver/component/cpp/driver_base2.h> 中定義的 fdf::DriverBase2 類別繼承。

驅動程式提供 FUCHSIA_DRIVER_EXPORT2 巨集,用於匯出標頭 <lib/driver/component/cpp/driver_export2.h> 中定義的驅動程式庫符號。這個巨集應位於 .cc 檔案中。

初始化驅動程式庫

請勿在驅動程式庫的建構函式中加入任何邏輯。您必須覆寫 fdf::DriverBase2 提供的其中一個 Start() 方法,實作驅動程式的初始化邏輯,即使初始化邏輯為空白也一樣,而非使用建構函式。請確保只使用單一 Start() 變化版本。

初始化邏輯包含:

  • 擷取及設定所有驅動程式庫資源。
  • 建立服務連線。
  • 將驅動程式庫自己的服務新增至傳出目錄。
  • 新增子節點 (在資源設定完成並提供專屬服務後執行)。
  • 使用 zx_bti_release_quarantine() 從隔離區釋出 BTI。

關閉驅動程式庫

驅動程式的解構函式應不含任何邏輯。在大多數情況下,不需要覆寫 DriverBase2::Stop() 並提供實作項目。除非驅動程式功能有特定需求 (例如執行硬體正常關機或取消固定 DMA),否則請勿實作 DriverBase2::Stop()。函式參數中的 fdf::StopCompleter 必須在函式結尾呼叫。

建構檔案

所有新驅動程式都必須使用 Bazel 進行建構程序,且必須包含下列目標:

  • fuchsia_driver_bind_bytecode
  • fuchsia_cc_driver
  • fuchsia_driver_component

與司機通訊

驅動程式會透過 FIDL 服務與父項驅動程式通訊。

提供服務

提供 FIDL 服務的驅動程式必須維護 fidl::ServerBindingGroup (使用 Zircon 傳輸時) 或 fdf::ServerBindingGroup (用於驅動程式庫傳輸)。除非驅動程式庫必須限制為單一用戶端連線,否則請避免使用 fidl::ServerBinding 或 fdf::ServerBinding。

在建立任何子節點之前,必須先將伺服器繫結新增至 Start() 函式中驅動程式的傳出目錄。此外,CML 檔案必須在能力中指定服務,並從 self 公開服務。

使用服務

如要使用服務,驅動程式應將其納入 bind 規則,並在 CML 的 uses 區段中指定。

新增孩子

新增子節點的主要原因是驅動程式庫需要為其他驅動程式庫提供服務或資源。避免無故新增子項。除非驅動程式庫需要動態新增子節點,否則應在驅動程式的 Start() 函式中,將子節點新增為初始化邏輯的一部分。

子節點應使用 DriverBase2 的 AddChild() 或 add_child 輔助程式庫 (sdk/lib/driver/node/cpp/add_child.h) 新增。只有在子節點需要使用不同的記錄器時,才應使用輔助程式庫。

除非用於支援已淘汰的 devfs,否則所有子節點都應為未擁有。如果子節點為自有,則驅動程式庫必須儲存從輔助函式收到的 fuchsia_driver_framework::Node 用戶端。

新增子節點時,請明確定義子節點的名稱,而非使用 DriverBase2 的 name()。使用 node_offers 輔助程式庫 (sdk/lib/driver/component/cpp/node_offers.h) 建立優惠。使用 node_properties 程式庫 (sdk/lib/driver/component/cpp/node_properties.h) 建立屬性。節點屬性鍵和值應使用產生的繫結程式庫程式碼繫結。

記錄

如要記錄,驅動程式應使用格式型記錄 API,例如 fdf::info() (位於驅動程式記錄器程式庫 sdk/lib/driver/logging/cpp/logger.h 中)。如要記錄自訂型別,請實作 std::format。

記錄 FIDL 錯誤等失敗事件時,請遵循 Fuchsia 記錄指南,使用 warning 或 error 記錄層級。

錯誤處理

從可能失敗的函式傳回 zx::result<T>,而非 void 或 zx_status_t 加上輸出參數,讓呼叫端可以處理錯誤。針對您自己的錯誤類型使用 fit::result<E, T>。標示簽章必須傳回 zx_status_t (C ABI、舊版介面) 的函式 [[nodiscard]]。

請勿在失敗後回報成功。使用 take_error() 或 zx::make_result(status) 將錯誤傳遞至接受錯誤的完成者 (例如 fdf::StartCompleter)。如果完成者無法回報錯誤 (fdf_power::ResumeCompleter),請讓驅動程式庫狀態顯示失敗。如果故意在失敗後繼續執行,請記錄或註解任何位置。

執行緒和同步處理

新增鎖定前,請先瞭解哪個調度工具會執行程式碼。如果狀態僅從一個同步調度器存取 (例如 Start() 的預設調度器),則不需要互斥鎖。從未同步的調度器、多個調度器或驅動程式庫執行階段未管理的執行緒 (專屬中斷執行緒 std::thread) 存取的狀態。請在宣告狀態的位置記錄這項資訊,或查看 fdf::Dispatcher::GetCurrent()。

呼叫任何可重新進入驅動程式庫或等待相同鎖定的項目時,請勿持有鎖定:更新狀態、釋放鎖定,然後進行呼叫。 請註解任何刻意在呼叫期間保留的鎖定。請勿在每次呼叫時釋放並重新取得鎖定,或新增會取得鎖定並傳回副本的 Getter;這兩種做法都會導致 TOCTOU 競爭。

Zircon 資源

中斷

中斷應在初始化期間擷取並儲存在驅動程式庫中。中斷應由 async::Irq 物件 (sdk/lib/async/include/lib/async/cpp/irq.h) 包裝。

請使用 IrqMethod 物件處理中斷觸發條件。處理中斷觸發程序後,應使用 ack() 確認 Irq 物件,以便重新啟動中斷程序,並再次觸發。

DMA

對於記憶體作業,驅動程式應使用 MmioBuffer,位於 sdk/lib/driver/mmio/cpp/mmio-buffer.h。這個程式庫提供原始 mmio_block_t 物件的包裝函式,用於讀取和寫入。

為確保安全存取暫存器位元欄位,建議使用 hwreg/bitfields 程式庫。這個程式庫位於 zircon/system/ulib/hwreg/include/hwreg/bitfields.h。

hwreg setter 不會將值限制在欄位寬度內:在偵錯版本中,它們會 ZX_DEBUG_ASSERT,在發布子版本中,則會覆寫相鄰欄位。在暫存器、DMA 傳輸大小或緩衝區索引中使用長度、偏移、索引和計數之前,請先檢查驅動程式庫外部 (FIDL 用戶端、匯流排主機、中繼資料、韌體) 的長度、偏移、索引和計數,以及從中計算的值。值進入驅動程式庫時,請傳回 ZX_ERR_INVALID_ARGS,或在通訊協定允許最大值時進行箝位;計算界限,確保不會溢位;確認用戶端提供的 ID 已由該用戶端註冊 (VMO 註冊)。如果註解已指出範圍保證,則可取代檢查;如要瞭解何時應改用斷言,請參閱「前置條件檢查」。

驅動程式應控管 BTI,並停止初始化期間可能正在進行的任何 DMA。完成後,應呼叫 zx_bti_release_quarantine(),告知 BTI 已重新取得硬體控制權。如果驅動程式要與硬體共用 DMA,則必須事先固定 BTI。硬體存取記憶體完畢後,驅動程式庫會負責取消釘選。在任何情況下,都不應在解構函式中取消固定 BTI。

Atomics (std::memory_order_release) 會排序 CPU 之間的記憶體存取,而非裝置的存取。在通知裝置 DMA 描述元 (門鈴暫存器寫入、擁有權位元、索引更新) 前,請先瞭解緩衝區是否已快取,並在通知時註解描述元寫入的排序方式。障礙會排序存取權;快取作業會讓裝置看到資料;兩者不會互相取代。請勿為了以防萬一而新增屏障:請為每個明確屏障加上註解,說明其排序的兩個存取作業。

在傳輸完成、失敗或取消時,以及關機時 (但僅限裝置停止後),取消固定每個 zx::pmt;如果無法確認 (例如停用逾時),請保持固定。如果銷毀 zx::pmt 時未呼叫 unpin(),系統會隔離網頁,直到 zx_bti_release_quarantine() 為止。

時鐘

時鐘是透過 fuchsia.hardware.clock FIDL 服務控制。驅動程式必須在所有依賴的時鐘上呼叫 Enable(),並在不再需要時鐘訊號時呼叫 Disable()。驅動程式必須先啟用時鐘,才能呼叫 Disable()。

測試

確認建構目標包含必要的驅動程式庫測試。

單元測試

單元測試應使用 gtests (third_party/googletest/src/googletest/include/gtest/gtest.h) 編寫。

測試整個驅動程式庫

如要測試整個驅動程式庫,應使用驅動程式測試程式庫 (sdk/lib/driver/testing/cpp/driver_test.h) 的 ForegroundDriverTest 或 BackgroundDriverTest 包裝驅動程式庫。單元測試應在測試初始化後立即呼叫 StartDriver(),並在 Teardown() 函式中呼叫 StopDriver()。驅動程式庫所需的所有服務和資源,都應在自訂驅動程式庫測試的 Environment 類別中初始化及提供。

如果驅動程式庫需要平台裝置服務,測試應例項化 FakePlatformDevice (sdk/lib/driver/fake-platform-device/) DriverTestEnvironment 並提供該服務。同樣地,如果驅動程式庫需要 FIDL 服務,則應在 DriverTestEnvironment 中例項化並提供該服務的測試實作。

測試驅動程式庫的部分

如果只隔離驅動程式庫程式邏輯的子集進行測試,測試可能需要特定的驅動程式庫環境元件。

評估使用驅動程式記錄器程式庫 (sdk/lib/driver/logging/cpp/logger.h) 的邏輯時,測試必須例項化並維護 fdf_testing::ScopedGlobalLogger 例項 (sdk/lib/driver/testing/cpp/scoped_global_logger.h)。

此外,如果邏輯需要驅動程式庫調度器,測試就必須設定並保留 fdf_testing::DriverRuntime 物件 (sdk/lib/driver/testing/cpp/driver_runtime.h)。

假/模擬程式庫

如果測試需要假 FIDL 服務或資源,應盡可能使用 sdk/lib/driver/ 提供的假和模擬程式庫。

常見的 FIDL 服務可使用下列程式庫:

下列程式庫適用於常見的內部 Zircon 物件、記憶體區域或驅動程式庫執行階段資源:

整合測試

整合測試應使用驅動程式測試領域 (sdk/lib/driver_test_realm/) 架構。首先,測試必須建立與 DriverTestRealm 服務的連線,在 RealmArgs 中執行必要設定,然後使用引數呼叫 Start()。