Banjo教程

本文档是[Zircon驱动程序开发工具包](overview.md)文档的一部分。

[TOC]

Banjo是一个“转换器”(就像[FIDL的
`fidlc`](https://fuchsia.googlesource.com/docs/+/master/development/languages/fidl/README.md))
—将接口定义语言(** IDL **)转换为目标语言文件的程序。

本教程的结构如下:

*Banjo的简要概述
*简单示例(I2C)
*来自示例的生成代码的说明

还有一个参考部分,其中包括:*内置关键字和基元类型的列表。

#概述

Banjo生成可由协议实现者和协议用户使用的C和C++代码。

#一个简单的例子

作为第一步,让我们来看一个相对简单的Banjo规范。
这是文件[`//zircon/system/banjo/ddk-protocol-i2c/i2c.banjo`](https://fuchsia.googlesource.com/zircon/+/master/system/banjo/ddk-protocol-i2c/i2c.banjo):
>请注意,本教程中代码示例中的行号不是文件的一部分。

```Banjo
[01] //版权所有2018 The Fuchsia Authors。版权所有。
[02] //使用此源代码由BSD样式的许可证管理
[03] //在LICENSE文件中找到。
[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] interface 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] };
```
它定义了一个允许应用程序在I2C总线上读写数据的接口。
在I2C总线中,必须首先将数据写入设备才能请求一个回应。
如果需要响应,则可以从设备读取响应。
(例如,设置只写寄存器时可能不需要响应。)

让我们逐行查看各个组件:

*`[05]`&mdash; `library`指令告诉Banjo编译器它应该用什么输出前缀;将其视为命名空间说明符。
*`[07]`&mdash; `using`指令告诉Banjo包含`zx`库。
*`[09]`和`[10]`&mdash;这些引入了两个常量供程序员使用。
*`[13` ..`17]`&mdash;这些定义了一个名为`I2cOp`的结构体,程序员将其用于与总线之间传输数据。
*`[19` ..`32]`&mdash;这些行定义了由Banjo规范提供的接口方法;我们将在[下面](#the-interface)中更详细地讨论这个问题。

>不要对“[21` ..`26]`(以及其他地方)的注释感到困惑&mdash;这些注释(“flow through”)会发布到生成的源码中。
>任何以“///`”(三个!斜杠)开头的注释都是“flow through”注释。
>普通注释(即“//”)用于当前模块。
>当我们查看生成的代码时,这将变得清晰。

##操作结构

在我们的I2C示例中,`struct I2cOp`结构定义了三个元素:

Element    | Type                | Use
---------- | ------------------- | ------------------- -------------------
`data`     | `vector <voidptr>`  |包含发送到总线并且可选地从总线接收的数据
`is_read`  | `bool`              |表示所需读取函数的标志
`stop`     | `bool`              |操作后应该发送表示停止字节的标志

该结构定义了将在协议实现(驱动程序)和协议用户(使用总线的程序)之间使用的通信区域。

##接口

更有趣的部分是`interface`规范。

我们现在跳过`[Layout]`(line` [19]`)和`[Async]`(line` [27]`)属性,
但是会在[Attributes](#attributes)中详细介绍。

`interface`部分定义了三种接口方法:

*`Transact`
*`GetMaxTransferSize`
*`GetInterrupt`

没有详细介绍他们的内部操作(这毕竟不是一个I2C教程),让我们看看他们如何翻译成目标语言。
我们将分别查看C和C++实现,使用C描述包含结构定义,同C++版本差不多的。

>目前,支持生成C和C++代码,并计划将来支持Rust。

## C
C实现相对简单:
*`struct`s和`union`s几乎直接映射到它们的C语言对应物。
*`enum`s和常量生成为`#define`宏。
*`interface`s生成为两个`struct`s:
    *函数表,和
    *一个带有指向函数表和上下文的指针的结构体。
*还会生成一些辅助函数。

生成C版本
`//zircon/build-`_TARGET_`/system/banjo/ddk-protocol-i2c/gen/include/ddk/protocol/i2c.h`,其中_TARGET_是目标体系结构,例如`arm64`。

该文件相对较长,因此我们将分几部分来看。

### Boilerplate(样板)

第一部分有一些样板,我们将在没有进一步注释的情况下展示:

```C
[01] //版权所有2018 The Fuchsia Authors。版权所有。
[02] //使用此源代码由BSD样式的许可证管理
[03] //在LICENSE文件中找到。
[04]
[05] //WARNING: THIS FILE IS MACHINE GENERATED. DO NOT EDIT.
[06] //MODIFY system/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声明

接下来是我们的结构和函数的Forward声明:

```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] };
```

注意,行`[17` ..`19]`只声明类型,它们实际上没有定义
函数的结构或原型。

注意“flow through”注释(例如原始`.banjo`文件行`[12]`)被发送到生成的代码中(上面的第[23]行),去掉一个斜杠,使它们看起来像普通的注释。

正前面所说的那样,行`[24` ..`29`]几乎直接映射来自上面的`.banjo`文件(行`[13` ..`17`])的`struct I2cOp`。

精明的C程序员会立即看到C++风格的`vector <voidptr> data`(原文
`.banjo`文件行`[14]`)在C中处理:它被转换为指针
(“`data_buffer`”)和大小(“`data_size`”)。

>就命名而言,基本名称是“data”(如`.banjo`文件中所给出的)。
>对于“voidptr”的向量,转化器附加“buffer”和“size”以将矢量转换为C兼容结构。
>对于所有其他向量类型,转化器将附加“_list”和“_count”(用于代码可读性)。

### 常数

接下来,我们将`const uint32`常量转换为`#define`语句:

接下来,我们将看到我们的“constunt32”常量转换为“define”语句:
```c
[31] #define I2C_MAX_RW_OPS UINT32_C(8)
[32]
[33] #define I2C_10_BIT_ADDR_MASK UINT32_C(0xF000)
```
在C版本中,我们选择`#define`而不是“传递”`const uint32_t`因为:
*`#define`语句仅在编译时存在,并且在每个使用点都有内联,而`const uint32_t`将嵌入二进制文件中,并且
*`#define`允许更多的编译时间优化(例如,使用常量值进行数学运算)。

缺点是我们没有获得类型安全性,这就是为什么你会看到辅助宏(比如**上面的UINT32_C()**); 他们只是将常量转换为适当的类型。

###协议结构

现在我们进入了好的部分。
```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;
```

这个`typedef`创建一个包含三个`interface`方法的结构体(定义在原始`.banjo`文件中定义的行`[28]`,`[30]`和`[31]`)。

注意已发生的名称混乱&mdash; 这是你如何映射的
`interface`方法命名为C函数指针名称,以便您知道他们被称为什么:

Banjo                | C                       | 规则
---------------------|-------------------------|---------------------------------------------------------------
`Transact`           | `transact`              | 将前导大写转换为小写
`GetMaxTransferSize` | `get_max_transfer_size` | 如上所述,并将驼峰格式转换为下划线分隔样式
`GetInterrupt`       | `get_interrupt`         | 与上面相同

接下来,接口定义包含在上下文结构中:
```c
[41] struct i2c_protocol {
[42]     i2c_protocol_ops_t* ops;
[43]     void* ctx;
[44] };
```
现在是“flow-through”注释(`.banjo`文件,行`[21` ..`26]`)突然让路更有意义!

```c
[46] //在i2c channel上写入和读取数据。 最多可以传入I2C_MAX_RW_OPS操作方法。
[47] //对于写操作,i2c_op_t.data指向要写入的数据。 要写的数据不需要
[48] //在这次调用后保存。 对于读操作,将忽略i2c_op_t.data。 
[49] //可以指定任何读写组合。 至少最后一个操作必须设置停止标志。
[50] //通过transact_cb异步返回操作的结果。
[51] // cookie参数可用于将您自己的私有数据传递给transact_cb回调。
```
最后,我们看到三个函数方法的实际生成代码:
```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] }
```

###前缀和路径

注意被添加到方法名称的前缀`i2c_`(来自接口名称``.banjo`文件行`[20]`);因此,`Transact`变为`i2c_transact`,依此类推。
这是`.banjo`名称与其C等价物之间映射的一部分。

此外,`library`名称(`.banjo`文件中的行`[05]`)被转换为
include path:所以`library ddk.protocol.i2c`意味着`<ddk/protocol/i2c.h>`的路径。

## C++

C++代码比C版稍微复杂一些。
让我们来看看。

Banjo转换器生成三个文件:
第一个是上面讨论的C文件,另外两个是在下面
`//zircon/build-`_TARGET_`/system/banjo/ddk-protocol-i2c/gen/include/ddktl/protocol/`:
*`i2c.h`&mdash;您的程序应包含的文件,以及
*`i2c-internal.h`&mdash;一个内部文件,包含在`i2c.h`中

像往常一样,_TARGET_是构建目标体系结构(例如,`x64`)。

“internal”文件包含声明和断言,我们可以安全地跳过这些声明和断言。

`i2c.h`的C++版本相当长,所以我们将以较小的部分来看待它。
这是我们将要看的内容的概述“地图”,显示每段起始的行:

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

样板文件几乎是您所期望的:

```C++
[001] //版权所有2018 The Fuchsia Authors。版权所有。
//使用此源代码由BSD样式的许可证管理
[003] //在LICENSE文件中找到。
[004]
[005] // WARNING: THIS FILE IS MACHINE GENERATED. DO NOT EDIT.
[006] //          MODIFY system/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"
```

它包括一堆DDK和OS头文件,包括:
*标题的C版本(行`[011]`,这意味着所讨论的一切
  [上面的C部分](#a-simple-example-c-1)也适用于此处,和
*生成的`i2c-internal.h`文件(行`[018]`)。

接下来是“自动生成的使用注释”部分;我们会回过头来看看[稍后](#auto_generated-comments)因为一旦我们看到它,实际的类声明会更有意义。

这两个类声明包含在DDK名称空间中:
```c++
[053] namespace ddk {
...
[150] } // namespace ddk
```
### I2cProtocolClient包装类

`I2cProtocolClient`类是`i2c_protocol_t`的简单包装器。
结构(在C include文件中定义,我们讨论过的行[41]`
[协议结构](#protocol-structures),上面)。

```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] //在i2c通道上写入和读取数据。最多可以传入I2C_MAX_RW_OPS操作。
[129] //对于写操作,i2c_op_t.data指向要写入的数据。要写的数据不需要
[130] //在这次调用后继续活着。对于读操作,将忽略i2c_op_t.data。任何读写组合
[131] //可以指定。至少最后一个操作必须设置停止标志。
[132] //通过transact_cb异步返回操作的结果。
[133] // cookie参数可用于将您自己的私有数据传递给transact_cb回调。
[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] //返回通道上读写操作的最大传输大小。
[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] };
```

有三个构造函数:
*默认的一个(`[101]`)将`ops_`和`ctx_`设置为`nullptr`,
*一个初始化器(`[103]`),它接受指向`i2c_protocol_t`结构的指针并填充
  来自他们名字的`ops_`和`ctx`_字段在结构中,和
*另一个初始化器(`[106]`)从一个`zx_device_t`中提取`ops`_和`ctx_`信息。

最后一个构造函数是首选构造函数,可以像这样使用:

```c++
ddk::I2cProtocolClient i2c(parent);
if (!i2c.is_valid()) {
  return ZX_ERR_*; // return an appropriate error
}
```

提供三个便利成员函数:
*`[117]`** GetProto()**将`ctx_`和`ops_`成员提取到协议结构中,
*`[121]`** is_valid()**返回一个`bool`,表示该类是否已经初始化
   协议,和
*`[124]`** clear()**使`ctx_`和`ops_`指针无效。

接下来,我们找到`.banjo`文件中指定的三个成员函数:
*`[134]`** Transact()**,
*`[138]`** GetMaxTransferSize()**,和
*`[141]`** GetInterrupt()**。

这些工作只是喜欢包含文件的C版本中的三个包装函数&mdash;
也就是说,它们通过相应的函数指针将参数传递给调用。

实际上,比较来自C版本的** i2c_get_max_transfer_size()**:
```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]}
```

使用上面的C++版本:
```C ++
[138] zx_status_t GetMaxTransferSize(size_t * out_size)const {
[139] return ops_->get_max_transfer_size(ctx_,out_size);
[140]}
```

正如所宣传的那样,这个类所做的就是存储操作和上下文指针以后使用,以便通过包装器调用更优雅。

>您还会注意到C++包装函数没有任何名称混乱&mdash;
>使用重言式,** GetMaxTransferSize()**是** GetMaxTransferSize()**。

### I2cProtocol mixin类

好的,这很容易。
对于下一部分,我们将讨论[mixins](https://en.wikipedia.org/wiki/Mixin)
和[CRTP&mdash;或奇怪的重复模板模式](https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern)。

让我们首先理解类的“形状”(删除注释行的目的是为了轮廓清晰):

```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] };
```

`I2CProtocol`类继承自第二个模板参数指定的基类。
如果未指定,则默认为`internal :: base_mixin`,并且不会发生特殊魔法。
但是,如果明确指定了基类,那么应该是`ddk::base_protocol`,
在这种情况下,添加了额外的断言(以便仔细检查只有一个mixin是基本协议)。
此外,特殊的DDKTL字段设置为自动注册该协议作为驱动程序触发** DdkAdd()**时的基本协议。

构造函数调用内部验证函数,** CheckI2cProtocolSubclass()**`[059]`
(在生成的`i2c-internal.h`文件中定义),它有几个** static_assert()**调用。
期望'D`类实现三个成员函数(** I2cTransact()**,** I2cGetMaxTransferSize()**和** I2cGetInterrupt()**)以使静态方法起作用。
如果它们不是由'D`提供的,那么编译器就会(在没有静态断言的情况下)产生粗糙的模板错误。
静态断言用于产生仅由人类可理解的诊断错误。

接下来,三个指向函数的操作成员(`transact`,
`get_max_transfer_size`和`get_interrupt`)是绑定的(行`[060` ..`062]`)。

最后,如果需要,`constexpr`表达式提供默认初始化。

###使用mixin类

`I2cProtocol`类可以如下使用(来自
[`//zircon/system/dev/bus/platform/platform-proxy-device.h`](https://fuchsia.googlesource.com/zircon/+/master/system/dev/bus/platform/platform-proxy-device.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] };
```

在这里,我们看到`class ProxyI2c`继承自DDK的`I2cProtocol`并提供
本身作为模板的参数&mdash;这就是“mixin”的概念。
这导致`ProxyI2c`类型在类(来自上面的`i2c.h`头文件,行`[084]`,`[088]`和`[093]`)模板定义中替换为'D`。

仅以** I2cGetMaxTransferSize()**函数为例,它是
有效地读取,就像源代码一样:

```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] }
```

这最终消除了代码中的cast-to-self样板。
这种铸造是必要的,因为类型信息在DDK边界被删除&mdash;
回想一下,上下文`ctx`是一个`void *`指针。

###自动生成的注释

Banjo自动在包含文件中生成注释,基本上总结了我们上面谈到的内容:

```c++
[020] // DDK i2c协议支持
[021] //
[022] // :: Proxies ::
[023] //
[024] // ddk::I2cProtocolClient是i2c_protocol_t的一个简单的包装器
[025] // 它没有传递给它的指针
[026] //
[027] // :: Mixins ::
[028] //
[029] // ddk::I2cProtocol是一个mixin类,它简化了编写DDK驱动程序实现i2c协议的过程
[030] //它不设置基本协议。
[031] //
[032] // ::示例::
[033] //
[034] // //实现ZX_PROTOCOL_I2C设备的驱动程序。
[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] // };
```

#使用Banjo

>Suraj说:
>>我们还需要FIDL教程和驱动程序编写教程之间的内容,
>>以描述Banjo的用法。
>>基本上,编写一个简单的协议,然后描述发布它的驱动程序
>>,以及另一个绑定它的驱动程序并使用该协议。
>>如果有意义,可以修改现有的驱动程序更多关于Banjo使用的详细信息的编写教程
>>我认为当前的驱动程序教程也专注于C的使用,并且正在获取
>>一个C++版本(使用ddktl)可能会带来最多的价值[这是
>>已经在我的工作队列中,“使用ddktl(C++ DDK包装器)的教程”-RK]。

现在我们已经看到了I2C驱动程序的生成代码,让我们来看看
我们将如何使用它。

> @@@即将完成

#参考

> @@@这是我们应该列出所有内置关键字和原始类型的地方

##属性

回想一下上面的例子,`interface`部分有两个属性;
一个`[Layout]`和`[Async]`属性。

###布局属性

`interface`之前的行是`[Layout]`属性:

```banjo
[19] [Layout = "ddk-protocol"]
[20] interface I2c {
```

该属性适用于下一个项目;所以在这种情况下,适用于整个`接口`。
每个接口只允许一个布局。

实际上目前支持3种`Layout`属性类型:

*`ddk-protocol`
*`ddk-interface`
*`ddk-callback`

为了理解这些布局类型的工作原理,我们假设我们有两个驱动程序,`A`和`B`。
驱动程序`A`产生一个设备,然后'B`连接到‘A’(使'B`成为'A`的孩子)。

如果`B`然后通过** device_get_protocol()**查询DDK的父级“协议”,
它会得到一个`ddk-protocol`。`ddk-protocol`是父对子女提供的一组回调。

协议函数之一可以是注册“反向协议”,由此子节点为父节点提供一组回调来代替。
这是一个`ddk-interface`。

从代码生成的角度来看,这两个(`ddk-protocol`和`ddk-interface`)
看起来几乎相同,除了一些轻微的命名差异(`ddk-protocol`自动将“协议”一词附加到生成的结构/类的末尾,而``ddk-interface`却没有)。

`ddk-callback`是对`ddk-interface`的略微优化,并在使用时使用接口只有一个函数。而不是生成两个结构,如:

```c
struct interface {
   void* ctx;
   inteface_function_ptr_table* callbacks;
};

struct interface_function_ptr_table {
   void (*one_function)(...);
}
```

`ddk-callback`将生成一个内联函数指针的结构:

```c
struct callback {
  void* ctx;
  void (*one_function)(...);
};
```

### Async属性

在`interface`部分,我们看到另一个属性:`[Async]`属性:

```banjo
[20] interface I2c {
...      /// comments (removed)
[27]     [Async]
```

`[Async]`属性是一种使协议消息不同步的方法。
它会自动生成一个回调类型,其中输出参数是回调的输入。
原始方法不会在其签名中指定任何输出参数。

回想一下上面的例子,我们有一个`Transact`方法:

```banjo
[27] [Async]
[28] Transact(vector<I2cOp> op) -> (zx.status status, vector<I2cOp> op);
```


当使用(如上所述)与`[Async]`属性一起使用时,意味着我们想要Banjo调用回调函数,以便我们可以处理输出数据(上面的第二个'vector <I2cOp>`,表示来自I2C总线的数据)。

这是它的工作原理。
我们通过第一个'vector <I2cOp>`参数将数据发送到I2C总线。
一段时间后,I2C总线可以响应我们的请求生成数据。
因为我们指定了`[Async]`,所以Banjo生成了带回调函数的函数作为输入。

在C中,这两行(来自`i2c.h`文件)很重要:

```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);
```

在C++中,我们有两个引用回调的地方:

```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] }
```

注意C ++是如何与C类似的:那是因为生成的代码包含了C头文件作为C ++头文件的一部分。

transaction回调具有以下参数:

Argument   | Meaning
-----------|----------------------------------------
`ctx`      | the cookie
`status`   | 异步响应的状态(由被调用者提供)
`op_list`  | the data from the transfer
`op_count` | the number of elements in the transfer

这与我们上面讨论使用`ddk-callback`` [Layout]`属性有何不同?

首先,没有带有回调和cookie值的`struct`,它们是内联的而不是作为参数。

其次,提供的回调是“一次性使用”函数。
也就是说,对于每次调用它应该被调用一次,并且只调用一次提供给它的协议方法。
相比之下,`ddk-callback`提供的方法是“注册一次,调用很多次“函数类型(类似于`ddk-interface`和`ddk-protocol`)。
出于这个原因,`ddk-callback`和`ddk-interface`结构通常都有配对** register()**和** unregister()**调用以告诉父设备什么时候应该停止调用那些回调。

>使用`[Async]`的另一个警告是,必须为每个调用它的回调协议方法调用,并且必须提供随附的cookie。
>如果不这样做将导致未定义的行为(可能是泄漏,死锁,超时或崩溃)。

虽然不是目前的情况,C ++和未来的语言绑定(如Rust)将在生成的代码中提供基于"future" / "promise"样式的API,这些回调是为了防止错误。

>好的,还有一个“[Async]”的警告&mdash; `[Async]`属性仅适用到紧接着的方法;不能是任何其他方法。
 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值