裝置驅動程式庫生命週期

判斷需要裝置驅動程式時,系統會將裝置驅動程式載入到驅動程式代管程序程序中。哪些元件會決定這些元件是否已載入或並非繫結程式,也就是說明驅動程式庫可繫結的裝置。繫結程式是以小型網域特定語言定義,該語言會編譯為與驅動程式庫一起發布的位元碼。

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 定義,可定義 init、繫結、create 和發布方法)。

將驅動程式庫載入至驅動程式主機程序時,系統會叫用 init(),並允許任何全域初始化。一般來說,不必採取任何行動。如果實作 init() 方法且失敗,驅動程式庫載入就會失敗。

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

系統會針對平台/系統匯流排驅動程式或 Proxy 驅動程式叫用 create()。對大多數的驅動程式而言,這個方法並非必要。

在卸載驅動程式庫之前,系統會先叫用 release(),接著該函式可能在 bind() 和其他位置建立的所有裝置都已刪除。目前永不叫用此方法。驅動程式載入後,在驅動程式主機程序的生命週期都會保持載入狀態。

裝置生命週期

在驅動程式主機程序中,裝置會以 zx_device_t 結構的樹狀結構形式呈現,而驅動程式庫無法解讀。這些屬性是使用 device_add() 建立,驅動程式庫提供 zx_protocol_device_t 結構。此結構中的函式指標定義的方法為「裝置作業」。各種結構和函式是在 device.h 中定義

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

device_add() 的副作用是將新建立的裝置加入由裝置協調者維護的全域裝置檔案系統。如果裝置尚未實作 init() 掛鉤,使用者只需在 devfs 開啟其節點,即可立即存取裝置。

系統會在 device_add() 之後叫用 init() 掛鉤。這對於需要延長初始化或探測,且在成功前不要清楚發布裝置的驅動程式 (如果驅動程式失敗,則會自動移除裝置)。驅動程式應在完成初始化後呼叫 device_init_reply()。此回覆不一定要從 init() 掛鉤呼叫。在此之前,裝置會維持隱藏狀態,且保證不會移除。

裝置也列入參考。當驅動程式庫使用 device_add() 建立裝置,以及透過裝置檔案系統從遠端程序開啟裝置時,系統會取得參考資料。

從呼叫 device_init_reply() 時,或在沒有實作 init() 掛鉤的情況下呼叫 device_add(),驅動程式主機可能會呼叫其他裝置作業。

在裝置上呼叫 device_async_remove() 時,系統會安排移除裝置及其子系。

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

叫用 unbind() 方法時,這個信號會通知驅動程式庫應開始關閉裝置,並在完成解除繫結後呼叫 device_unbind_reply()。取消繫結也是 FIDL 交易的困難之處。 FDF 不允許在呼叫 Unbind 時建立新的 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);
    }
  • 一旦移除裝置的所有用戶端,且該裝置中沒有任何子項,其拒絕次數就會到達零,並且系統會呼叫其 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)。 這兩個 API 都會發布後,回呼最後會達到零,並叫用其 release() 方法。
    wlan_phy_release(void* ctx) {
        // Release sources allocated at creation.
        ...

        // Delete the object here.
        ...
    }
  • 如果 USB 裝置現在沒有子項裝置或開啟連線,系統會呼叫其 release()