blob: 313169afe3d45c36a249efeac74001866909469a [file] [log] [blame] [view]
# Using the C++ DDK Template Library
This document is part of the [Driver Development Kit tutorial][ddktutorial] documentation.
The preceeding sections in the tutorial have illustrated the basics
of what a device driver needs to do (e.g., register its name, handle
operations, etc.)
In this section, we'll look at the C++ DDK Template Library, or "**DDKTL**" for short.
It's a set of C++ templated classes that simplify the work of writing a driver
by providing mixins that ensure type safety and perform basic functionality.
> If you're not familiar with mixins, you should read the Wikipedia articles on:
> * [mixins] and [CRTPs — or Curiously Recurring Template Patterns][crtp].
--------------------------------
@@@ I'm just dumping stuff here for right now, so there's no rhyme or reason to any of this :-)
--------------------------------
The mixins are defined in
[`//zircon/system/ulib/ddktl/include/ddktl/device.h`][include/device.h].
The following mixins are provided:
Mixin class | Function | Purpose
-----------------------|----------------------|------------------------------
`ddk::GetProtocolable` | **DdkGetProtocol()** | fetches the protocol
`ddk::Openable` | **DdkOpen()** | client's **open()**
`ddk::Closable` | **DdkClose()** | client's **close()**
`ddk::Unbindable` | **DdkUnbind()** | called when parent of this device is being removed
`ddk::Readable` | **DdkRead()** | client's **read()**
`ddk::Writable` | **DdkWrite()** | client's **write()**
`ddk::GetSizable` | **DdkGetSize()** | returns size of device
`ddk::Ioctlable` | **DdkIoctl()** | client's **ioctl()**
`ddk::Messageable` | **DdkMessage()** | for FIDL RPC messages
`ddk::Suspendable` | **DdkSuspend()** | to suspend device
`ddk::Resumable` | **DdkResume()** | to resume device
`ddk::Rxrpcable` | **DdkRxrpc()** | RPC message for bus devices
When defining the class for your device, you specify which functions it will
support by including the appropriate mixins.
For example (line numbers added for documentation purposes only):
```c++
[01] using DeviceType = ddk::Device<MyDevice,
[02] ddk::Openable, // we support open()
[03] ddk::Closable, // close()
[04] ddk::Readable, // read()
[05] ddk::Unbindable>; // and the device can be unbound
```
This creates a shortcut to `DeviceType`.
The `ddk::Device` templated class takes one or more arguments, with the
first argument being the base class (here, `MyDevice`).
The additional template arguments are the mixins that define
which DDK device member functions are implemented.
Once defined, we can then declare our device class (`MyDevice`) as inheriting
from `DeviceType`:
```c++
[07] class MyDevice : public DeviceType {
[08] public:
[09] MyDevice(zx_device_t* parent)
[10] : DeviceType(parent) {}
[11]
[12] zx_status_t Bind() {
[13] // Any other setup required by MyDevice.
[14] // The device_add_args_t will be filled out by the base class.
[15] return DdkAdd("my-device-name");
[16] }
[17]
[18] // Methods required by the ddk mixins
[19] zx_status_t DdkOpen(zx_device_t** dev_out, uint32_t flags);
[20] zx_status_t DdkClose(uint32_t flags);
[21] zx_status_t DdkRead(void* buf, size_t count, zx_off_t off, size_t* actual);
[22] void DdkUnbind();
[23] void DdkRelease();
[24] };
```
Because the `DeviceType` class contains four mixins (lines `[02` .. `05]`: `Openable`,
`Closable`, `Readable`, and `Unbindable`), we're required to provide
the respective function implementations (lines `[18` .. `22]`)
in our class.
Because all DDKTL classes must provide a release function (here,
line `[23]` provides **DdkRelease()**), we didn't specify this in
the mixin definition for `DeviceType`.
> Keep in mind that once you call **DdkAdd()** you _cannot_ safely use the
> device instance &mdash; other threads may call **DdkUnbind()**, which typically
> calls **DDkRelease()**, and that frees the driver's device context.
> This would constitute a "use-after-free" violation.
Recall from the preceeding sections that your device must register with the device manager
in order to be usable.
This is accomplished as follows:
```c++
[26] extern "C" zx_status_t my_bind(zx_device_t* device,
[27] void** cookie) {
[28] auto dev = make_unique<MyDevice>(device);
[29] auto status = dev->Bind();
[30] if (status == ZX_OK) {
[31] // devmgr is now in charge of the memory for dev
[32] dev.release();
[33] }
[34] return status;
[35] }
```
Here, we declare **my_bind()** as an `extern "C"` function (because it needs to
be callable by the C code that handles binding).
The function creates an instance of `MyDevice`, calls the **Bind()** routine,
and returns a status.
**Bind()** (line `[12]`, above), performs whatever setup it needs to, and
then calls **DdkAdd()** with the device name.
After this point, your device is registered with the device manager, and
any client calls from the client's **open()**, **close()**, and **read()**
will now flow to your implementations of **DdkOpen()**, **DdkClose()**,
and **DdkRead()**, respectively.
As an example, in the directory [`zircon/system/dev/block/zxcrypt`][zxcrypt]
we have a typical device declaration ([`device.h`][zxcryptdeviceh]):
```c++
[01] class Device;
[02] using DeviceType = ddk::Device<Device,
[03] ddk::GetProtocolable,
[04] ddk::GetSizable,
[05] ddk::Unbindable>;
...
[06] class Device final : public DeviceType,
[07] public ddk::BlockImplProtocol<Device, ddk::base_protocol>,
[08] public ddk::BlockPartitionProtocol<Device>,
[09] public ddk::BlockVolumeProtocol<Device> {
[10] public:
...
[11] // ddk::Device methods; see ddktl/device.h
[12] zx_status_t DdkGetProtocol(uint32_t proto_id, void* out);
[13] zx_off_t DdkGetSize();
[14] void DdkUnbind();
[15] void DdkRelease();
...
```
Here we see lines `[01` .. `05]` declare the shortcut `DeviceType` with the base class
`Device` and three mixins, `GetProtocolable`, `GetSizable`, and `Unbindable`.
What's interesting in this case is that on line `[06]` we inherit from the `DeviceType`
as usual, but we also inherit from other classes on lines `[07` .. `09]`.
Lines `[11` .. `15]` provide the prototypes for the three optional mixins and the
mandatory **DdkRelease()** member function.
Here's an example of that device's `DdkGetProtocol` implementation (from
[`device.cpp`][zxcryptdevicecpp]):
```c++
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;
}
}
```
# A FIDL, and a Banjo
[**FIDL**][fidl] is the IPC system for Fuchsia, and [**Banjo**][banjo], built on top of it,
provides device-to-device communications.
In this section, we'll see how a DDKTL driver:
* interacts with parent protocols using Banjo,
* implements a protocol, and
* implements a FIDL interface.
<!-- xref table -->
[banjo]: https://fuchsia.googlesource.com/zircon/+/master/docs/ddk/banjo-tutorial.md
[crtp]: https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern
[ddktutorial]: https://fuchsia.googlesource.com/zircon/+/master/docs/ddk/ddk-tutorial.md
[fidl]: https://fuchsia.googlesource.com/zircon/+/master/docs/development/languages/fidl/README.md
[mixins]: https://en.wikipedia.org/wiki/Mixin
<!-- local -->
[include/device.h]: https://fuchsia.googlesource.com/zircon/+/master/system/ulib/ddktl/include/ddktl/device.h
[zxcrypt]: https://fuchsia.googlesource.com/zircon/+/master/system/dev/block/zxcrypt
[zxcryptdevicecpp]: https://fuchsia.googlesource.com/zircon/+/master/system/dev/block/zxcrypt/device.cpp
[zxcryptdeviceh]: https://fuchsia.googlesource.com/zircon/+/master/system/dev/block/zxcrypt/device.h