Fuchsia 驅動程式庫開發 (DFv1)

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

目錄結構

您可以在整個來源樹狀結構中,找到位於各區域 driver 子目錄下的驅動程式,如原始碼版面配置文件所述。大部分的 Fuchsia 驅動程式位於 //src/devices/ 下。這些類別會根據實作的通訊協定分組。驅動程式通訊協定定義於 ddk/include/lib/ddk/protodefs.h。舉例來說,USB 乙太網路驅動程式庫會放在 //src/connectivity/ethernet/drivers/ 中,而非 //src/devices/usb/drivers/,因為它會實作乙太網路通訊協定。不過,實作 USB 堆疊的驅動程式會位於 //src/devices/usb/drivers/,因為這些驅動程式會實作 USB 通訊協定。

在驅動程式庫的 BUILD.gn 中,應有 fuchsia_driver_component 目標。如要讓驅動程式庫顯示在 /boot/driver 下方,請在 //boards 下方的相關板卡檔案中,將驅動程式納入 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. meta/<NAME>.bind 中新增適當的繫結規則。
  6. meta/<NAME>-info.json 中新增驅動程式庫資訊。檔案必須包含至少一個與 //build/drivers/areas.txt 所列區域相符的 short_descriptionareas
  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 建構目標。這應該會宣告與繫結檔案中「using」陳述式相對應的依附元件。

driver_bind_rules("bind") {
    rules = "meta/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_str_prop_t pci_device_props[] = {
    ddk::MakeStrProperty(bind_fuchsia::PCI_VID,
        static_cast<uint32_t>(device.info.vendor_id)),
    ddk::MakeStrProperty(bind_fuchsia::PCI_DID,
        static_cast<uint32_t>(device.info.device_id)),
    ddk::MakeStrProperty(bind_fuchsia::PCI_CLASS,
        static_cast<uint32_t>(device.info.base_class)),
    ddk::MakeStrProperty(bind_fuchsia::PCI_SUBCLASS,
        static_cast<uint32_t>(device.info.sub_class)),
    ddk::MakeStrProperty(bind_fuchsia::PCI_INTERFACE,
        static_cast<uint32_t>(device.info.program_interface)),
    ddk::MakeStrProperty(bind_fuchsia::PCI_REVISION,
        static_cast<uint32_t>(device.info.revision_id)),
    ddk::MakeStrProperty(bind_fuchsia::PCI_TOPO, pci_bind_topo),
};

目前,繫結變數和巨集是在 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 中的 device_add()ddk::Device::DdkAdd() 發布新裝置,並傳回 `ZX_OK。

  2. 驅動程式判斷即使繫結規則相符,裝置仍無法支援 (可能是因為檢查硬體版本位元或其他項目),並傳回錯誤。

  3. 驅動程式需要在裝置就緒或確保可支援裝置之前,執行進一步的初始化作業,因此會發布實作 init() 鉤子的隱藏裝置,並啟動執行緒以維持運作,同時將 ZX_OK 傳回至 bind()。該執行緒最終會呼叫 C 中的 device_init_reply(),或 DDKTL C++ 包裝函式庫中的 ddk::InitTxn::Reply()。我們會在收到回覆前,保證不會移除裝置。如果狀態為 ZX_OK,表示裝置已成功初始化,且應顯示裝置;如果狀態為錯誤,則表示應移除裝置。

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

裝置新增後,系統會將其顯示,並提供給用戶端程序,以及相容的驅動程式繫結。

Banjo 通訊協定

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

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

駕駛操作

驅動程式通常會透過服務子項驅動程式或其他程序的用戶端要求來運作。它會直接與硬體通訊 (例如透過 MMIO) 或與其父項裝置通訊 (例如排入 USB 交易),以便滿足這些要求。

來自驅動程式代管程序外部程序的外部用戶端要求,會由子項驅動程式 (通常位於同一個程序中) 滿足,而由與裝置類別相對應的 banjo 通訊協定滿足。駕駛員對驅動程式庫的要求應使用 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 訊息。

其他程序中的驅動程式

如果驅動程式庫需要在個別程序中與驅動程式庫通訊,而非定義通訊協定操作,則必須代管傳出目錄,類似元件,應代管子驅動程式庫程式在繫結時會存取的所有 FIDL 通訊協定。

通訊協定作業與 FIDL 訊息

通訊協定作業會定義裝置的程序內 API。FIDL 訊息會定義用於通訊的 API。如果函式是供同一個程序中的其他驅動程式呼叫,請定義通訊協定操作。驅動程式應在其父項上呼叫通訊協定操作,以便使用這些函式。

隔離裝置

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

駕駛人權利

雖然驅動程式會在使用者空間程序中執行,但其權限會比一般程序受到更多限制。驅動程式不得存取檔案系統,包括 devfs。這表示驅動程式庫無法與任意裝置互動。如果驅動程式需要執行這項操作,建議改為編寫服務元件。舉例來說,虛擬主控台是由 virtcon 元件實作。

zx_vmo_create_contiguous()zx_interrupt_create 等特殊權限作業需要根資源句柄。除了系統驅動程式庫 (x86 系統上的 ACPI 和 ARM 系統上的 platform) 以外,其他驅動程式都無法使用此句柄。裝置應要求其父項裝置為其執行這類操作。如果父項驅動程式庫的通訊協定無法解決這個用途,請與該驅動程式的作者聯絡。

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

進階主題和提示

初始化時間過長

如果裝置需要很長的時間才能完成初始化,該怎麼辦?在上述討論 null_bind() 函式時,我們指出成功傳回的結果會告知驅動程式管理器,驅動程式庫現在已與裝置建立關聯。我們無法在繫結函式中花費太多時間,因為我們基本上只需要初始化裝置、發布裝置,然後完成作業即可。

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

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

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

您可以實作裝置 init() 鉤子,將裝置發布為「隱藏」裝置。init() 鉤子會在裝置透過 device_add() 新增後執行,可用於安全存取裝置狀態,並產生 worker 執行緒。裝置會保持隱藏狀態,且在呼叫 device_init_reply() 之前,系統保證不會移除裝置。呼叫 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() 排定移除裝置及其所有子項