Banjo教程

    xiaoxiao2023-10-16  175

    本文档是[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)