系統判斷需要裝置驅動程式時,會將驅動程式載入驅動程式代管程序程序。載入與否取決於繫結程式,該程式會說明驅動程式庫可繫結的裝置。繫結程式是使用小型網域專屬語言定義,編譯成位元碼後,會隨附於驅動程式庫。
以下是 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 和 release 方法)。
驅動程式庫載入至 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() hook。如果驅動程式必須執行擴充初始化或探查作業,且不希望在作業成功前發布裝置 (如果作業失敗,則會悄悄移除裝置),這項功能就非常實用。驅動程式應在完成初始化後呼叫 device_init_reply()。這個回覆不一定需要從 init() hook 呼叫。裝置會維持隱藏狀態,且保證不會在此之前移除。
裝置會進行參照計數。當驅動程式庫使用 device_add() 建立裝置,以及遠端程序透過裝置檔案系統開啟裝置時,就會取得參照。
從呼叫 device_init_reply() 的那一刻起,或呼叫 device_add() 但未實作 init() 勾點時,驅動程式主機可能會呼叫其他裝置作業。
在裝置上呼叫 device_async_remove() 時,系統會排定移除該裝置及其子項目的時間。
移除裝置的程序分為四個部分:執行裝置的 unbind() hook、從裝置檔案系統中移除裝置、捨棄 device_add() 取得的參照,以及執行裝置的 release() hook。
叫用 unbind() 方法時,會向驅動程式庫發出信號,表示應開始關閉裝置,並在完成取消繫結後呼叫 device_unbind_reply()。取消繫結也會做為 FIDL 交易的硬性障礙。
呼叫 Unbind 時,FDF 不允許建立任何新的 FIDL 交易或連線。如果處理 FIDL 訊息,駕駛人須負責關閉或回覆未完成的交易。這是選用 Hook。如果未實作,系統會視為立即呼叫 device_unbind_reply()。呼叫 device_unbind_reply 時,所有 FIDL 連線都會終止。
只有在建立驅動程式庫完成取消繫結、該裝置的所有開啟例項都已關閉,且該裝置的所有子項都已取消繫結並釋出後,才會呼叫 release() 方法。這是驅動程式庫最後一次有機會銷毀或釋放與裝置相關聯的任何資源。release() 退貨後,該裝置的zx_device_t即失效。在此時間點之後,針對從父項裝置取得的通訊協定呼叫任何裝置方法或通訊協定方法,都是違法的行為,且很可能會導致當機。
拆除順序範例
為說明拆除程序期間 unbind() 和 release() 的運作方式,以下提供 USB WLAN 驅動程式庫通常的處理方式範例。簡而言之,unbind() 和 release() 的呼叫順序是從下往上。
請注意,這只是一個範例。這可能與實際 WLAN 驅動程式庫的運作方式不符。
假設 WLAN 裝置已插入 USB 裝置,且 PHY 介面已在 USB 裝置下建立。除了 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)。系統會對所有葉子後代 (wlan_mac_0、wlan_mac_1) 呼叫 unbind()。
wlan_mac_unbind(void* ctx) {
// Cancel any outstanding requests.
...
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 的
unbind()。解除繫結完成後,系統會呼叫device_unbind_reply()。
wlan_phy_unbind(void* ctx) {
// Stop interrupt and cancel any outstanding work.
...
device_unbind_reply(wlan_phy);
}
- 解除繫結常式完成後,系統會呼叫 WLAN PHY 的
release()。
wlan_phy_release(void* ctx) {
// Release sources allocated at creation.
...
// Delete the object here.
...
}
- 這會導致呼叫 USB 裝置的
unbind()。 解除繫結完成後,系統會呼叫device_unbind_reply()。
usb_device_unbind(void* ctx) {
// Stop interrupt cancel any outstanding work.
...
device_unbind_reply(usb_dev);
}
- 最後,系統會呼叫 USB 裝置的
release()。