裝置驅動程式庫生命週期

系統會在判斷需要裝置驅動程式時,將其載入驅動程式代管程序程序。決定是否載入的因素是繫結程式,也就是驅動程式庫可繫結至的裝置。綁定程式會使用小型網域專屬語言定義,並編譯為與驅動程式庫一起發布的位元碼。

Intel 乙太網路驅動程式的繫結程式範例:

fuchsia.device.protocol == fuchsia.pci.protocol.PCI_DEVICE;
fuchsia.pci.vendor == fuchsia.pci.vendor.INTEL;
accept fuchsia.pci.device {
    0x100E, // Qemu
    0x15A3, // Broadwell
    0x1570, // Skylake
    0x1533, // I210 standalone
    0x15b7, // Skull Canyon NUC
    0x15b8, // I219
    0x15d8, // Kaby Lake NUC
}

繫結編譯器會接收繫結程式,並輸出定義巨集 ZIRCON_DRIVER 的 C 標頭檔案。ZIRCON_DRIVER 巨集包含必要的編譯器指示,可將繫結程式置入 ELF NOTE 區段,讓裝置協調器進行檢查,而不需要將驅動程式庫完全載入其程序。

ZIRCON_DRIVER 的第二個參數是 zx_driver_ops_t 結構體指標 (由 lib/ddk/driver.h 定義,該方法定義初始化、繫結、建立和釋放方法)。

當驅動程式庫載入至 Driver Host 程序時,系統會叫用 init(),並允許任何全域初始化。通常不需要。如果實作 init() 方法失敗,驅動程式庫載入作業也會失敗。

系統會叫用 bind(),為驅動程式庫提供要繫結的裝置。裝置必須符合驅動程式庫發布的繫結規則。如果 bind() 方法成功,驅動程式庫必須建立新裝置,並將其新增為傳遞至 bind() 方法的裝置子項。詳情請參閱「裝置生命週期」。

create() 會針對平台/系統匯流排驅動程式或 Proxy 驅動程式叫用。對於絕大多數的駕駛人來說,這並不是必要的做法。

在驅動程式庫卸載前,系統會先呼叫 release(),此時 bind() 和其他位置中所建立的所有裝置都已遭到銷毀。目前這個方法「從未」叫用。載入的驅動程式會在驅動程式主機程序的生命週期內持續載入。

裝置生命週期

在驅動程式主機程序中,裝置會以 zx_device_t 結構的樹狀結構存在,對驅動程式庫而言是不可見的。這些項目是使用 device_add() 建立,驅動程式庫會為其提供 zx_protocol_device_t 結構。這個結構體中函式指標定義的方法就是「裝置作業」。各種結構和函式均在 device.h 中定義

device_add() 函式會建立新的裝置,並將其新增為提供的父項裝置的子項。該父項裝置必須是傳入裝置驅動程式庫 bind() 方法的裝置,或是由相同裝置驅動程式庫建立的其他裝置。

device_add() 的副作用是,新建的裝置會新增至裝置協調器維護的全球裝置檔案系統。如果裝置未實作 init() 掛鉤,則可立即存取裝置。

系統會在 device_add() 之後叫用 init() 鉤子。這對必須進行擴充初始化或探測的驅動程式很有幫助,因為在成功前,驅動程式不想公開裝置 (在失敗時,則悄悄移除裝置)。驅動程式應在完成初始化後呼叫 device_init_reply()。這項回覆不一定需要從 init() 鉤子中呼叫。裝置會保持隱藏狀態,且保證在這個時間點前不會移除。

裝置會計算參照計數。當驅動程式庫使用 device_add() 建立裝置,以及當裝置透過裝置檔案系統由遠端程序開啟時,系統會取得參照。

從呼叫 device_init_reply()device_add() 的當下開始,如果沒有實作的 init() 鉤子,驅動程式主機可能會呼叫其他裝置作業。

在裝置上呼叫 device_async_remove() 時,這會排定移除裝置及其子項的作業。

移除裝置的過程分為四個部分:執行裝置的 unbind() 鉤子、從裝置檔案系統移除裝置、放棄由 device_add() 取得的參照,以及執行裝置的 release() 鉤子。

叫用 unbind() 方法時,這會向驅動程式庫發出信號,指出應開始關閉裝置,並在解除繫結完成後呼叫 device_unbind_reply()。解除繫結也會做為 FIDL 交易的硬性障礙。在呼叫 Unbind 時,FDF 不會允許建立任何新的 FIDL 交易或連線。如果驅動程式會處理 FIDL 訊息,則須負責關閉或回覆未完成的交易。這是選用掛鉤。如果未實作,系統會視為立即呼叫 device_unbind_reply()。呼叫 device_unbind_reply 時,所有 FIDL 連線都會終止。

由於子裝置在呼叫其 unbind() 方法時可能會有工作正在進行,因此父裝置 (已完成解除綁定) 可能會繼續代表該子裝置接收裝置方法呼叫或通訊協定方法呼叫。建議在完成解綁前,父項裝置應安排這些方法傳回錯誤,以便在子項移除完成前,從子項發出的呼叫不會啟動更多工作或造成意外的互動。

只有在建立驅動程式庫完成解綁、該裝置的所有已開啟例項已關閉,且該裝置的所有子項已解綁並釋放後,才會呼叫 release() 方法。這是驅動程式庫最後一次有機會銷毀或釋放與裝置相關聯的任何資源。release() 傳回後,您無法參照該裝置的 zx_device_t。在這個時間點之後,針對從父項裝置取得的通訊協定,呼叫任何裝置方法或通訊協定方法都是非法行為,且可能導致程式異常終止。

拆解序列範例

為說明 unbind()release() 在拆解程序期間的運作方式,以下提供 USB WLAN 驅動程式庫通常如何處理的範例。簡而言之,unbind() 呼叫序列是自上而下,而 release() 序列則是自下而上。

請注意,這只是範例。這可能與實際的 WLAN 驅動程式庫所執行的操作不符。

假設 WLAN 裝置已插入做為 USB 裝置,且已在 USB 裝置下建立 PHY 介面。除了 PHY 介面外,系統已在 PHY 介面下建立 2 個 MAC 介面。

            +------------+
            | USB Device | .unbind()
            +------------+ .release()
                  |
            +------------+
            |  WLAN PHY  | .unbind()
            +------------+ .release()
              |        |
    +------------+  +------------+
    | WLAN MAC 0 |  | WLAN MAC 1 | .unbind()
    +------------+  +------------+ .release()

接著,我們將拔除這個 USB WLAN 裝置。

  • USB XHCI 會偵測移除作業,並呼叫 device_async_remove(usb_device)

  • 這會導致 USB 裝置的 unbind() 呼叫。解除綁定完成後,就會呼叫 device_unbind_reply()

    usb_device_unbind(void* ctx) {
        // Stop interrupt or anything to prevent incoming requests.
        ...

        device_unbind_reply(usb_dev);
    }
  • USB 裝置解除繫結後,系統會呼叫 WLAN PHY 的 unbind()。解除綁定完成後,就會呼叫 device_unbind_reply()
    wlan_phy_unbind(void* ctx) {
        // Stop interrupt or anything to prevent incoming requests.
        ...

        device_unbind_reply(wlan_phy);
    }
  • 當 wlan_phy 完成解除綁定後,系統會對其所有子項 (wlan_mac_0、wlan_mac_1) 呼叫 unbind()。
    wlan_mac_unbind(void* ctx) {
        // Stop accepting new requests, and notify clients that this device is offline (often just
        // by returning a ZX_ERR_IO_NOT_PRESENT to any requests that happen after unbind).
        ...

        device_unbind_reply(iface_mac_X);
    }
  • 一旦裝置的所有用戶端都已移除,且該裝置沒有子項,其 refcount 就會達到零,並呼叫其 release() 方法。

  • 呼叫 WLAN MAC 0 和 1 的 release()

    wlan_mac_release(void* ctx) {
        // Release sources allocated at creation.
        ...

        // Delete the object here.
        ...
    }
  • wlan_phy 沒有任何開放連線,但仍有子裝置 (wlan_mac_0 和 wlan_mac_1)。一旦兩者都已釋出,其 refcount 最終會達到零,並叫用其 release() 方法。
    wlan_phy_release(void* ctx) {
        // Release sources allocated at creation.
        ...

        // Delete the object here.
        ...
    }
  • 當 USB 裝置沒有子裝置或開放連線時,就會呼叫其 release()