fuchsia学习-banjo-tutorial.md(下)

C++语言风格

    C++比c语言版本稍微复杂一些。让我们来看一下。

  • banjo转译器生成三个文件

1、第一个文件在c语言版本中已经介绍讨论过了,其他两个文件在目录`//zircon/build-`_TARGET_`/system/banjo/ddk-protocol-i2c/gen/include/ddktl/protocol/`。

2、`i2c.h`——你的程序需要包含的文件。

3、`i2c-internal.h`——内部文件,由i2c.h文件包含。

通常,_TARGET_ 是目标构建架构,比如x64,arm64等。

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

C++版本的`i2c.h`文件内容是相当的长,所以我们需要将他分成小片段来查看。下面是文件i2c.h概览图,显示了从开始到每个片段的行数。

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)
  • 样板

这份样板的内容几乎就是你所期望的:

[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 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"

它的#include包含了一堆ddk和os的头文件,i2c相关的包含:

C版本的头文件(`[011]`行,这意味着所讨论的所有内容[上面的C部分](#a-simple-example-c-1)也适用于此处);还有生成的`i2c-internal.h`文件,在[018]行。

接下来是自动生成的使用注释部分;我们稍后将回到自动生成注释的地方(#auto_generated-comments),因为一旦我们看到实际代码的类声明,这个注释它会显得更有意义。

这两个类的声明被封装在DDK的域名中,如代码所示:

[053] namespace ddk {

...

[150]} // namespace ddk

  • I2cProtocolClient封装类

I2cProtocolClient类是i2c_protocol_t结构体的简单封装,该结构体定义在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] };

上面代码有三个构造函数。

    101行`ops_` 和 `ctx_`被赋值为`nullptr`。

    103行的初始化函数使用结构体指针`i2c_protocol_t`,构建`ops_` 和 `ctx_`的对应值。

    106行另一个初始化函数,从父设备`zx_device_t`获取`ops_` 和 `ctx_`值。

第三个构造函数是推荐使用的,使用示例如下:

ddk::I2cProtocolClient i2c(parent);

if (!i2c.is_valid()) {

  return ZX_ERR_*; // return an appropriate error

}

提供了三个所需要的成员函数:

    117行GetProto()获取`ops_` 和 `ctx_`成员到对应的协议结构体中。

    121行is_valid()返回一个布尔值,判断是否该类已由一个协议初始化。

    124行clear()清除`ops_` 和 `ctx_`成员的指针赋值信息。

接着,我们发现曾在banjo文件中的三个成员函数:

    134行的Transact()

    138行的GetMaxTransferSize()

    141行的GetInterrupt()

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

事实上,对比c语言版本的i2c_get_max_transfer_size()函数:

    [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++版本的函数:

    [138] zx_status_t GetMaxTransferSize(size_t* out_size) const {

    [139]   return ops_->get_max_transfer_size(ctx_, out_size);

    [140] }

    正如所宣传的那样,这个类所做的就是存储操作和上下文指针以供以后使用,这样通过包装器的调用就更优雅了。您还会注意到C ++包装器函数没有任何名称重整;要使用同名式,GetMaxTransferSize()是GetMaxTransferSize()。

  • The I2cProtocol

这是容易的部分。对此的下部分章节,我们将在https://en.wikipedia.org/wiki/Mixin讲解。让我们先来理解类的形状。代码如下:

[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类继承自base类,base由第二个模板参数指定。如果没有人为指定,它将默认使用internal::base_mixin类,并且没有特别的模数产生。然而,如果要显示的指定该base,应该使用ddk::base_protocol类,这会把asserts检查机制加上,双重检查只有一个base protocol混合型。此外,特殊的DDKTL字段设置为在驱动程序触发DdkAdd()时自动将此协议注册为基本协议。

    构造函数会调用内部有效性函数,在59行CheckI2cProtocolSubclass()。定义在头文件i2c-internal.h,它有几种静态断言调用。

    ‘D’类被期望去实现三个成员函数使静态方法有效工作:I2cTransact()、I2cGetMaxTransferSize()、I2cGetInterrupt()。如果没有提供‘D’,编译器会产生一些模板错误。

    静态断言用于产生描述不清的错误信息,一般人不能理解。

    接下来,在60~62行,指针函数(`transact`,`get_max_transfer_size`,`get_interrupt`)被绑定了。最后,如果需要,`constexpr`表达式提供默认初始化。

  • 使用mixin类

    I2cProtocol类可以按照如下方式使用。来自文件:`//zircon/system/dev/bus/platform/platform-proxy-device.h`。

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

    我们看到ProxyI2c类继承自I2cProtocol类,并将自身ProxyI2c作为I2cProtocol的模板参数。这就是Mixin的概念。这导致`ProxyI2c`类型在类的模板定义中替换为'D`。(来自84,88,93行的i2c.h文件)。

    作为例子,看一下I2cGetMaxTransferSize()函数,阅读源码发现它简洁高效:

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

    这最终消除了代码中的指向自己的样板。这种转换是必要的,因为类型信息在DDK边界被擦除;回想一下上下文`ctx`是一个`void *`指针。 

  • 自动生成注释

    banjo会在头文件中自动生成注释,基本总结了写了什么内容。例如:

[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] // };
  • 使用banjo

    我们还需要FIDL教程和驱动程序编写教程之间的内容,以描述banjo的使用。基本上就是,编写一个简单的协议,然后描述对应它的驱动程序。另一个在协议上绑定并使用该协议的驱动程序。如果有意义的话,可以修改现有的驱动程序编写教程,以获得有关banjo使用的更多详细信息。我认为当前的驱动程序教程也专注于C的使用,而获得C ++版本(使用ddktl)可能会带来最大的价值。这个已经在我的工作队列中,“使用ddktl教程”(C++ DDK wrappers)。

    现在我们看见生成的I2C驱动代码,让我们来看一下怎么使用它。

(原英文文档待完成。。。)

    参考:这里主要列出内建关键字和原语类型。

    属性:例子中的protocol部分有两个属性[layout]和[async]。

Layout
目前有三个可选类型,
* `ddk-protocol`
* `ddk-interface`
* `ddk-callback`

在protoco的前一行就是layout属性,代码如下:

    [19] [Layout = "ddk-protocol"]

    [20] protocol I2c {

    为了理解layout类型怎么工作,我们现在假设有两个驱动A和B。A驱动产出一个设备,然后B连接到该设备上,使得B作为A的子。如果B通过device_get_protocol()获取DDK它的父协议,B会得到一个ddk协议,`ddk-protocol`。`ddk-protocol`就是一组回调的集合,由父设备提供给子设备。

    其中一个协议功能可以是注册一个“反向协议”,由此子进程为父进程提供一组回调来代替。这就是ddk接口,`ddk-interface`。

    从生成的代码来看,`ddk-protocol`和`ddk-interface`看起来大致相同,除了名字看上去有细微的差异。(`ddk-protocol`会自动将字符"protocol"附加到生成的结构体或者类字符的后面,`ddk-interface`却不会这么做)。

    `ddk-callback`是对`ddk-interface`进行了稍微优化,用于当`ddk-interface`只有一个函数接口的时候。并不会生成两个结构体,如下:

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

struct interface_function_ptr_table {
   void (*one_function)(...);
}
`ddk-callback`只会生成一个带有函数指针成员的结构体,如下:
struct callback {
  void* ctx;
  void (*one_function)(...);
};
  • 异步属性

    在 有protocol的一行,我们可以看到另一种属性:异步属性。 

    [20] protocl I2c {

     ...      /// comments (removed)

    [27]    [Async]

    异步属性是一种,使协议消息不同步的方式。它会自动生成一个回调类型,其中输出参数是回调的输入。原始方法不会在其签名中指定任何输出参数。回想一下上面的例子,我们有一个`Transact`方法。

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

    当和`[Async]`属性一起使用时,意味着,我们希望banjo调用一个回调函数,以便我们处理输出数据(如上面代码的`vector<I2cOp>`参数,表示来自I2C总线的数据)。

  • 它怎么工作的

    我们通过第一个参数`vector<I2cOp>`发送数据到I2C总线。一段时间后,I2C总线可能会发出数据作为我们请求的回应。由于我们设置了异步属性,banjo产生一个带有回调函数作为输入参数的函数。

    C语言版本中,`i2c.h`头文件中这两行比较重要:

[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++版本中,我们涉及到两处回调函数:

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

和 

[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`   | status of the asynchronous response (provided by callee)
`op_list`  | the data from the transfer
`op_count` | the number of elements in the transfer

这里和我们上面提到的`ddk-callback` `[Layout]`属性有什么不同呢?

第一、这里没有包含callback和cookie成员的结构体,他们被当作内联参数传递进去了。

第二、提供的回调时一次性使用功能。

就是说,它能且只能被调用一次它所提供的协议方法。

    相比之下,`ddk-callback`的方法是一种注册一次,调用多次的函数类型(类似于`ddk-interface`和`ddk-protocol`)。因此,`ddk-callback` 和 `ddk-interface`结构体,通常成对调用register()和unregister(),告诉父设备什么时候应该调用回调函数。

    另外`[Async]`需要提醒的是,回调必须通过协议方法调用,并且需要提供同行的cookie。不这样做,可能会导致未定义的行为(比如内存泄漏、死锁、超时或者崩溃)。

    虽然不是目前的情况,C ++和未来的语言绑定(如Rust)将在生成的代码中提供基于“未来”/“承诺”样式的API这些回调是为了防止错误。还有一个关于“[异步]”的警告; `[Async]`属性仅适用于紧随其后的方法; 没有任何其他方法。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章