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]`屬性僅適用於緊隨其後的方法; 沒有任何其他方法。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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