C++ DDK 模板库使用指南

在本章中,我们将关注 C++ DDK 模板库,或者简短了解“DDKTL”。 它是一系列的 C++ 模板类,通过提供多态来确保类型安全和运行基本功能,用来简化写驱动程序的工作。

如果你不熟悉多态的话,你可以阅读相关维基百科文章:

我们将要讨论的多态在//src/lib/ddktl/include/ddktl/device.h中定义。

我们将提供下述的多态:

Mixin class Function Purpose
ddk::GetProtocolable DdkGetProtocol() fetches the protocol
ddk::Initializable DdkInit() called after DdkAdd(), for completing initialization of a device safely
ddk::Openable DdkOpen() client’s open()
ddk::Closable DdkClose() client’s close()
ddk::Unbindable DdkUnbind() called when this device is being removed
ddk::Messageable DdkMessage() for FIDL IPC messages
ddk::Suspendable DdkSuspend() to suspend device
ddk::Resumable DdkResume() to resume device
ddk::PerformanceTunable DdkSetPerformanceState() to transition the performant state
ddk::AutoSuspendable DdkConfigureAutoSuspend() to configure whether a driver can auto suspend the device
ddk::Rxrpcable DdkRxrpc() remote messages for bus devices

为了完整性期间,下述的多态也同样会提供,但是已经被废弃:

Deprecated Mixin class Function Purpose
ddk::Readable DdkRead() client’s read()
ddk::Writable DdkWrite() client’s write()
ddk::GetSizable DdkGetSize() returns size of device
ddk::UnbindableDeprecated DdkUnbindDeprecated() called when this device is being removed

这些多态对应的函数被定义在zx_protocol_device_t结构体中,用于 简单的, 基于C语言的驱动程序中。

当定义你的设备类时,你需要明确哪个函数将通过包含适合的多态来支持。 例如(添加的行号仅用于文件编制):

  1. [01] using DeviceType = ddk::Device<MyDevice,
  2. [02] ddk::Initializable, // safely initialize after **DdkAdd()**
  3. [03] ddk::Openable, // we support open()
  4. [04] ddk::Closable, // close()
  5. [05] ddk::Readable, // read()
  6. [06] ddk::Unbindable>; // and the device can be unbound

这将创建一个DeviceType的快捷方式。 ddk::Device模板类输入一个或多个参数,第一个参数为基础类(这里则为MyDevice)。 额外的模板参数则为多态,定义了哪个 FDF 设备成员函数被实现。

一旦被定义后,接下来我们可以从DeviceType的继承,声明我们的设备类(MyDevice):

  1. [07] class MyDevice : public DeviceType {
  2. [08] public:
  3. [09] explicit MyDevice(zx_device_t* parent)
  4. [10] : DeviceType(parent) {}
  5. [11]
  6. [12] zx_status_t Bind() {
  7. [13] // Any other setup required by MyDevice.
  8. [14] // The device_add_args_t will be filled out by the base class.
  9. [15] return DdkAdd("my-device-name");
  10. [16] }
  11. [17]
  12. [18] // Methods required by the ddk mixins
  13. [19] void DdkInit(ddk::InitTxn txn);
  14. [20] zx_status_t DdkOpen(zx_device_t** dev_out, uint32_t flags);
  15. [21] zx_status_t DdkClose(uint32_t flags);
  16. [22] zx_status_t DdkRead(void* buf, size_t count, zx_off_t off, size_t* actual);
  17. [23] void DdkUnbind(ddk::UnbindTxn txn);
  18. [24] void DdkRelease();
  19. [25] };

因为 DeviceType 类包含五个多态( [02 .. 06]行:Initializable, Openable, Closable, ReadableUnbindable),我们需要在自己的类中提供各自的函数实现( [18 .. 23]行)。

所有 DDKTL 类必须提供一个 release 函数(这里为[24]行中提供的DdkRelease()),这也就是为什么我们没有在DeviceType内的多态定义中声明的原因。

请记住,一旦你回复了InitTxn(在DdkInit()中提供),你 不能 安全的使用设备实例—因为其他的线程可能调用DdkUnbind(),它通常会调用DdkRelease(),就会释放驱动的设备上下文。这样会构成“释放后再使用”的违例。对于没有实现DdkInit()的设备,在你调用DdkAdd()后适用。

从前面的章节中可以看出,你的设备必须在驱动管理器中注册,才能使用。 具体做法如下:

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

在这里的代码中,my_bind()创建了一个MyDevice的实例,调用Bind()例行程序,然后返回一个状态。

Bind()(上述class MyDevice 声明中[12]行),执行它需要的任何设置,然后使用设备名称调用DdkAdd()

因为设备是Initializable的,接下来驱动管理器将使用InitTxn调用你的DdkInit() 实现。在设备回复InitTxn前,设备都保持不可见并且不可被绑定状态。这个回复可以在任意的线程中完成—它不一定需要在DdkInit()返回之前。

在回复了InitTxn之后,你的设备将在设备文件系统中变得可见,并且客户端调用的任何 open(), close(),和read()都将分别运行在你的DdkOpen(), DdkClose()DdkRead()的实现中。

正如在目录//src/devices/block/drivers/zxcrypt中的示例,我们提供了一个典型的设备声明(device.h)。

  1. [01] class Device;
  2. [02] using DeviceType = ddk::Device<Device,
  3. [03] ddk::GetProtocolable,
  4. [04] ddk::GetSizable,
  5. [05] ddk::Unbindable>;
  6. ...
  7. [06] class Device final : public DeviceType,
  8. [07] public ddk::BlockImplProtocol<Device, ddk::base_protocol>,
  9. [08] public ddk::BlockPartitionProtocol<Device>,
  10. [09] public ddk::BlockVolumeProtocol<Device> {
  11. [10] public:
  12. ...
  13. [11] // ddk::Device methods; see ddktl/device.h
  14. [12] zx_status_t DdkGetProtocol(uint32_t proto_id, void* out);
  15. [13] zx_off_t DdkGetSize();
  16. [14] void DdkUnbind(ddk::UnbindTxn txn);
  17. [15] void DdkRelease();
  18. ...

[01 .. 05]行中使用基础类Device和三个多态 GetProtocolable, GetSizableUnbindable声明了 DeviceType的快捷方式。

有趣的是在[06]行中:我们不仅是从DeviceType继承,也同样从[07 .. 09]行中的其他类继承。

[11 .. 15]行中提供了三个可选多态的原型,和一个必备的DdkRelease()成员函数。

下面是一个zxcrypt设备的DdkGetProtocol实现的示例(从device.cc节选):

  1. zx_status_t Device::DdkGetProtocol(uint32_t proto_id, void* out) {
  2. auto* proto = static_cast<ddk::AnyProtocol*>(out);
  3. proto->ctx = this;
  4. switch (proto_id) {
  5. case ZX_PROTOCOL_BLOCK_IMPL:
  6. proto->ops = &block_impl_protocol_ops_;
  7. return ZX_OK;
  8. case ZX_PROTOCOL_BLOCK_PARTITION:
  9. proto->ops = &block_partition_protocol_ops_;
  10. return ZX_OK;
  11. case ZX_PROTOCOL_BLOCK_VOLUME:
  12. proto->ops = &block_volume_protocol_ops_;
  13. return ZX_OK;
  14. default:
  15. return ZX_ERR_NOT_SUPPORTED;
  16. }
  17. }

从驱动程序出发

让我们来看看驱动程序是如何使用 DDKTL 。

我们将用这组代码示例来使用 USB XHCI 驱动程序;你可以在here: //src/devices/usb/drivers/xhci/usb-xhci.cpp中找到。

simple, C-based drivers中回顾的话, 驱动程序有这样的驱动声明(通常在源代码文件的底部),正如这样:

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

对于 ZIRCON_DRIVER()宏定义来讲,第二个参数是一个zx_driver_ops_t结构体。 在 C++ 版本中,我们使用一个 lambda 函数来进行初始化:

  1. namespace usb_xhci {
  2. ...
  3. static zx_driver_ops_t driver_ops = [](){
  4. zx_driver_ops_t ops = {};
  5. ops.version = DRIVER_OPS_VERSION;
  6. ops.bind = UsbXhci::Create;
  7. return ops;
  8. }();
  9. } // namespace usb_xhci
  10. ZIRCON_DRIVER(usb_xhci, usb_xhci::driver_ops, "zircon", "0.1");

这里运行driver_ops() 的 lambda 函数,它返回了一个已经初始化的zx_driver_ops_t 结构体。 为什么要使用 lambda 呢? C++ 中不喜欢部分初始化结构,所以我们以一个ops的空实例开始,设置我们感兴趣的字段,然后返回结构体。

UsbXhci::Create() 函数就像它对应的 C 的部分(例如在Simple Drivers章节中的null_bind()), 但是有几个额外的参数:

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

首先,注意dev的构造函数(它是在[03]new ... UsbXhci(parent)调用)— 我们稍微会回到这个话题。

一旦dev被构造,[08]行调用dev->Init(),它作为一个去复用点,调用两个初始化函数中的一个:

  1. zx_status_t UsbXhci::Init() {
  2. if (pci_.is_valid()) {
  3. return InitPci();
  4. } else if (pdev_.is_valid()) {
  5. return InitPdev();
  6. } else {
  7. return ZX_ERR_NOT_SUPPORTED;
  8. }
  9. }

父协议的使用

让我们通过InitPci()函数来跟踪 pci_成员的路径。 我们将看到设备是怎样在父协议中使用这些函数的。

UsbXhci::Create()中,dev的构造函数从parent参数中初始化了pci_成员。 以下是类型定义中的相关摘录:

  1. class UsbXhci: ... {
  2. public:
  3. explicit UsbXhci(zx_device_t* parent)
  4. : UsbXhciType(parent), pci_(parent), pdev_(parent) {}
  5. ...
  6. prviate:
  7. ddk::PciProtocolClient pci_;
  8. ...
  9. };

InitPci()pci_成员的第一次使用是为了获得一个BTI (Bus Transaction Initiator) 对象。

  1. zx_status_t UsbXhci::InitPci() {
  2. ...
  3. zx::bti bti;
  4. status = pci_.GetBti(0, &bti);
  5. if (status != ZX_OK) {
  6. return status;
  7. }
  8. ...

这是一种典型用法。