| # Banjo tutorial | |
| This document is part of the [Zircon Driver Development Kit](/docs/concepts/drivers/overview.md) documentation. | |
| Banjo is a "transpiler" (like [FIDL's | |
| `fidlc`](/docs/development/languages/fidl/README.md)) | |
| — a program that converts an interface definition language (**IDL**) into target language | |
| specific files. | |
| This tutorial is structured as follows: | |
| * brief overview of Banjo | |
| * simple example (I2C) | |
| * explanation of generated code from example | |
| There's also a reference section that includes: | |
| * a list of builtin keywords and primitive types. | |
| ## Overview | |
| Banjo generates C and C++ code that can be used by both the protocol implementer | |
| and the protocol user. | |
| ## A simple example | |
| As a first step, let's take a look at a relatively simple Banjo specification. | |
| This is the file [`//sdk/banjo/ddk.protocol.i2c/i2c.banjo`](/sdk/banjo/ddk.protocol.i2c/i2c.banjo): | |
| > Note that the line numbers in the code samples throughout this tutorial are not part of the files. | |
| ```banjo | |
| [01] // Copyright 2018 The Fuchsia Authors. All rights reserved. | |
| [02] // Use of this source code is governed by a BSD-style license that can be | |
| [03] // found in the LICENSE file. | |
| [04] | |
| [05] library ddk.protocol.i2c; | |
| [06] | |
| [07] using zx; | |
| [08] | |
| [09] const uint32 I2C_10_BIT_ADDR_MASK = 0xF000; | |
| [10] const uint32 I2C_MAX_RW_OPS = 8; | |
| [11] | |
| [12] /// See `Transact` below for usage. | |
| [13] struct I2cOp { | |
| [14] vector<voidptr> data; | |
| [15] bool is_read; | |
| [16] bool stop; | |
| [17] }; | |
| [18] | |
| [19] [Layout = "ddk-protocol"] | |
| [20] protocol I2c { | |
| [21] /// Writes and reads data on an i2c channel. Up to I2C_MAX_RW_OPS operations can be passed in. | |
| [22] /// For write ops, i2c_op_t.data points to data to write. The data to write does not need to be | |
| [23] /// kept alive after this call. For read ops, i2c_op_t.data is ignored. Any combination of reads | |
| [24] /// and writes can be specified. At least the last op must have the stop flag set. | |
| [25] /// The results of the operations are returned asynchronously via the transact_cb. | |
| [26] /// The cookie parameter can be used to pass your own private data to the transact_cb callback. | |
| [27] [Async] | |
| [28] Transact(vector<I2cOp> op) -> (zx.status status, vector<I2cOp> op); | |
| [29] /// Returns the maximum transfer size for read and write operations on the channel. | |
| [30] GetMaxTransferSize() -> (zx.status s, usize size); | |
| [31] GetInterrupt(uint32 flags) -> (zx.status s, handle<interrupt> irq); | |
| [32] }; | |
| ``` | |
| It defines an interface that allows an application to read and write data on an I2C bus. | |
| In the I2C bus, data must first be written to the device in order to solicit | |
| a response. | |
| If a response is desired, the response can be read from the device. | |
| (A response might not be required when setting a write-only register, for example.) | |
| Let's look at the individual components, line-by-line: | |
| * `[05]` — the `library` directive tells the Banjo compiler what prefix it should | |
| use on the generated output; think of it as a namespace specifier. | |
| * `[07]` — the `using` directive tells Banjo to include the `zx` library. | |
| * `[09]` and `[10]` — these introduce two constants for use by the programmer. | |
| * `[13` .. `17]` — these define a structure, called `I2cOp`, that the programmer | |
| will then use for transferring data to and from the bus. | |
| * `[19` .. `32]` — these lines define the interface methods that are provided by | |
| this Banjo specification; we'll discuss this in greater detail [below](#the-interface). | |
| > Don't be confused by the comments on `[21` .. `26]` (and elsewhere) — they're | |
| > "flow through" comments that are intended to be emitted into the generated source. | |
| > Any comment that starts with "`///`" (three! slashes) is a "flow through" comment. | |
| > Ordinary comments (that is, "`//`") are intended for the current module. | |
| > This will become clear when we look at the generated code. | |
| ## The operation structure | |
| In our I2C sample, the `struct I2cOp` structure defines three elements: | |
| Element | Type | Use | |
| ----------|-------------------|----------------------------------------------------------------- | |
| `data` | `vector<voidptr>` | contains the data sent to, and optionally received from, the bus | |
| `is_read` | `bool` | flag indicating read functionality desired | |
| `stop` | `bool` | flag indicating a stop byte should be sent after the operation | |
| The structure defines the communications area that will be used between the protocol | |
| implementation (the driver) and the protocol user (the program that's using the bus). | |
| ## The interface | |
| The more interesting part is the `protocol` specification. | |
| We'll skip the `[Layout]` (line `[19]`) and `[Async]` (line `[27]`) attributes for now, | |
| but will return to them below, in [Attributes](#attributes). | |
| The `protocol` section defines three interface methods: | |
| * `Transact` | |
| * `GetMaxTransferSize` | |
| * `GetInterrupt` | |
| Without going into details about their internal operations (this isn't a tutorial on | |
| I2C, after all), let's see how they translate into the target language. | |
| We'll look at the C and C++ implementations separately, using the C description | |
| to include the structure definition that's common to the C++ version as well. | |
| > Currently, generation of C and C++ code is supported, with Rust support planned | |
| > in the future. | |
| ## C | |
| The C implementation is relatively straightforward: | |
| * `struct`s and `union`s map almost directly into their C language counterparts. | |
| * `enum`s and constants are generated as `#define` macros. | |
| * `protocol`s are generated as two `struct`s: | |
| * a function table, and | |
| * a struct with pointers to the function table and a context. | |
| * Some helper functions are also generated. | |
| The C version is generated into | |
| `$BUILD_DIR/banjoing/gen/ddk/protocol/i2c.h`, | |
| where _TARGET_ is the target architecture, e.g., `arm64`. | |
| The file is relatively long, so we'll look at it in several parts. | |
| ### Boilerplate | |
| The first part has some boilerplate which we'll show without further comment: | |
| ```c | |
| [01] // Copyright 2018 The Fuchsia Authors. All rights reserved. | |
| [02] // Use of this source code is governed by a BSD-style license that can be | |
| [03] // found in the LICENSE file. | |
| [04] | |
| [05] // WARNING: THIS FILE IS MACHINE GENERATED. DO NOT EDIT. | |
| [06] // MODIFY sdk/banjo/ddk.protocol.i2c/i2c.banjo INSTEAD. | |
| [07] | |
| [08] #pragma once | |
| [09] | |
| [10] #include <zircon/compiler.h> | |
| [11] #include <zircon/types.h> | |
| [12] | |
| [13] __BEGIN_CDECLS | |
| ``` | |
| ### Forward declarations | |
| Next are forward declarations for our structures and functions: | |
| ```c | |
| [15] // Forward declarations | |
| [16] | |
| [17] typedef struct i2c_op i2c_op_t; | |
| [18] typedef struct i2c_protocol i2c_protocol_t; | |
| [19] typedef void (*i2c_transact_callback)(void* ctx, zx_status_t status, const i2c_op_t* op_list, size_t op_count); | |
| [20] | |
| [21] // Declarations | |
| [22] | |
| [23] // See `Transact` below for usage. | |
| [24] struct i2c_op { | |
| [25] const void* data_buffer; | |
| [26] size_t data_size; | |
| [27] bool is_read; | |
| [28] bool stop; | |
| [29] }; | |
| ``` | |
| Note that lines `[17` .. `19]` only declare types, they don't actually define | |
| structures or prototypes for functions. | |
| Notice how the "flow through" comments (original `.banjo` file line `[12]`, for example) | |
| got emitted into the generated code (line `[23]` above), with one slash stripped off to | |
| make them look like normal comments. | |
| Lines `[24` .. `29`] are, as advertised, an almost direct mapping of the `struct I2cOp` | |
| from the `.banjo` file above (lines `[13` .. `17`]). | |
| Astute C programmers will immediately see how the C++ style `vector<voidptr> data` (original | |
| `.banjo` file line `[14]`) is handled in C: it gets converted to a pointer | |
| ("`data_buffer`") and a size ("`data_size`"). | |
| > As far as the naming goes, the base name is `data` (as given in the `.banjo` file). | |
| > For a vector of `voidptr`, the transpiler appends `_buffer` and `_size` to convert the | |
| > `vector` into a C compatible structure. | |
| > For all other vector types, the transpiler appends `_list` and `_count` instead (for | |
| > code readability). | |
| ### Constants | |
| Next, we see our `const uint32` constants converted into `#define` statements: | |
| ```c | |
| [31] #define I2C_MAX_RW_OPS UINT32_C(8) | |
| [32] | |
| [33] #define I2C_10_BIT_ADDR_MASK UINT32_C(0xF000) | |
| ``` | |
| In the C version, We chose `#define` instead of "passing through" the `const uint32_t` | |
| representation because: | |
| * `#define` statements only exist at compile time, and get inlined at every usage site, whereas | |
| a `const uint32_t` would get embedded in the binary, and | |
| * `#define` allows for more compile time optimizations (e.g., doing math with the constant value). | |
| The downside is that we don't get type safety, which is why you see the helper macros (like | |
| **UINT32_C()** above); they just cast the constant to the appropriate type. | |
| ### Protocol structures | |
| And now we get into the good parts. | |
| ```c | |
| [35] typedef struct i2c_protocol_ops { | |
| [36] void (*transact)(void* ctx, const i2c_op_t* op_list, size_t op_count, i2c_transact_callback callback, void* cookie); | |
| [37] zx_status_t (*get_max_transfer_size)(void* ctx, size_t* out_size); | |
| [38] zx_status_t (*get_interrupt)(void* ctx, uint32_t flags, zx_handle_t* out_irq); | |
| [39] } i2c_protocol_ops_t; | |
| ``` | |
| This `typedef` creates a structure definition that contains the three `protocol` methods | |
| that were defined in the original `.banjo` file at lines `[28]`, `[30]` and `[31]`. | |
| Notice the name mangling that has occurred — this is how you can map the | |
| `protocol` method names to the C function pointer names so that you know what | |
| they're called: | |
| Banjo | C | Rule | |
| ---------------------|-------------------------|--------------------------------------------------------------- | |
| `Transact` | `transact` | Convert leading uppercase to lowercase | |
| `GetMaxTransferSize` | `get_max_transfer_size` | As above, and convert camel-case to underscore-separated style | |
| `GetInterrupt` | `get_interrupt` | Same as above | |
| Next, the interface definitions are wrapped in a context-bearing structure: | |
| ```c | |
| [41] struct i2c_protocol { | |
| [42] i2c_protocol_ops_t* ops; | |
| [43] void* ctx; | |
| [44] }; | |
| ``` | |
| And now the "flow-through" comments (`.banjo` file, lines `[21` .. `26]`) | |
| suddenly make way more sense! | |
| ```c | |
| [46] // Writes and reads data on an i2c channel. Up to I2C_MAX_RW_OPS operations can be passed in. | |
| [47] // For write ops, i2c_op_t.data points to data to write. The data to write does not need to be | |
| [48] // kept alive after this call. For read ops, i2c_op_t.data is ignored. Any combination of reads | |
| [49] // and writes can be specified. At least the last op must have the stop flag set. | |
| [50] // The results of the operations are returned asynchronously via the transact_cb. | |
| [51] // The cookie parameter can be used to pass your own private data to the transact_cb callback. | |
| ``` | |
| Finally, we see the actual generated code for the three methods: | |
| ```c | |
| [52] static inline void i2c_transact(const i2c_protocol_t* proto, const i2c_op_t* op_list, size_t op_count, i2c_transact_callback callback, void* cookie) { | |
| [53] proto->ops->transact(proto->ctx, op_list, op_count, callback, cookie); | |
| [54] } | |
| [55] // Returns the maximum transfer size for read and write operations on the channel. | |
| [56] static inline zx_status_t i2c_get_max_transfer_size(const i2c_protocol_t* proto, size_t* out_size) { | |
| [57] return proto->ops->get_max_transfer_size(proto->ctx, out_size); | |
| [58] } | |
| [59] static inline zx_status_t i2c_get_interrupt(const i2c_protocol_t* proto, uint32_t flags, zx_handle_t* out_irq) { | |
| [60] return proto->ops->get_interrupt(proto->ctx, flags, out_irq); | |
| [61] } | |
| ``` | |
| ### Prefixes and paths | |
| Notice how the prefix `i2c_` (from the interface name, `.banjo` file line `[20]`) | |
| got added to the method names; thus, `Transact` became `i2c_transact`, and so on. | |
| This is part of the mapping between `.banjo` names and their C equivalents. | |
| Also, the `library` name (line `[05]` in the `.banjo` file) is transformed into the | |
| include path: so `library ddk.protocol.i2c` implies a path of `<ddk/protocol/i2c.h>`. | |
| ## C++ | |
| The C++ code is slightly more complex than the C version. | |
| Let's take a look. | |
| The Banjo transpiler generates three files: | |
| the first is the C file discussed above, and the other two are under | |
| `$BUILD_DIR/banjoing/gen/ddktl/protocol/i2c.h`: | |
| * `i2c.h` — the file your program should include, and | |
| * `i2c-internal.h` — an internal file, included by `i2c.h` | |
| As usual, _TARGET_ is the build target architecture (e.g., `x64`). | |
| The "internal" file contains declarations and assertions, which we can safely skip. | |
| The C++ version of `i2c.h` is fairly long, so we'll look at it in smaller pieces. | |
| Here's an overview "map" of what we'll be looking at, showing the starting line | |
| number of each piece: | |
| Line | Section | |
| --------------|---------------------------- | |
| 1 | [boilerplate](#a-simple-example-c-boilerplate-2) | |
| 20 | [auto generated usage comments](#auto_generated-comments) | |
| 55 | [class I2cProtocol](#the-i2cprotocol-mixin-class) | |
| 99 | [class I2cProtocolClient](#the-i2cprotocolclient-wrapper-class) | |
| ### Boilerplate | |
| The boilerplate is pretty much what you'd expect: | |
| ```c++ | |
| [001] // Copyright 2018 The Fuchsia Authors. All rights reserved. | |
| [002] // Use of this source code is governed by a BSD-style license that can be | |
| [003] // found in the LICENSE file. | |
| [004] | |
| [005] // WARNING: THIS FILE IS MACHINE GENERATED. DO NOT EDIT. | |
| [006] // MODIFY sdk/banjo/ddk.protocol.i2c/i2c.banjo INSTEAD. | |
| [007] | |
| [008] #pragma once | |
| [009] | |
| [010] #include <ddk/driver.h> | |
| [011] #include <ddk/protocol/i2c.h> | |
| [012] #include <ddktl/device-internal.h> | |
| [013] #include <zircon/assert.h> | |
| [014] #include <zircon/compiler.h> | |
| [015] #include <zircon/types.h> | |
| [016] #include <lib/zx/interrupt.h> | |
| [017] | |
| [018] #include "i2c-internal.h" | |
| ``` | |
| It `#include`s a bunch of DDK and OS headers, including: | |
| * the C version of the header (line `[011]`, which means that everything discussed | |
| [above in the C section](#a-simple-example-c-1) applies here as well), and | |
| * the generated `i2c-internal.h` file (line `[018]`). | |
| Next is the "auto generated usage comments" section; we'll come back to that | |
| [later](#auto_generated-comments) as it will make more sense once we've seen | |
| the actual class declarations. | |
| The two class declarations are wrapped in the DDK namespace: | |
| ```c++ | |
| [053] namespace ddk { | |
| ... | |
| [150] } // namespace ddk | |
| ``` | |
| ### The I2cProtocolClient wrapper class | |
| The `I2cProtocolClient` class is a simple wrapper around the `i2c_protocol_t` | |
| structure (defined in the C include file, line `[41]` which we discussed in | |
| [Protocol structures](#protocol-structures), above). | |
| ```c++ | |
| [099] class I2cProtocolClient { | |
| [100] public: | |
| [101] I2cProtocolClient() | |
| [102] : ops_(nullptr), ctx_(nullptr) {} | |
| [103] I2cProtocolClient(const i2c_protocol_t* proto) | |
| [104] : ops_(proto->ops), ctx_(proto->ctx) {} | |
| [105] | |
| [106] I2cProtocolClient(zx_device_t* parent) { | |
| [107] i2c_protocol_t proto; | |
| [108] if (device_get_protocol(parent, ZX_PROTOCOL_I2C, &proto) == ZX_OK) { | |
| [109] ops_ = proto.ops; | |
| [110] ctx_ = proto.ctx; | |
| [111] } else { | |
| [112] ops_ = nullptr; | |
| [113] ctx_ = nullptr; | |
| [114] } | |
| [115] } | |
| [116] | |
| [117] void GetProto(i2c_protocol_t* proto) const { | |
| [118] proto->ctx = ctx_; | |
| [119] proto->ops = ops_; | |
| [120] } | |
| [121] bool is_valid() const { | |
| [122] return ops_ != nullptr; | |
| [123] } | |
| [124] void clear() { | |
| [125] ctx_ = nullptr; | |
| [126] ops_ = nullptr; | |
| [127] } | |
| [128] // Writes and reads data on an i2c channel. Up to I2C_MAX_RW_OPS operations can be passed in. | |
| [129] // For write ops, i2c_op_t.data points to data to write. The data to write does not need to be | |
| [130] // kept alive after this call. For read ops, i2c_op_t.data is ignored. Any combination of reads | |
| [131] // and writes can be specified. At least the last op must have the stop flag set. | |
| [132] // The results of the operations are returned asynchronously via the transact_cb. | |
| [133] // The cookie parameter can be used to pass your own private data to the transact_cb callback. | |
| [134] void Transact(const i2c_op_t* op_list, size_t op_count, i2c_transact_callback callback, void* cookie) const { | |
| [135] ops_->transact(ctx_, op_list, op_count, callback, cookie); | |
| [136] } | |
| [137] // Returns the maximum transfer size for read and write operations on the channel. | |
| [138] zx_status_t GetMaxTransferSize(size_t* out_size) const { | |
| [139] return ops_->get_max_transfer_size(ctx_, out_size); | |
| [140] } | |
| [141] zx_status_t GetInterrupt(uint32_t flags, zx::interrupt* out_irq) const { | |
| [142] return ops_->get_interrupt(ctx_, flags, out_irq->reset_and_get_address()); | |
| [143] } | |
| [144] | |
| [145] private: | |
| [146] i2c_protocol_ops_t* ops_; | |
| [147] void* ctx_; | |
| [148] }; | |
| ``` | |
| There are three constructors: | |
| * the default one (`[101]`) that sets `ops_` and `ctx_` to `nullptr`, | |
| * an initializer (`[103]`) that takes a pointer to an `i2c_protocol_t` structure and populates | |
| the `ops_` and `ctx`_ fields from their namesakes in the structure, and | |
| * another initializer (`[106]`) that extracts the `ops`_ and `ctx_` information from | |
| a `zx_device_t`. | |
| The last constructor is the preferred one, and can be used like this: | |
| ```c++ | |
| ddk::I2cProtocolClient i2c(parent); | |
| if (!i2c.is_valid()) { | |
| return ZX_ERR_*; // return an appropriate error | |
| } | |
| ``` | |
| Three convenience member functions are provided: | |
| * `[117]` **GetProto()** fetches the `ctx_` and `ops_` members into a protocol structure, | |
| * `[121]` **is_valid()** returns a `bool` indicating if the class has been initialized with | |
| a protocol, and | |
| * `[124]` **clear()** invalidates the `ctx_` and `ops_` pointers. | |
| Next we find the three member functions that were specified in the `.banjo` file: | |
| * `[134]` **Transact()**, | |
| * `[138]` **GetMaxTransferSize()**, and | |
| * `[141]` **GetInterrupt()**. | |
| These work just liked the three wrapper functions from the C version of the include file — | |
| that is, they pass their arguments into a call through the respective function pointer. | |
| In fact, compare **i2c_get_max_transfer_size()** from the C version: | |
| ```c | |
| [56] static inline zx_status_t i2c_get_max_transfer_size(const i2c_protocol_t* proto, size_t* out_size) { | |
| [57] return proto->ops->get_max_transfer_size(proto->ctx, out_size); | |
| [58] } | |
| ``` | |
| with the C++ version above: | |
| ```c++ | |
| [138] zx_status_t GetMaxTransferSize(size_t* out_size) const { | |
| [139] return ops_->get_max_transfer_size(ctx_, out_size); | |
| [140] } | |
| ``` | |
| As advertised, all that this class does is store the operations and context pointers for | |
| later use, so that the call through the wrapper is more elegant. | |
| > You'll also notice that the C++ wrapper function doesn't have any name mangling — | |
| > to use a tautology, **GetMaxTransferSize()** is **GetMaxTransferSize()**. | |
| ### The I2cProtocol mixin class | |
| Ok, that was the easy part. | |
| For this next part, we're going to talk about [mixins](https://en.wikipedia.org/wiki/Mixin) | |
| and [CRTPs — or Curiously Recurring Template | |
| Patterns](https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern). | |
| Let's understand the "shape" of the class first (comment lines deleted for outlining | |
| purposes): | |
| ```c++ | |
| [055] template <typename D, typename Base = internal::base_mixin> | |
| [056] class I2cProtocol : public Base { | |
| [057] public: | |
| [058] I2cProtocol() { | |
| [059] internal::CheckI2cProtocolSubclass<D>(); | |
| [060] i2c_protocol_ops_.transact = I2cTransact; | |
| [061] i2c_protocol_ops_.get_max_transfer_size = I2cGetMaxTransferSize; | |
| [062] i2c_protocol_ops_.get_interrupt = I2cGetInterrupt; | |
| [063] | |
| [064] if constexpr (internal::is_base_proto<Base>::value) { | |
| [065] auto dev = static_cast<D*>(this); | |
| [066] // Can only inherit from one base_protocol implementation. | |
| [067] ZX_ASSERT(dev->ddk_proto_id_ == 0); | |
| [068] dev->ddk_proto_id_ = ZX_PROTOCOL_I2C; | |
| [069] dev->ddk_proto_ops_ = &i2c_protocol_ops_; | |
| [070] } | |
| [071] } | |
| [072] | |
| [073] protected: | |
| [074] i2c_protocol_ops_t i2c_protocol_ops_ = {}; | |
| [075] | |
| [076] private: | |
| ... | |
| [083] static void I2cTransact(void* ctx, const i2c_op_t* op_list, size_t op_count, i2c_transact_callback callback, void* cookie) { | |
| [084] static_cast<D*>(ctx)->I2cTransact(op_list, op_count, callback, cookie); | |
| [085] } | |
| ... | |
| [087] static zx_status_t I2cGetMaxTransferSize(void* ctx, size_t* out_size) { | |
| [088] auto ret = static_cast<D*>(ctx)->I2cGetMaxTransferSize(out_size); | |
| [089] return ret; | |
| [090] } | |
| [091] static zx_status_t I2cGetInterrupt(void* ctx, uint32_t flags, zx_handle_t* out_irq) { | |
| [092] zx::interrupt out_irq2; | |
| [093] auto ret = static_cast<D*>(ctx)->I2cGetInterrupt(flags, &out_irq2); | |
| [094] *out_irq = out_irq2.release(); | |
| [095] return ret; | |
| [096] } | |
| [097] }; | |
| ``` | |
| The `I2CProtocol` class inherits from a base class, specified by the second template parameter. | |
| If it's left unspecified, it defaults to `internal::base_mixin`, and no special magic happens. | |
| If, however, the base class is explicitly specified, it should be `ddk::base_protocol`, | |
| in which case additional asserts are added (to double check that only one mixin is the base protocol). | |
| In addition, special DDKTL fields are set to automatically register this protocol as the | |
| base protocol when the driver triggers **DdkAdd()**. | |
| The constructor calls an internal validation function, **CheckI2cProtocolSubclass()** `[059]` | |
| (defined in the generated `i2c-internal.h` file), which has several **static_assert()** calls. | |
| The class `D` is expected to implement the three member functions (**I2cTransact()**, | |
| **I2cGetMaxTransferSize()**, and **I2cGetInterrupt()**) in order for the static methods to work. | |
| If they're not provided by `D`, then the compiler would (in the absence of the static | |
| asserts) produce gnarly templating errors. | |
| The static asserts serve to produce diagnostic errors that are understandable by mere humans. | |
| Next, the three pointer-to-function operations members (`transact`, | |
| `get_max_transfer_size`, and `get_interrupt`) are bound (lines `[060` .. `062]`). | |
| Finally, the `constexpr` expression provides a default initialization if required. | |
| ### Using the mixin class | |
| The `I2cProtocol` class can be used as follows (from | |
| [`//src/devices/bus/drivers/platform/platform-proxy.h`](/src/devices/bus/drivers/platform/platform-proxy.h)): | |
| ```c++ | |
| [01] class ProxyI2c : public ddk::I2cProtocol<ProxyI2c> { | |
| [02] public: | |
| [03] explicit ProxyI2c(uint32_t device_id, uint32_t index, fbl::RefPtr<PlatformProxy> proxy) | |
| [04] : device_id_(device_id), index_(index), proxy_(proxy) {} | |
| [05] | |
| [06] // I2C protocol implementation. | |
| [07] void I2cTransact(const i2c_op_t* ops, size_t cnt, i2c_transact_callback transact_cb, | |
| [08] void* cookie); | |
| [09] zx_status_t I2cGetMaxTransferSize(size_t* out_size); | |
| [10] zx_status_t I2cGetInterrupt(uint32_t flags, zx::interrupt* out_irq); | |
| [11] | |
| [12] void GetProtocol(i2c_protocol_t* proto) { | |
| [13] proto->ops = &i2c_protocol_ops_; | |
| [14] proto->ctx = this; | |
| [15] } | |
| [16] | |
| [17] private: | |
| [18] uint32_t device_id_; | |
| [19] uint32_t index_; | |
| [20] fbl::RefPtr<PlatformProxy> proxy_; | |
| [21] }; | |
| ``` | |
| Here we see that `class ProxyI2c` inherits from the DDK's `I2cProtocol` and provides | |
| itself as the argument to the template — this is the "mixin" concept. | |
| This causes the `ProxyI2c` type to be substituted for `D` in the template definition | |
| of the class (from the `i2c.h` header file above, lines `[084]`, `[088]`, and `[093]`). | |
| Taking a look at just the **I2cGetMaxTransferSize()** function as an example, it's | |
| effectively as if the source code read: | |
| ```c++ | |
| [087] static zx_status_t I2cGetMaxTransferSize(void* ctx, size_t* out_size) { | |
| [088] auto ret = static_cast<ProxyI2c*>(ctx)->I2cGetMaxTransferSize(out_size); | |
| [089] return ret; | |
| [090] } | |
| ``` | |
| This ends up eliminating the cast-to-self boilerplate in your code. | |
| This casting is necessary because the type information is erased at the DDK boundary — | |
| recall that the context `ctx` is a `void *` pointer. | |
| ### Auto-generated comments | |
| Banjo automatically generates comments in the include file that basically summarize what we | |
| talked about above: | |
| ```c++ | |
| [020] // DDK i2c-protocol support | |
| [021] // | |
| [022] // :: Proxies :: | |
| [023] // | |
| [024] // ddk::I2cProtocolClient is a simple wrapper around | |
| [025] // i2c_protocol_t. It does not own the pointers passed to it | |
| [026] // | |
| [027] // :: Mixins :: | |
| [028] // | |
| [029] // ddk::I2cProtocol is a mixin class that simplifies writing DDK drivers | |
| [030] // that implement the i2c protocol. It doesn't set the base protocol. | |
| [031] // | |
| [032] // :: Examples :: | |
| [033] // | |
| [034] // // A driver that implements a ZX_PROTOCOL_I2C device. | |
| [035] // class I2cDevice; | |
| [036] // using I2cDeviceType = ddk::Device<I2cDevice, /* ddk mixins */>; | |
| [037] // | |
| [038] // class I2cDevice : public I2cDeviceType, | |
| [039] // public ddk::I2cProtocol<I2cDevice> { | |
| [040] // public: | |
| [041] // I2cDevice(zx_device_t* parent) | |
| [042] // : I2cDeviceType(parent) {} | |
| [043] // | |
| [044] // void I2cTransact(const i2c_op_t* op_list, size_t op_count, i2c_transact_callback callback, void* cookie); | |
| [045] // | |
| [046] // zx_status_t I2cGetMaxTransferSize(size_t* out_size); | |
| [047] // | |
| [048] // zx_status_t I2cGetInterrupt(uint32_t flags, zx::interrupt* out_irq); | |
| [049] // | |
| [050] // ... | |
| [051] // }; | |
| ``` | |
| # Using Banjo | |
| > Suraj says: | |
| >> We also need something in-between a FIDL tutorial and a driver writing tutorial, | |
| >> in order to describe banjo usage. | |
| >> Basically, writing a simple protocol, and then describing a driver that emits | |
| >> it, and another driver that binds on top of it and makes use of that protocol. | |
| >> If it makes sense, the existing driver writing tutorial could just be modified | |
| >> to have more fleshed out details on banjo usage. | |
| >> I think the current driver tutorial is focused on C usage as well, and getting | |
| >> a C++ version (using ddktl) would probably bring the most value [this is | |
| >> already on my work queue, "Tutorial on using ddktl (C++ DDK wrappers)" -RK]. | |
| Now that we've seen the generated code for the I2C driver, let's take a look | |
| at how we would use it. | |
| > @@@ to be completed | |
| # Reference | |
| > @@@ This is where we should list all builtin keywords and primitive types | |
| ## Attributes | |
| Recall from the example above that the `protocol` section had two attributes; | |
| a `[Layout]` and an `[Async]` attribute. | |
| ### The Layout attribute | |
| The line just before the `protocol` is the `[Layout]` attribute: | |
| ```banjo | |
| [19] [Layout = "ddk-protocol"] | |
| [20] protocol I2c { | |
| ``` | |
| The attribute applies to the next item; so in this case, the entire `protocol`. | |
| Only one layout is allowed per interface. | |
| There are in fact 3 `Layout` attribute types currently supported: | |
| * `ddk-protocol` | |
| * `ddk-interface` | |
| * `ddk-callback` | |
| In order to understand how these layout types work, let's assume we have two drivers, | |
| `A` and `B`. | |
| Driver `A` spawns a device, which `B` then attaches to, (making `B` a child of `A`). | |
| If `B` then queries the DDK for its parent's "protocol" via **device_get_protocol()**, | |
| it'll get a `ddk-protocol`. | |
| A `ddk-protocol` is a set of callbacks that a parent provides to its child. | |
| One of the protocol functions can be to register a "reverse-protocol", whereby | |
| the child provides a set of callbacks for the parent to trigger instead. | |
| This is a `ddk-interface`. | |
| From a code generation perspective, these two (`ddk-protocol` and `ddk-interface`) | |
| look almost identical, except for some slight naming differences (`ddk-protocol` | |
| automatically appends the word "protocol" to the end of generated structs / classes, | |
| whereas `ddk-interface` doesn't). | |
| `ddk-callback` is a slight optimization over `ddk-interface`, and is used when an | |
| interface has just one single function. | |
| Instead of generating two structures, like: | |
| ```c | |
| struct interface { | |
| void* ctx; | |
| inteface_function_ptr_table* callbacks; | |
| }; | |
| struct interface_function_ptr_table { | |
| void (*one_function)(...); | |
| } | |
| ``` | |
| a `ddk-callback` will generate a single structure with the function pointer inlined: | |
| ```c | |
| struct callback { | |
| void* ctx; | |
| void (*one_function)(...); | |
| }; | |
| ``` | |
| ### The Async attribute | |
| Within the `protocl` section, we see another attribute: the `[Async]` attribute: | |
| ```banjo | |
| [20] protocl I2c { | |
| ... /// comments (removed) | |
| [27] [Async] | |
| ``` | |
| The `[Async]` attribute is a way to make protocol messages not be synchronous. | |
| It autogenerates a callback type in which the output arguments are inputs to the callback. | |
| The original method will not have any of the output parameters specified in its signatures. | |
| Recall from the example above that we had a `Transact` method: | |
| ```banjo | |
| [27] [Async] | |
| [28] Transact(vector<I2cOp> op) -> (zx.status status, vector<I2cOp> op); | |
| ``` | |
| When used (as above) in conjunction with the `[Async]` attribute, it means that we want Banjo | |
| to invoke a callback function, so that we can handle the output data (the second | |
| `vector<I2cOp>` above, representing the data from the I2C bus). | |
| Here's how it works. | |
| We send data to the I2C bus via the first `vector<I2cOp>` argument. | |
| Some time later, the I2C bus may generate data in response to our request. | |
| Because we specified `[Async]`, Banjo generates the functions to take a callback function | |
| as input. | |
| In C, these two lines (from the `i2c.h` file) are important: | |
| ```c | |
| [19] typedef void (*i2c_transact_callback)(void* ctx, zx_status_t status, const i2c_op_t* op_list, size_t op_count); | |
| ... | |
| [36] void (*transact)(void* ctx, const i2c_op_t* op_list, size_t op_count, i2c_transact_callback callback, void* cookie); | |
| ``` | |
| In C++, we have two place where the callback is referenced: | |
| ```c++ | |
| [083] static void I2cTransact(void* ctx, const i2c_op_t* op_list, size_t op_count, i2c_transact_callback callback, void* cookie) { | |
| [084] static_cast<D*>(ctx)->I2cTransact(op_list, op_count, callback, cookie); | |
| [085] } | |
| ``` | |
| and | |
| ```c++ | |
| [134] void Transact(const i2c_op_t* op_list, size_t op_count, i2c_transact_callback callback, void* cookie) const { | |
| [135] ops_->transact(ctx_, op_list, op_count, callback, cookie); | |
| [136] } | |
| ``` | |
| Notice how the C++ is similar to the C: that's because the generated code includes the | |
| C header file as part of the C++ header file. | |
| The transaction callback has the following arguments: | |
| Argument | Meaning | |
| -----------|---------------------------------------- | |
| `ctx` | the cookie | |
| `status` | status of the asynchronous response (provided by callee) | |
| `op_list` | the data from the transfer | |
| `op_count` | the number of elements in the transfer | |
| How is this different than just using the `ddk-callback` `[Layout]` attribute we | |
| discussed above? | |
| First, there's no `struct` with the callback and cookie value in it, they're inlined | |
| as arguments instead. | |
| Second, the callback provided is a "one time use" function. | |
| That is to say, it should be called once, and only once, for each invocation of the | |
| protocol method it was supplied to. | |
| For contrast, a method provided by a `ddk-callback` is a "register once, call | |
| many times" type of function (similar to `ddk-interface` and `ddk-protocol`). | |
| For this reason, `ddk-callback` and `ddk-interface` structures usually have | |
| paired **register()** and **unregister()** calls in order to tell the parent device | |
| when it should stop calling those callbacks. | |
| > One more caveat with `[Async]` is that its callback *MUST* be called for each | |
| > protocol method invocation, and the accompanying cookie must be provided. | |
| > Failure to do so will result in undefined behavior (likely a leak, deadlock, | |
| > timeout, or crash). | |
| Although not the case currently, C++ and future language bindings (like Rust) | |
| will provide "future" / "promise" style based APIs in the generated code, built on top of | |
| these callbacks in order to prevent mistakes. | |
| > Ok, one more caveat with `[Async]` — the `[Async]` attribute applies *only* | |
| > to the immediately following method; not any other methods. | |
| # Banjo Mocks | |
| Banjo generates a C++ mock class for each protocol. This mock can be passed to protocol users in | |
| tests. | |
| ## Building | |
| Tests in Zircon get the mock headers automatically. Tests outsize of Zircon must depend on the | |
| protocol target with a `_mock` suffix, e.g. | |
| `//zircon/public/banjo/ddk.protocol.gpio:ddk.protocol.gpio_mock`. | |
| ## Using the mocks | |
| Test code must include the protocol header with a `mock/` prefix, e.g. | |
| `#include <mock/ddktl/protocol/gpio.h>`. | |
| Consider the following Banjo protocol snippet: | |
| ```banjo | |
| [021] [Layout = "ddk-protocol"] | |
| [022] protocol Gpio { | |
| ... | |
| [034] /// Gets an interrupt object pertaining to a particular GPIO pin. | |
| [035] GetInterrupt(uint32 flags) -> (zx.status s, handle<interrupt> irq); | |
| ... | |
| [040] }; | |
| ``` | |
| Here are the corresponding bits of the mock class generated by Banjo: | |
| ```c++ | |
| [034] class MockGpio : ddk::GpioProtocol<MockGpio> { | |
| [035] public: | |
| [036] MockGpio() : proto_{&gpio_protocol_ops_, this} {} | |
| [037] | |
| [038] const gpio_protocol_t* GetProto() const { return &proto_; } | |
| ... | |
| [065] virtual MockGpio& ExpectGetInterrupt(zx_status_t out_s, uint32_t flags, zx::interrupt out_irq) { | |
| [066] mock_get_interrupt_.ExpectCall({out_s, std::move(out_irq)}, flags); | |
| [067] return *this; | |
| [068] } | |
| ... | |
| [080] void VerifyAndClear() { | |
| ... | |
| [086] mock_get_interrupt_.VerifyAndClear(); | |
| ... | |
| [089] } | |
| ... | |
| [117] virtual zx_status_t GpioGetInterrupt(uint32_t flags, zx::interrupt* out_irq) { | |
| [118] std::tuple<zx_status_t, zx::interrupt> ret = mock_get_interrupt_.Call(flags); | |
| [119] *out_irq = std::move(std::get<1>(ret)); | |
| [120] return std::get<0>(ret); | |
| [121] } | |
| ``` | |
| The MockGpio class implements the GPIO protocol. `ExpectGetInterrupt` | |
| is used to set expectations on how `GpioGetInterrupt` is called. `GetProto` is used to get the | |
| `gpio_protocol_t` that can be passed to the code under test. This code will call `GpioGetInterrupt` | |
| which will ensure that it got called with the correct arguments and will return the value specified | |
| by `ExpectGetInterrupt`. Finally, the test can call `VerifyAndClear` to verify that all expectations | |
| were satisfied. Here is an example test using this mock: | |
| ```c++ | |
| TEST(SomeTest, SomeTestCase) { | |
| ddk::MockGpio gpio; | |
| zx::interrupt interrupt; | |
| gpio.ExpectGetInterrupt(ZX_OK, 0, zx::move(interrupt)) | |
| .ExpectGetInterrupt(ZX_ERR_INTERNAL, 100, zx::interrupt()); | |
| CodeUnderTest dut(gpio.GetProto()); | |
| EXPECT_OK(dut.DoSomething()); | |
| ASSERT_NO_FATAL_FAILURES(gpio.VerifyAndClear()); | |
| } | |
| ``` | |
| ### Equality operator overrides | |
| Tests using Banjo mocks with structure types will have to define equality operator overrides. For | |
| example, for a struct type `some_struct_type` the test will have to define a function with the | |
| signature | |
| ```c++ | |
| bool operator==(const some_struct_type& lhs, const some_struct_type& rhs); | |
| ``` | |
| in the top-level namespace. | |
| ### Custom mocks | |
| It is expected that some tests may need to alter the default mock behavior. To help with this, all | |
| expectation and protocol methods are `virtual`, and all `MockFunction` members are `protected`. | |
| ### Async methods | |
| The Banjo mocks issue callbacks from all async methods by default. |