Fuchsia 驅動程式庫開發 (DFv1)

Fuchsia 驅動程式是共用程式庫,會在使用者空間中的驅動程式代管程序程序中動態載入。驅動程式庫的載入程序是由驅動程式管理器控管。如要進一步瞭解驅動程式庫主機、驅動程式管理器、驅動程式庫和裝置生命週期,請參閱裝置型號

目錄結構

該驅動程式可能會在原始碼樹狀結構的 driver 子目錄中找到,如原始碼版面配置文件所述。大多數的 Fuchsia 驅動程式位於 //src/devices/ 下。這些元件會按照實作的通訊協定分組。驅動程式通訊協定會定義於 ddk/include/lib/ddk/protodefs.h。舉例來說,USB 乙太網路驅動程式庫會導入 //src/connectivity/ethernet/drivers/,而不是 //src/devices/usb/drivers/,因為這項通訊協定會實作乙太網路通訊協定。不過,實作 USB 堆疊的驅動程式會實作 USB 通訊協定,因此位於 //src/devices/usb/drivers/ 中。

在驅動程式庫的 BUILD.gn 中,應有 fuchsia_driver_component 目標。為了讓驅動程式庫顯示在 /boot/driver 下方,應將其列在 //板下方的相關主面板檔案中的 board_bootfs_labels 清單底下。若要讓程式碼顯示在 /system/driver 中,您必須將僅供 driver_package 建構目標的系統套件加入系統套件,而後者應由 //boards 下方的相關主面板參照。驅動程式管理器會先在 /boot/driver 中尋找,再為可載入的驅動程式搜尋 /system/driver/

建立新的驅動程式庫

使用建立工具可自動建立新的驅動程式庫。只要執行下列指令即可:

fx create driver --path <PATH> --lang cpp

這項操作會建立含有空白驅動程式庫的目錄 <PATH>,其中 <PATH> 的最後一個區段是驅動程式庫名稱和 GN 目標名稱。執行這個指令後,請按照下列步驟操作:

  1. 請在正確的位置納入 fuchsia_driver_componentdriver_package 建構目標,以便將驅動程式納入系統。
  2. 如果是已封裝的驅動程式,driver_package 建構目標則應新增至 //boards 中的相關主面板檔案或 //vendor/<foo>/boards 中,並新增至 xx_package_labels GN 引數。
  3. 如果是驅動程式,請將 fuchsia_driver_component 建構目標新增至 //boards//vendor/<foo>/boards 中的相關主面板檔案,並新增至 board_bootfs_labels GN 引數。
  4. <PATH>:tests 建構目標中加入 tests 建構目標,以便將測試納入 CQ。
  5. <NAME>.bind 中新增適當的繫結規則。
  6. <NAME>-info.json 中新增驅動程式庫資訊。檔案必須包含 short_descriptionareas,且至少符合 //build/drivers/areas.txt 所列的其中一個區域。
  7. 撰寫驅動程式庫的功能。

宣告驅動程式庫

驅動程式庫至少應包含驅動程式庫宣告,並實作 bind() 驅動程式運算。

當驅動程式管理器成功找到與裝置相符的驅動程式庫時,驅動程式就會載入並繫結至裝置。驅動程式會透過繫結規則宣告與裝置相容的裝置,請將這些規則和驅動程式庫一起放在 .bind 檔案中。繫結編譯器會編譯這些規則,並在 C 標頭檔案中建立包含這些規則的驅動程式庫宣告巨集。下列繫結規則會宣告 AHCI 驅動程式

using fuchsia.pci;
using fuchsia.pci.massstorage;

fuchsia.BIND_PROTOCOL == fuchsia.pci.BIND_PROTOCOL.DEVICE;
fuchsia.BIND_PCI_CLASS == fuchsia.pci.BIND_PCI_CLASS.MASS_STORAGE;
fuchsia.BIND_PCI_SUBCLASS == fuchsia.pci.massstorage.BIND_PCI_SUBCLASS_SATA;
fuchsia.BIND_PCI_INTERFACE == 0x01;
fuchsia.BIND_COMPOSITE == 1;

這些繫結規則指出,驅動程式庫繫結至裝置的 BIND_PROTOCOL 屬性與 pci 命名空間和指定 PCI 類別/子類別/介面的 DEVICE 相符。第 行中的 pci 命名空間是從 fucnsia.pci 程式庫匯入。詳情請參閱繫結說明文件

如要產生包含這些繫結規則的驅動程式庫宣告巨集,應有對應的 bind_rules 建構目標。這樣做應宣告對應繫結檔案中「使用」陳述式的依附元件。

driver_bind_rules("bind") {
    rules = "ahci.bind"
    bind_output = "ahci.bindbc"
    deps = [
        "//src/devices/bind/fuchsia.pci",
        "//src/devices/bind/fuchsia.pci.massstorage",
    ]
}

驅動程式現在可以納入產生的標頭,並使用下列巨集自行宣告。"zircon" 是供應商 ID,"0.1" 是驅動程式庫版本。

#include <lib/ddk/binding_driver.h>
...
ZIRCON_DRIVER(ahci, ahci_driver_ops, "zircon", "0.1");

PCI 驅動程式會發布具有下列屬性的相符裝置:

zx_device_prop_t device_props[] = {
    {BIND_PROTOCOL, 0, ZX_PROTOCOL_PCI},
    {BIND_PCI_VID, 0, info.vendor_id},
    {BIND_PCI_DID, 0, info.device_id},
    {BIND_PCI_CLASS, 0, info.base_class},
    {BIND_PCI_SUBCLASS, 0, info.sub_class},
    {BIND_PCI_INTERFACE, 0, info.program_interface},
    {BIND_PCI_REVISION, 0, info.revision_id},
    {BIND_PCI_BDF_ADDR, 0, BIND_PCI_BDF_PACK(info.bus_id, info.dev_id,
                                             info.func_id)},
};

目前,繫結變數和巨集是在 lib/ddk/binding.h 中定義。在不久之後,所有節點屬性都會由繫結程式庫 (例如上方匯入的 fuchsia.pci 程式庫) 定義。導入新的裝置類別時,您可能需要將新的節點屬性導入繫結標頭和繫結程式庫

節點屬性為 32 位元值。如果變數值需要超過 32 位元的值,請將值拆分成多個 32 位元變數。例如 ACPI HID 值,長度為 8 個字元 (64 位元)。分割為 BIND_ACPI_HID_0_3BIND_ACPI_HID_4_7。完成繫結程式庫的遷移作業後,您就能使用其他資料類型,例如字串、較大的數字和布林值。

只要在 bind_rules 建構規則中指定 disable_autobind = true,即可停用自動繫結行為。在這種情況下,可以使用 fuchsia.device.Controller/Bind FIDL 呼叫將驅動程式庫繫結至裝置。

驅動程式繫結

當驅動程式與裝置配對時,系統會呼叫驅動程式的 bind() 函式。一般來說,驅動程式庫會初始化裝置所需的所有資料結構,並初始化這個函式中的硬體。這個函式不應在這個函式中執行任何耗時的工作或區塊,因為該作業是透過驅動程式代管程序的 RPC 執行緒叫用,同時也無法服務其他要求。而是應產生新的執行緒以執行冗長的工作。

驅動程式不應假設 bind() 中的硬體狀態。您可能需要重設硬體,或確保硬體處於已知狀態。由於系統會重新輪動驅動程式庫主機,從驅動程式庫當機時復原,因此叫用 bind() 時,硬體可能會處於不明狀態。

bind() 通常有四個結果:

  1. 驅動程式會判斷裝置受支援,且不需要執行任何複雜的操作,因此請在 DDKTL C++ 包裝函式程式庫中,在 C 或 ddk::Device::DdkAdd() 中使用 device_add() 發布新裝置,並傳回 `ZX_OK。

  2. 驅動程式即使與繫結規則相符,系統依然無法支援裝置 (原因可能是檢查硬體版本位元或錯誤),並傳回錯誤。

  3. 在裝置準備就緒之前,驅動程式需要進一步初始化,或是確定裝置可以支援,因此會發布實作 init() 掛鉤的隱形裝置,並取下執行緒以保持運作,同時將 ZX_OK 傳回 bind()。該執行緒最終會在 DDKTL C++ 包裝函式程式庫中呼叫 C 或 ddk::InitTxn::Reply() 中的 device_init_reply()。在收到回覆之前,我們保證不會移除裝置。狀態會指出 ZX_OK 是否能夠成功初始化裝置,且應顯示裝置;或是顯示錯誤表示應移除裝置。

  4. 驅動程式代表具有 0..n 子項的公車或控制器,其子項可能會動態顯示或消失。在這種情況下,您應該立即發布代表公車或控制器的裝置,然後以動態方式發布子項 (該下游驅動程式會繫結至該匯流排上的硬體)。例如:AHCI/SATA、USB 等

系統新增及顯示裝置後,即可供用戶端程序使用,並由相容的驅動程式進行繫結。

Banjo 通訊協定

驅動程式會為裝置提供一組裝置作業和選用的通訊協定作業。裝置作業會實作裝置生命週期方法,以及由其他使用者空間應用程式和服務呼叫的裝置外部介面。通訊協定作業會實作裝置的處理中通訊協定,由載入同一驅動程式代管程序的其他驅動程式呼叫。

您可以在 device_add_args_t 中為裝置傳遞一組通訊協定作業。如果裝置支援多個通訊協定,請實作 get_protocol() 裝置運算。每部裝置只能有一個通訊協定 ID。通訊協定 ID 對應至裝置在 devfs 中發布的類別。

驅動程式作業

驅動程式通常透過服務來自子項驅動程式或其他程序的用戶端要求來運作。應用程式會藉由直接與硬體通訊 (例如透過 MMIO) 通訊,或與父項裝置進行通訊 (例如將 USB 交易排入佇列),以完成這些要求。

驅動程式代管程序外部程序傳出的外部用戶端要求是由子項驅動程式執行,一般和相同程序相同。駕駛人要求驅動程式庫要求應使用 Banjo 通訊協定,而非裝置作業。

裝置可以在父項裝置上呼叫 device_get_protocol(),藉此取得父項支援的通訊協定。

裝置中斷

中斷物件是一種核心物件類型,實作裝置中斷。驅動程式會在裝置通訊協定方法中要求裝置的控制代碼,從父項裝置中斷。傳回的控點將繫結至裝置的適當中斷,如父項驅動程式庫的定義。舉例來說,PCI 通訊協定會為 PCI 子項實作 map_interrupt()。驅動程式應產生執行緒,以等待中斷控點。

核心會根據幹擾是邊緣觸發還是層級觸發,視情況自動處理遮蓋及取消遮蓋中斷情形。對於層級觸發的硬體中斷,zx_interrupt_wait() 會在傳回前遮蓋中斷,並在下次再次呼叫時將中斷情形設為卸載。在邊緣觸發的中斷情形中,幹擾設定會保持未遮蓋狀態。

中斷執行緒不應執行任何長時間執行的工作。對於執行冗長工作的驅動程式,請使用工作站執行緒。

您可以使用 zx_interrupt_trigger() 在運算單元 ZX_INTERRUPT_SLOT_USER 上發出中斷控制代碼,以便從 zx_interrupt_wait() 傳回。這是在驅動程式庫清理期間,關閉中斷執行緒的必要動作。

FIDL 訊息

非驅動程式庫程序

每個裝置類別的訊息都是以 FIDL 語言定義。每部裝置都會實作零或多個 FIDL 通訊協定,並透過每個用戶端在單一管道上進行多工處理。驅動程式有機會透過 message() 掛鉤解讀 FIDL 訊息。只有 devfs 的非驅動程式庫元件可存取這些元件。

其他程序中的驅動程式

如果驅動程式庫需要透過獨立程序與驅動程式庫通訊,而不是定義通訊協定運算,就必須改為代管傳出目錄 (與元件類似),且應代管子項驅動程式庫程式在繫結上存取的所有 FIDL 通訊協定。

通訊協定作業與 FIDL 訊息

通訊協定作業會定義裝置的處理中 API。FIDL 訊息會定義與程序外通訊的 API。請定義通訊協定運算,以供相同程序中的其他驅動程式呼叫。驅動程式應在其父項上呼叫通訊協定運算,以使用這些函式。

隔離裝置

使用 DEVICE_ADD_MUST_ISOLATE 新增的裝置會產生新的驅動程式代管程序。裝置必須具有代管 FIDL 通訊協定的傳出目錄。繫結至裝置的驅動程式會載入新的驅動程式代管程序,並允許連結從父項驅動程式庫提供的傳出目錄匯出的 FIDL 通訊協定。

駕照

雖然驅動程式是在使用者空間程序中執行,但其權利組合比一般程序受到更多限制。驅動程式不允許驅動程式存取檔案系統 (包括 devf)。也就是說,驅動程式庫無法與任意裝置互動。如果驅動程式需要執行此作業,請考慮改為編寫服務元件。舉例來說,虛擬主控台是由 virtcon 元件實作。

特殊權限作業 (例如 zx_vmo_create_contiguous()zx_interrupt_create) 需要根資源控制代碼。此控制代碼不適用於系統驅動程式庫 (x86 系統上的 ACPI,以及 ARM 系統上的平台) 以外的驅動程式。裝置應要求父項執行這類作業。如果父項驅動程式的通訊協定不符合這個使用案例,請與父項驅動程式的作者聯絡。

同樣地,驅動程式庫也不得要求任意 MMIO 範圍、中斷或 GPIO。PCI 和平台等公車驅動程式只會傳回與子裝置相關聯的資源。

進階主題與訣竅

初始化作業需要很長的時間

如果裝置需要很長的時間才能初始化,該怎麼辦?在討論上述 null_bind() 函式時,我們曾表示成功返回驅動程式管理器,說明驅動程式庫現已與裝置建立關聯。我們不能花太多時間在繫結函式中,基本上就是要初始化、發布裝置並完成。

但裝置可能需要執行長時間的初始化作業,例如:

  • 列舉硬體點數
  • 載入韌體
  • 議定通訊協定

這可能需要很長的時間才能完成

只要實作裝置 init() 掛鉤,即可將裝置發布為「隱藏」。裝置透過 device_add() 新增後,系統會執行 init() 掛鉤,並可用於安全存取裝置狀態及產生工作站執行緒。裝置會保持隱藏狀態,而且保證在呼叫 device_init_reply() 前不會移除裝置,因為這可以透過任何執行緒完成。這符合繫結函式的要求,但任何人都無法使用您的裝置 (因為還沒有人知道,因為您的裝置不會顯示)。現在裝置可透過背景執行緒執行長時間作業。

當裝置準備好為用戶端要求提供服務時,請呼叫 device_init_reply(),這會導致裝置出現在路徑名稱空間中。

省電

您的裝置可以使用兩則呼叫 (suspend()resume()),以便支援電力或其他資源儲存功能。

兩者都會使用裝置內容指標和標記引數,但標記引數僅適用於暫停案例。

檢舉 意義
DEVICE_SUSPEND_FLAG_REBOOT 驅動程式應在準備重新啟動或關機時自行關機。
DEVICE_SUSPEND_FLAG_REBOOT_BOOTLOADER ?
DEVICE_SUSPEND_FLAG_REBOOT_RECOVERY ?
DEVICE_SUSPEND_FLAG_POWEROFF 駕駛應在準備關機前自行關機
DEVICE_SUSPEND_FLAG_MEXEC 驅動程式應在進行軟重新啟動準備時自行關機。
DEVICE_SUSPEND_FLAG_SUSPEND_RAM 驅動程式應排列,以便從 RAM 重新啟動

參考資料:支援函式

本節列出駕駛人可用的支援功能。

存取子函式

傳送至驅動程式通訊協定函式第一個引數的結構定義區塊是不透明的資料結構。也就是說,如要存取資料元素,您必須呼叫存取子函式:

函式 目的
device_get_name() 擷取裝置名稱

管理功能

以下函式可用於管理裝置:

函式 目的
device_add() 將裝置新增至家長
device_async_remove() 安排移除裝置及其所有子項