使用 C++ DDK 範本資料庫

在本節中,我們將查看 C++ DDK 範本庫,或簡稱「DDKTL」。這是一組 C++ 範本類別,透過提供組合確保類型安全並執行基本功能,藉此簡化編寫驅動程式庫的工作。

如果你不熟悉混合型組合,請參閱維基百科的相關文章: * mixins 和 * CRTP — 以及「好奇的週期性範本模式」

我們將討論的混合函數定義於 //src/lib/ddktl/include/ddktl/device.h 中。

我們提供的混合函式如下:

Mixin 課 函式 目的
ddk::GetProtocolable DdkGetProtocol() 擷取通訊協定
ddk::Initializable DdkInit() DdkAdd() 之後呼叫,藉此安全地完成裝置初始化作業
ddk::Unbindable DdkUnbind() 正在移除這部裝置時呼叫
ddk::Suspendable DdkSuspend() 將裝置停權
ddk::Resumable DdkResume() 恢復裝置
ddk::PerformanceTunable DdkSetPerformanceState() 將 Pod 中
ddk::AutoSuspendable DdkConfigureAutoSuspend() 設定驅動程式庫是否能自動將裝置暫停
ddk::Rxrpcable DdkRxrpc() 公車裝置的遠端訊息

為裝置定義類別時,您必須納入適當的混合函式,才能指定裝置支援哪些函式。例如 (僅為了文件而新增的行號):

[01] using DeviceType = ddk::Device<MyDevice,
[02]                                ddk::Initializable,   // safely initialize after **DdkAdd()**
[03]                                ddk::Unbindable>;     // safely clean up before **DdkRelease()**

這項操作會建立 DeviceType 的捷徑。ddk::Device 範本類別採用一或多個引數,第一個引數是基礎類別 (此處為 MyDevice)。其他範本引數是組合函式,用於定義實作的 FDF 裝置成員函式。

定義完畢後,我們就可以宣告裝置類別 (MyDevice) 為繼承自 DeviceType

[05] class MyDevice : public DeviceType {
[06]   public:
[07]     explicit MyDevice(zx_device_t* parent)
[08]       : DeviceType(parent) {}
[09]
[10]     zx_status_t Bind() {
[11]         // Any other setup required by MyDevice.
[12]         // The device_add_args_t will be filled out by the base class.
[13]         return DdkAdd("my-device-name");
[14]     }
[15]
[16]     // Methods required by the ddk mixins
[17]     void DdkInit(ddk::InitTxn txn);
[18]     void DdkUnbind(ddk::UnbindTxn txn);
[19]     void DdkRelease();
[20] };

由於 DeviceType 類別包含 Mixins (第 [02 行 .. 03]InitializableUnbindable),因此我們必須在類別中提供相應的函式實作 (行 [17 .. 18])。

所有 DDKTL 類別都必須提供發布函式 (此處的 [19] 行提供 DdkRelease()),因此我們並未在 DeviceType 的混合定義中指定這一點。

請注意,當您回覆 InitTxn (透過 DdkInit() 提供) 後,就「無法」安全使用裝置執行個體,其他執行緒可能會呼叫 DdkUnbind() (通常會呼叫 DdkRelease()),釋出驅動程式庫的裝置內容。這就構成「釋放後使用」的違規行為。 對於未實作 DdkInit() 的裝置,系統會在呼叫 DdkAdd() 之後套用此設定。

記得之前的章節,您的裝置必須向驅動程式管理器註冊才能使用。運作方式如下:

[26] zx_status_t my_bind(zx_device_t* device,
[27]                     void** cookie) {
[28]     auto dev = std::make_unique<MyDevice>(device);
[29]     auto status = dev->Bind();
[30]     if (status == ZX_OK) {
[31]         // driver manager is now in charge of the memory for dev
[32]         dev.release();
[33]     }
[34]     return status;
[35] }

在這裡,my_bind() 會建立 MyDevice 的執行個體、呼叫 Bind() 處理常式,然後傳回狀態。

Bind() (上述 class MyDevice 宣告行中的 [12]) 會執行需要的任何設定,然後使用裝置名稱呼叫 DdkAdd()

由於裝置為 Initializable,因此驅動程式管理器接著會使用 InitTxn 呼叫實作的 DdkInit()。在裝置回覆 InitTxn 之前,裝置將不會顯示,並且無法解除繫結。這個回覆可以透過任何執行緒完成,不需要先從 DdkInit() 傳回。

回應 InitTxn 後,裝置就會顯示在裝置檔案系統中。

舉例來說,//src/devices/block/drivers/zxcrypt 目錄中有一般的裝置宣告 (device.h):

[01] class Device;
[02] using DeviceType = ddk::Device<Device,
[03]                                ddk::GetProtocolable,
[04]                                ddk::Unbindable>;
...
[05] class Device final : public DeviceType,
[06]                      public ddk::BlockImplProtocol<Device, ddk::base_protocol>,
[07]                      public ddk::BlockPartitionProtocol<Device>,
[08]                      public ddk::BlockVolumeProtocol<Device> {
[09] public:
...
[10]     // ddk::Device methods; see ddktl/device.h
[11]     zx_status_t DdkGetProtocol(uint32_t proto_id, void* out);
[12]     void DdkUnbind(ddk::UnbindTxn txn);
[13]     void DdkRelease();
...

[01 .. 05] 行會使用基礎類別 Device 及其 Mixins、GetProtocolableUnbindable 宣告捷徑 DeviceType

這裡有個有趣的是 [06] 行:我們不僅繼承 DeviceType,還來自第 [07 行的其他類別。09]

[11 .. 15] 行提供三個選用混合函式的原型,以及必要的 DdkRelease() 成員函式。

以下是 zxcrypt 裝置的 DdkGetProtocol 實作範例 (來自 device.cc):

zx_status_t Device::DdkGetProtocol(uint32_t proto_id, void* out) {
    auto* proto = static_cast<ddk::AnyProtocol*>(out);
    proto->ctx = this;
    switch (proto_id) {
    case ZX_PROTOCOL_BLOCK_IMPL:
        proto->ops = &block_impl_protocol_ops_;
        return ZX_OK;
    case ZX_PROTOCOL_BLOCK_PARTITION:
        proto->ops = &block_partition_protocol_ops_;
        return ZX_OK;
    case ZX_PROTOCOL_BLOCK_VOLUME:
        proto->ops = &block_volume_protocol_ops_;
        return ZX_OK;
    default:
        return ZX_ERR_NOT_SUPPORTED;
    }
}

如同驅動程式庫所見

一起來看看驅動程式庫如何使用 DDKTL。

我們會使用 USB XHCI 驅動程式庫執行這組程式碼範例。您可以在這裡://src/devices/usb/drivers/xhci/usb-xhci.cpp

驅動程式具有驅動程式庫宣告 (通常位於來源檔案底部),如下所示:

ZIRCON_DRIVER(driver_name, driver_ops, "zircon", "0.1");

ZIRCON_ DriveR() 巨集的第二個參數是 zx_driver_ops_t 結構。在 C++ 版本中,我們使用 lambda 函式來協助初始化:

namespace usb_xhci {
...
static zx_driver_ops_t driver_ops = [](){
    zx_driver_ops_t ops = {};
    ops.version = DRIVER_OPS_VERSION;
    ops.bind = UsbXhci::Create;
    return ops;
}();

} // namespace usb_xhci

ZIRCON_DRIVER(usb_xhci, usb_xhci::driver_ops, "zircon", "0.1");

這會執行 driver_ops() lambda,其會傳回初始化的 zx_driver_ops_t 結構。為什麼要使用 lambda?C++ 與結構的部分初始化不同,因此我們會從 ops 的空白執行個體開始,設定感興趣的欄位,然後傳回結構。

UsbXhci::Create() 函式如下所示:

[01] zx_status_t UsbXhci::Create(void* ctx, zx_device_t* parent) {
[02]     fbl::AllocChecker ac;
[03]     auto dev = std::unique_ptr<UsbXhci>(new (&ac) UsbXhci(parent));
[04]     if (!ac.check()) {
[05]         return ZX_ERR_NO_MEMORY;
[06]     }
[07]
[08]     auto status = dev->Init();
[09]     if (status != ZX_OK) {
[10]         return status;
[11]     }
[12]
[13]     // driver manager is now in charge of the device.
[14]     [[maybe_unused]] auto* unused = dev.release();
[15]     return ZX_OK;
[16] }

首先,請注意 dev 的建構函式 (其在 [03] 行上的 new ... UsbXhci(parent) 呼叫),我們很快就會回來。

建構 dev 後,行 [08] 會呼叫 dev->Init(),其可做為呼叫兩個初始化函式之一的去多工點:

zx_status_t UsbXhci::Init() {
    if (pci_.is_valid()) {
        return InitPci();
    } else if (pdev_.is_valid()) {
        return InitPdev();
    } else {
        return ZX_ERR_NOT_SUPPORTED;
    }
}

上層通訊協定使用方式

讓我們透過 InitPci() 函式遵循 pci_ 成員的路徑。我們會示範裝置如何使用父項通訊協定中的函式。

UsbXhci::Create() 中,dev 的建構函式已從 parent 引數初始化成員 pci_。以下是類別定義中的相關摘錄:

class UsbXhci: ... {
public:
    explicit UsbXhci(zx_device_t* parent)
        : UsbXhciType(parent), pci_(parent), pdev_(parent) {}
...
private:
    ddk::PciProtocolClient pci_;
...
};

pci_ 成員的第一個 InitPci() 使用方法是取得 BTI (Bus Transaction Initiator) 物件:

zx_status_t UsbXhci::InitPci() {
...
    zx::bti bti;
    status = pci_.GetBti(0, &bti);
    if (status != ZX_OK) {
        return status;
    }
    ...

這種用法屬於一般情況。