fuchsia學習-banjo-tutorial.md(上)

banjo教程

本文檔是zircon驅動開發套件[Zircon Driver Development Kit]文檔的一部分內容。

總覽

Banjo是一個轉換編碼器。一個將fidl定義的接口語言轉換成目標語言的程序(例如*.fidl轉換成*.c,*.cpp,*.java等)。

詳細內容可以參考:

https://fuchsia.googlesource.com/fuchsia/+/master/docs/development/languages/fidl/README.md。

本教程由以下內容構成:

  1. Banjo簡要概述
  2. 簡單示例(I2C)
  3. 解釋從示例到生成代碼

同時,這裏還包含一些參考,比如:一些內建關鍵字和原始類型。

概述

Banjo同時生成協議制定者和協議使用者的C和C++代碼。

 

示例

第一步,讓我們來看一個相對簡單的Banjo規範。在文件中zircon/system/banjo/ddk-protocol-i2c/i2c.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] };

該文件定義了一個允許應用程序通過I2C總線讀寫數據的接口。在I2C總線中,必須先寫數據到設備請求應答。如果得到了應答,那麼就可以從設備讀取數據了。

我們來一行一行地看一下這些代碼:

第5行,`library`直接告訴Banjo編譯器需要添加什麼前綴到輸出的產物。可以認爲它是一個域名識別符。

第7行,`using`直接告訴Banjo編譯器去包含’zx’庫。

第9~10行,會被程序使用到的兩個常量。

第13~17行,定義了一個`I2cOp`結構體,程序會使用該結構體完成和總線的數據發送和接收。

第19~32行,定義了Banjo規範的接口方法。我們會在接口部分內容做更詳細的介紹。

不要被21~26行的註釋困惑了,它僅僅是註釋而已,只不過它用了“///”,普通註釋是“//”。它將被生成到目標源碼中。在目標源碼中“///”會變成“//”。

 

操作結構

在我們I2C例子中,結構體`struct I2cOp`定義了以下三個元素:

元素

類型

使用

Data

Vector<voidptr>

包含發送到總線,並且可以從總線接收的數據

Is_read

Bool

表示讀取功能所需的標誌

Stop

Bool

操作後應該發送一個表示停止字節所需的標誌

 

該結構定義了通信區域,應用於協議實現者(通常是驅動程序)和協議使用者(使用總線的應用程序)。

接口

更有趣的部分內容是’protocol’規範。

我們現在將會跳過19行的`[Layout]`,和27行的`[Async]`屬性,不過在後面專講屬性[Attributes]的時候,會反回到這兩項內容。

本協議定義了三個接口方法:

* `Transact` ——辦理

* `GetMaxTransferSize` ——獲取最大傳輸大小

* `GetInterrupt` ——獲取中斷

這裏並沒詳細講解I2C內部操作(畢竟不是I2C總線的教程),直接看他們怎樣轉換成目標語言的。

我們會分開查看c和c++的實現,使用c語言描述包含結構體的定義和c++是相同的。

>現在支持生成c和c++語言代碼,計劃將來支持RUST語言。

banjo對應的c語言版本

c語言的實現相對比較直接了當:

* `struct`s和`union`s幾乎直接映射到他們的C語言對應關鍵字

* `enum`s和常量生成爲`#define`宏

* `protocol`s生成爲兩個`struct`s

1、a function table——函數功能表

2、一個指向函數表和上下文的結構體指針

* 也會生成一些輔助函數

c語言版本的文件生成在:

`//zircon/build-`_TARGET_`/system/banjo/ddk-protocol-i2c/gen/include/ddk/protocol/i2c.h`。TARGET是目標架構,比如arm64,x86等。

文件範例

本範例比較長,我們分幾個部分來查看。

第一部分,樣版頭,我們看到之前說的”///”變成了”//”。

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

第二部分,進一步聲明結構體和函數。

[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行,只聲明瞭類型,並沒有定義實際的結構體和該協議類型的函數。

注意文件*.banjo的第12行,經過編譯轉換後,到生成文件(這裏是*.h)的第23行。原banjo文件被去掉了一個’/’符號,使得生成的文件的註釋符合常規格式。

生成文件的第24~29行,結構體struct i2c_op幾乎是從原banjo文件直接複製映射過來的。

精明的C程序員將立即看到如何在C中處理C ++樣式`vector <voidptr> data`(原始`.banjo`文件行`[14]`):它被轉換爲指針(“`data_buffer`”)和大小(“`data_size`”)。

以命名而言,基礎名字是data(由banjo文件提供)。對於voidptr的向量,轉譯器增加了”_buffer”和”_size”,將向量轉換成c語言兼容的結構體。對於其他向量類型,轉譯器增加”_list”和”_count”作爲替換。

第三部分,常量。

接下來,我們看到`const uint32`常量轉換成了`#define`語句。如下:

[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`的原因:

  1. `#define`語句只存在於編譯時間,在使用的地方會被內聯使用。
  2. `#define`允許在編譯時間做優化處理。(比如,使用常量做數學運算)

缺點是我們沒有類型安全性,這就是你看到輔助宏的原因(比如上面的,UINT32_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`創建了一個包含三個協議方法的結構體定義,這些協議方法是我們之前在banjo文件中定義的,見原banjo文件的第28,30,31行。

注意已經發生重整的名字。這個可以讓你怎樣把協議方法名字和c語言函數指針映射關係對應起來,以便於知道他們調用的什麼。

下一步,接口定義封裝在上下文結構中,

[41] struct i2c_protocol {
[42]     i2c_protocol_ops_t* ops;
[43]     void* ctx;
[44] };

現在,banjo中21~26行的註釋變得更有意義。

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

最後,我們看到實際生成的三個方法的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文件的第5行),被轉譯成了頭文件包含路徑。比如:

`library ddk.protocol.i2c`    ——》    `<ddk/protocol/i2c.h>`

 

 

 

 

 

 

 

 

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