天天看點

Zircon裝置模型

#Zircon裝置模型

## 介紹

在Zircon中,裝置驅動程式實作為ELF共享庫(DSO),加載到裝置主機(devhost)程序中。裝置管理器(devmgr)程序,包含裝置協調器(Coordinator),用于跟蹤驅動程式和裝置,進行管理發現驅動程式,裝置主機程序的建立和方向,以及維護裝置檔案系統(devfs),這是使用者空間的服務和應用程式(受其命名空間限制)獲得裝置通路權限的機制。

裝置協調器将裝置視為單個統一樹的一部分。該樹的分支(和子分支)由裝置主機程序中的部分裝置組成。關于如何細分的決定,裝置主機中的整體樹基于用于隔離的系統政策驅動程式出于安全性或穩定性原因以及為了性能而共同驅動的原因。

注意:目前政策很簡單(每個裝置代表一個實體總線主站有能力的硬體裝置及其子裝置被放入一個單獨的devhost)。它将發展為提供更細粒度的分區。

##裝置,驅動程式和裝置主機

這是裝置樹的轉儲(為了清晰起見略微修剪)在Qemu x86-64上運作的Zircon:

```

$ dm dump

[root]

   <root> pid=1509

      [null] pid=1509 /boot/driver/builtin.so

      [zero] pid=1509 /boot/driver/builtin.so

   [misc]

      <misc> pid=1645

         [console] pid=1645 /boot/driver/console.so

         [dmctl] pid=1645 /boot/driver/dmctl.so

         [ptmx] pid=1645 /boot/driver/pty.so

         [i8042-keyboard] pid=1645 /boot/driver/pc-ps2.so

            [hid-device-001] pid=1645 /boot/driver/hid.so

         [i8042-mouse] pid=1645 /boot/driver/pc-ps2.so

            [hid-device-002] pid=1645 /boot/driver/hid.so

   [sys]

      <sys> pid=1416 /boot/driver/bus-acpi.so

         [acpi] pid=1416 /boot/driver/bus-acpi.so

         [pci] pid=1416 /boot/driver/bus-acpi.so

            [00:00:00] pid=1416 /boot/driver/bus-pci.so

            [00:01:00] pid=1416 /boot/driver/bus-pci.so

               <00:01:00> pid=2015 /boot/driver/bus-pci.proxy.so

                  [bochs_vbe] pid=2015 /boot/driver/bochs-vbe.so

                     [framebuffer] pid=2015 /boot/driver/framebuffer.so

            [00:02:00] pid=1416 /boot/driver/bus-pci.so

               <00:02:00> pid=2052 /boot/driver/bus-pci.proxy.so

                  [intel-ethernet] pid=2052 /boot/driver/intel-ethernet.so

                     [ethernet] pid=2052 /boot/driver/ethernet.so

            [00:1f:00] pid=1416 /boot/driver/bus-pci.so

            [00:1f:02] pid=1416 /boot/driver/bus-pci.so

               <00:1f:02> pid=2156 /boot/driver/bus-pci.proxy.so

                  [ahci] pid=2156 /boot/driver/ahci.so

            [00:1f:03] pid=1416 /boot/driver/bus-pci.so

```

方括号中的名稱是裝置。尖括号中的名稱是代理裝置,它在“lower”devhost中執行個體化,當正在提供程序隔離。 pid =字段表示程序對象裝置包含在其中的devhost程序的id。路徑表明哪個驅動程式實作該裝置。

上面,例如,pid 1416 devhost包含pci總線驅動程式,它具有為系統中的每個PCI裝置建立裝置。 PCI裝置00:02:00發生成為一個intel以太網接口,我們有一個驅動程式(intel-ethernet.so)。建立一個新的devhost(pid 2052),使用PCI 00:02:00的代理裝置進行設定,并且intel以太網驅動程式已加載并綁定到它。

代理裝置在裝置檔案系統中是不可見的,是以這個以太網裝置顯示為`/dev/sys/pci/00:02:00/intel-ethernet`。

##協定,接口和類

裝置可以實作協定,這些協定是子裝置使用的C ABIs以特定于裝置的方式與父裝置互動。舉例:

[PCI協定](../../system/ulib/ddk/include/ddk/protocol/pci.h),

[USB協定](../../system/ulib/ddk/include/ddk/protocol/usb.h),

[塊核心協定](../../system/ulib/ddk/include/ddk/protocol/block.h),以及

[Ethermac協定](../../system/ulib/ddk/include/ddk/protocol/ethernet.h),

協定通常是程序間的互動裝置在同一個devhost中,但在驅動程式隔離的情況下,它們可能會占用通過RPC放置到“higher”的devhost(通過代理)。

裝置可以實作接口,即[FIDL](../../../docs/development/languages/fidl/README.md)RPC協定客戶(服務,應用程式等)使用。基礎裝置接口支援POSIX樣式打開/關閉/讀/寫IO。通過支援接口基本裝置接口中的`message()`操作。由于遺留原因,一個`ioctl()`操作目前存在,但它已被棄用并且在被删除的過程。

在許多情況下,協定通過利用接口的通用實作來使驅動更簡單。例如,“block”驅動程式實作公共塊接口,并綁定到實作塊核心協定的裝置,“以太網”驅動程式對以太網接口和Ethermac做同樣的事情協定。一些協定,例如這裡引用的兩個協定,使用共享記憶體,和非rpc信令比更高效,更低的延遲和更高的吞吐量否則就會實作。

類表示裝置實作接口或協定的承諾。裝置存在于拓撲路徑下的裝置檔案系統中,如

`/sys/pci/00:02:00/intel-ethernet`。如果他們是特定的類,他們也會出現作為`/dev/class/CLASSNAME/...`下的别名。 `intel-ethernet`驅動程式實作Ethermac接口,是以它也顯示在`/dev/class/ethermac/000`。名字類目錄中的内容是唯一的但沒有意義,并且是按需配置設定的。

注意:目前類目錄中的名稱是3位十進制數,但它們可能會在未來改變形式。客戶不應該假設有任何類别名的具體含義。

##裝置驅動程式生命周期

裝置驅動程式在需要時會加載到devhost程序中。決定它們是否被加載的是綁定程式,它是驅動程式可以綁定到哪個裝置的描述。綁定程式是使用[`ddk/binding.h`]中的宏定義(../../system/ulib/ddk/include/ddk/binding.h)

英特爾以太網驅動程式的示例綁定程式:

```

ZIRCON_DRIVER_BEGIN(intel_ethernet,intel_ethernet_driver_ops,“Zircon”,“0.1”,9)

    BI_ABORT_IF(NE,BIND_PROTOCOL,ZX_PROTOCOL_PCI),

    BI_ABORT_IF(NE,BIND_PCI_VID,0x8086),

    BI_MATCH_IF(EQ,BIND_PCI_DID,0x100E),// Qemu

    BI_MATCH_IF(EQ,BIND_PCI_DID,0x15A3),// Broadwell

    BI_MATCH_IF(EQ,BIND_PCI_DID,0x1570),// Skylake

    BI_MATCH_IF(EQ,BIND_PCI_DID,0x1533),// I210 standalone

    BI_MATCH_IF(EQ,BIND_PCI_DID,0x15b7),// Skull Canyon NUC

    BI_MATCH_IF(EQ,BIND_PCI_DID,0x15b8),// I219

    BI_MATCH_IF(EQ,BIND_PCI_DID,0x15d8),// Kaby Lake NUC

ZIRCON_DRIVER_END(intel_ethernet)

```

ZIRCON_DRIVER_BEGIN和_END宏包含必要的編譯器指令将綁定程式放入ELF NOTE部分,允許對其進行檢查通過裝置協調器,無需将驅動程式完全加載到其程序中。

_BEGIN宏的第二個參數是`zx_driver_ops_t`結構指針(已定義)通過`[ddk/driver.h](../../system/ulib/ddk/include/ddk/driver.h)`來定義init,bind,create和release方法。

當驅動程式加載到裝置主機程序并允許任何全局初始化時,調用`init()`。通常什麼都不需要。如果`init()`方法實作了但失敗,驅動程式加載将失敗。

調用`bind()`以向驅動程式提供要綁定的裝置。該裝置已比對驅動程式釋出的綁定程式。如果`bind()`方法成功,驅動程式**必須**建立一個新裝置并将其添加到通過`bind()`方法傳入裝置的子裝置。有關更多資訊,請參閱裝置生命周期。

為平台/系統總線驅動程式或代理驅動程式調用`create()`。為了絕大多數驅動,這個方法不是必需的。

在驅動程式釋放之前,在`bind()`或其他地方建立的所有裝置被銷毀之後,`release()`被調用。目前這種方法是**從不**調用。驅動程式一旦加載,将在裝置主機程序的生命周期内保持加載狀态。

##裝置生命周期

在裝置主機程序中,裝置作為`zx_device_t`結構樹存在,這對驅動來說是不透明的。這些是使用`device_add()`建立的驅動程式提供`zx_protocol_device_t`結構。由此結構中的函數指針定義的方法是“[device ops](device-ops.md)”。該各種結構和函數在[`device.h`]中定義(../../system/ulib/ddk/include/ddk/device.h)

`device_add()`函數建立一個新裝置,将其作為子裝置添加到提供的父裝置。那個父裝置**必須**是通過到裝置驅動程式的`bind()`方法傳遞的裝置,或者已經有的由同一裝置驅動程式建立的另一個裝置。

`device_add()`的副作用是添加新建立的裝置到裝置協調器維護的全局裝置檔案系統。如果使用** DEVICE_ADD_INVISIBLE **标志建立裝置,它将無法通路通過在devfs中打開它的節點,直到調用`device_make_visible()`。這個對于必須進行擴充初始化或探測的驅動程式非常有用,不想明顯地釋出他們的裝置,直到成功(并且如果失敗則安靜地删除它們)。

裝置引用會計數。當驅動程式使用`device_add()`建立一個時,然後它在該裝置上儲存一個引用,直到它最終調用`device_remove()`。如果裝置由遠端程序通過裝置檔案系統打開,則會在那裡獲得一個引用。删除裝置的父項時,其“unbind()”方法被調用。這向驅動發出信号,告知它應該開始關閉裝置并通過調用`device_remove()`删除它建立的所有子裝置。

由于`unbind()`方法調用時,子裝置可能有工作正在處理,有可能剛在child身上調用`device_remove()`的父裝置可以繼續接收那個child的裝置方法調用或協定方法調用。建議在除去child之前,父裝置應該安排這些方法傳回錯誤,這樣在child移除完成之前從child過來的調用不要再開始工作或引起意外的互動。從沒有** DEVICE_ADD_INVISIBLE **标志的`device_add()`調用或者在不可見的裝置上的`device_make_visible()調用起,其他裝置ops可以由裝置主機調用。

隻有在建立驅動程式裝置上調用`device_remove()`後,才會調用`release()`方法,該裝置的所有打開執行個體都已關閉,該裝置的所有child都已被移除并釋放。這個是驅動程式銷毀或釋放裝置關聯的任何資源的最後機會。在`release()`傳回之後引用該裝置的`zx_device_t`是無效的。在此之後,調用任何裝置方法或從父裝置獲得協定方法是非法的并且将是可能導緻崩潰。

###一個例子(Tear-Down Sequence)

解釋`unbind()`和`release()`在teardown過程中如何工作,下面是USB WLAN驅動程式通常如何處理它的示例。簡而言之,`unbind()`調用序列是自上而下的,而`release()`序列是自下而上的。

請注意,這隻是一個例子。這可能與真正的WLAN驅動程式完全不比對。

假設WLAN裝置作為USB裝置插入,并且PHY接口已經在USB裝置下建立。除PHY接口外,還有2個MAC接口已在PHY接口下建立。

```

            + ------------ +

            |  USB Device  |

            + ------------ +

                  |

            + ------------ +

            | WLAN PHY     | .unbind()

            + ------------ + .release()

              |          |

    + ------------ + + ------------ +

    | WLAN MAC 0 | | WLAN MAC 1     | .unbind()

    + ------------ + + ------------ + .release()

```

現在,我們拔掉這個USB WLAN裝置。

* USB XHCI檢測到,并調用`device_remove(usb_device)`删除。

*由于移除了父裝置,是以調用WLAN PHY的`unbind()`。  在這個`unbind()`中,它會删除它通過`device_add()`建立的接口:

```

    wlan_phy_unbind(void * ctx){

        //停止中斷或任何阻止傳入請求的内容。

        ...

        device_remove(wlan_phy);

    }

```

*當删除wlan_phy時,将在其所有子節點(wlan_mac_0,wlan_mac_1)上調用unbind()。

```

    wlan_mac_unbind(void * ctx){

        //停止接受新請求,并通知用戶端此裝置處于脫機狀态(通常隻是

        //将ZX_ERR_IO_NOT_PRESENT傳回給unbind之後發生的任何請求)。

        ...

        device_remove(iface_mac_X);

    }

```

*一旦裝置的所有用戶端都被删除,并且該裝置沒有子裝置,它的引用計數将達到零,并将調用其release()方法。

*調用WLAN MAC 0和1的`release()`。

```

    wlan_mac_release(void * ctx){

        //釋放在建立時配置設定的源。

        ...

        //在這裡删除對象。

        ...

    }

```

* wlan_phy沒有打開的連接配接,但仍然有子裝置(wlan_mac_0和wlan_mac_1)。

  一旦他們都被'release()`',它的引用計數最終達到零并且它的release()方法被調用。

```

    wlan_phy_release(void * ctx){

        //釋放在建立時配置設定的源。

        ...

        //在這裡删除對象。

        ...

    }

```

繼續閱讀