使用 C++ DDK 範本資料庫

在本節中,我們將查看 C++ DDK 範本程式庫 (或稱「DDKTL」)短句 這是一組 C++ 範本類別,可簡化驅動程式庫的編寫工作 提供混合符號,確保類型安全並執行基本功能

如果您不熟悉混合功能,不妨參考下列的維基百科文章: * mixins 和 * CRTP:或尋常的重複範本模式

我們將討論這種混合配置的定義位於 //src/lib/ddktl/include/ddktl/device.h

提供的混合值如下:

混成課 函式 目的
ddk::GetProtocolable DdkGetProtocol() 擷取通訊協定
ddk::Initializable DdkInit() DdkAdd() 之後呼叫,以便安全完成裝置的初始化作業
ddk::Unbindable DdkUnbind() 裝置移除時呼叫
ddk::Suspendable DdkSuspend() 以便暫停裝置
ddk::Resumable DdkResume() 以便重新啟用裝置
ddk::PerformanceTunable DdkSetPerformanceState() 轉換成效
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 類別包含混合 (第 [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,因此驅動程式管理器會呼叫實作項目。 具有 InitTxnDdkInit()。裝置不會顯示, 直到裝置回應 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] 使用基本類別宣告捷徑 DeviceType Device 及其混合項目:GetProtocolableUnbindable

有意思的是第 [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 的建構函式 (其為 new ... UsbXhci(parent) 呼叫) 第 [03] 行) — 我們稍後會再回頭處理。

建構 dev 後,第 [08] 行會呼叫 dev-&gt;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 的建構函式初始化了成員 pci_ 來自 parent 引數 以下是類別定義的相關摘錄:

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

pci_ 成員的第一次使用 InitPci() 是為了取得 BTI (Bus 交易發起者) 物件:

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

這是正常現象。