| # 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 — 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 |