在本节中,我们将了解 C++ DDK 模板库(简称“DDKTL”)。 它是一组 C++ 模板化类,可简化驱动程序编写工作 通过提供 mixin 来确保类型安全并执行基本功能。
如果您不熟悉 mixins,应阅读有关以下内容的维基百科文章: * 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
类包含 mixins(第 [02
行 .. 03]
行:
Initializable
和 Unbindable
),则需要提供相应的
函数实现(第 [17
行到 18]
行)。
所有 DDKTL 类都必须提供释放函数(此处的 [19]
行提供了
DdkRelease()),因此我们没有在 mixin 定义中指定这一点
价格为 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]
行使用基类声明快捷键 DeviceType
Device
及其混音:GetProtocolable
和 Unbindable
。
这里有趣的是 [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
的构造函数(即 new ... UsbXhci(parent)
调用)
(在第 [03]
行),我们稍后会进行回复。
构建了 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
的构造函数初始化了成员 pci_
。parent
下面是类定义的相关摘录:
class UsbXhci: ... {
public:
explicit UsbXhci(zx_device_t* parent)
: UsbXhciType(parent), pci_(parent), pdev_(parent) {}
...
private:
ddk::PciProtocolClient pci_;
...
};
InitPci() 对 pci_
成员进行的第一项用途是获取
BTI(总线交易发起程序)对象:
zx_status_t UsbXhci::InitPci() {
...
zx::bti bti;
status = pci_.GetBti(0, &bti);
if (status != ZX_OK) {
return status;
}
...
这属于正常使用情况。