使用 C++ DDK 模板库

在本部分中,我们将介绍 C++ DDK 模板库(简称“DDKTL”)。它是一组 C++ 模板化类,通过提供能够确保类型安全并执行基本功能的 mixin,来简化编写驱动程序的工作。

如果您不熟悉 mixin,请先参阅关于以下内容的维基百科文章:* mixins 和 * CRTP - 或奇怪的重复模板模式

我们要讨论的 mixin 是在 //src/lib/ddktl/include/ddktl/device.h 中定义的。

提供了以下 mixin:

Mixin 课程 功能 目的
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() 总线设备的远程消息

在为设备定义类时,您可以通过添加适当的 mixin 来指定它支持哪些函数。例如(添加行号仅用于文档目的):

[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 类包含 mixin(第 [02 行、第 03] 行:InitializableUnbindable 行),因此我们需要在类中提供相应的函数实现(第 [17 行 .. 18] 行)。

所有 DDKTL 类都必须提供发布函数(这里,第 [19] 行提供 DdkRelease()),因此,我们未在 DeviceType 的 mixin 定义中指定此功能。

请注意,一旦回复 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] 行提供了三个可选 mixins 以及强制性 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_;
...
};

InitPci()pci_ 成员的第一个用途是获取 BTI (Bus Transaction Initiator) 对象:

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

此用量属于正常情况。