banjo教程
本文檔是zircon驅動開發套件[Zircon Driver Development Kit]文檔的一部分內容。
總覽
Banjo是一個轉換編碼器。一個將fidl定義的接口語言轉換成目標語言的程序(例如*.fidl轉換成*.c,*.cpp,*.java等)。
詳細內容可以參考:
https://fuchsia.googlesource.com/fuchsia/+/master/docs/development/languages/fidl/README.md。
本教程由以下內容構成:
- Banjo簡要概述
- 簡單示例(I2C)
- 解釋從示例到生成代碼
同時,這裏還包含一些參考,比如:一些內建關鍵字和原始類型。
概述
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`的原因:
- `#define`語句只存在於編譯時間,在使用的地方會被內聯使用。
- `#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>`