Banjo 是一位「移民」(例如 FIDL 的
fidlc
)
— 可將介面定義語言 (IDL) 轉換為目標語言的程式
物件
本教學課程的結構如下:
- Banjo 簡介
- 簡易範例 (I2C)
- 從範例產生的程式碼
我們的參考部分也包含以下內容:
- 列出內建關鍵字和原始類型的清單
總覽
Banjo 會產生 C 和 C++ 程式碼供通訊協定實作者使用 和通訊協定使用者
簡易範例
首先,我們來看看一個相對簡單的 Banjo 規格。
以下是 //sdk/banjo/fuchsia.hardware.i2cimpl/i2cimpl.fidl
檔案:
請注意,本教學課程所有程式碼範例中的行號並不屬於檔案。
[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] @available(added=7)
[05] library fuchsia.hardware.i2cimpl;
[06]
[07] using zx;
[08]
[09] const I2C_IMPL_10_BIT_ADDR_MASK uint32 = 0xF000;
[10] /// The maximum number of I2cImplOp's that may be passed to Transact.
[11] const I2C_IMPL_MAX_RW_OPS uint32 = 8;
[12] /// The maximum length of all read or all write transfers in bytes.
[13] const I2C_IMPL_MAX_TOTAL_TRANSFER uint32 = 4096;
[14]
[15] /// See `Transact` below for usage.
[16] type I2cImplOp = struct {
[17] address uint16;
[18] @buffer
[19] @mutable
[20] data vector<uint8>:MAX;
[21] is_read bool;
[22] stop bool;
[23] };
[24]
[25] /// Low-level protocol for i2c drivers.
[26] @transport("Banjo")
[27] @banjo_layout("ddk-protocol")
[28] protocol I2cImpl {
[29] /// First bus ID that this I2cImpl controls, zero-indexed.
[30] GetBusBase() -> (struct {
[31] base uint32;
[32] });
[33] /// Number of buses that this I2cImpl supports.
[34] GetBusCount() -> (struct {
[35] count uint32;
[36] });
[37] GetMaxTransferSize(struct {
[38] bus_id uint32;
[39] }) -> (struct {
[40] s zx.Status;
[41] size uint64;
[42] });
[43] /// Sets the bitrate for the i2c bus in KHz units.
[44] SetBitrate(struct {
[45] bus_id uint32;
[46] bitrate uint32;
[47] }) -> (struct {
[48] s zx.Status;
[49] });
[50] /// |Transact| assumes that all ops buf are not null.
[51] /// |Transact| assumes that all ops length are not zero.
[52] /// |Transact| assumes that at least the last op has stop set to true.
[53] Transact(struct {
[54] bus_id uint32;
[55] op vector<I2cImplOp>:MAX;
[56] }) -> (struct {
[57] status zx.Status;
[58] });
[59] };
它定義了介面,允許應用程式讀取及寫入 I2C 匯流排資料。 在 I2C 匯流排中,資料必須先寫入裝置才能達到目的 回應。 如果需要回應,可以從裝置讀取回應。 (例如,設定僅供寫入註冊的暫存器時,可能不需要回應)。
讓我們逐一瞭解各個元件:
[05]
:library
指令會告知 Banjo 編譯器應該使用何種前置字串 產生的輸出內容;您可以將其視為命名空間指定碼[07]
:using
指令會指示 Banjo 納入zx
程式庫。[09]
[11]
和[13]
:這些常數會引入兩個常數,供程式設計師使用。[16
..23]
— 這些定義會定義程式設計人員的結構 (稱為I2cImplOp
) 再透過匯流排轉入和轉出資料。[26
..59]
— 這些行定義了由 這種 Banjo 規格我們會在下方詳細說明。
別覺得
[50
.52]
(及其他平台) 的留言,因為 「流動」發送給生成來源的註解。 任何開頭為「///
」的註解(三!斜線) 是一個「流經」留言。 一般註解 (即「//
」) 是針對目前的模組。 查看產生的程式碼時,這一點就會更清楚。
作業結構
在我們的 I2C 範例中,struct I2cImplOp
結構定義了四個元素:
元素 | 類型 | 使用 |
---|---|---|
address |
uint16 |
在公車上互動的方塊地址 |
data |
vector<voidptr> |
包含傳送至公車的資料,以及選擇從公車接收的資料 |
is_read |
bool |
標記表示需要的讀取功能 |
stop |
bool |
旗標,表示應在作業後傳送停止位元組 |
結構定義了通訊協定之間要使用的通訊區域 系統實作 (驅動程式庫) 和通訊協定使用者 (使用匯流排的程式) 的通訊協定。
介面
更有趣的部分是 protocol
規格。
我們會略過 @transport("Banjo")
(第 [26]
行) 和 @banjo_layout("ddk-protocol")
(第 [27]
行)
屬性,但會在下方的「屬性」中調整。
protocol
區段定義了五種介面方法:
GetBusBase
GetBusCount
GetMaxTransferSize
SetBitrate
Transact
如果不深入探討內部作業的細節 (這並非 接著來看看 I2C 如何翻譯成目標語言。 我們會使用 C 說明,分別瞭解 C 和 C++ 實作 ,加入 C++ 版本通用的結構定義。
目前支援產生 C 和 C++ 程式碼,並規劃提供 Rust 支援
C
C 實作相對簡單:
struct
和union
幾乎能直接對應至 C 語言對應語言。- 產生
enum
和常數時,會以#define
巨集的形式產生。 protocol
會產生兩個struct
:- 函數資料表
- 結構體,內含函式資料表和結構定義的指標。
- 系統也會產生一些輔助函式。
C 版本會生成至
$BUILD_DIR/fidling/gen/sdk/banjo/fuchsia.hardware.i2cimpl/fuchsia.hardware.i2cimpl_banjo_c/fuchsia/hardware/i2cimpl/c/banjo.h
此檔案很長,我們會分成幾個部分來看看。
樣板
第一部分有一些樣板,我們會在沒有進一步評論的情況下顯示:
[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] // Generated from the fuchsia.hardware.i2cimpl banjo file
[07]
[08] #pragma once
[09]
[10]
[11] #include <zircon/compiler.h>
[12] #include <zircon/types.h>
[13]
[14] __BEGIN_CDECLS
轉送宣告
接下來是結構和函式的前向宣告:
[16] // Forward declarations
[17] typedef struct i2c_impl_op i2c_impl_op_t;
[18] typedef struct i2c_impl_protocol i2c_impl_protocol_t;
[19] typedef struct i2c_impl_protocol_ops i2c_impl_protocol_ops_t;
...
[26] // Declarations
[27] // See `Transact` below for usage.
[28] struct i2c_impl_op {
[29] uint16_t address;
[30] uint8_t* data_buffer;
[31] size_t data_size;
[32] bool is_read;
[33] bool stop;
[34] };
請注意,[17
.. 19]
只宣告類型,實際上並未定義
函式的結構或原型
請注意註解 (例如原始 .fidl
檔案行 [15]
)
進入產生的程式碼 (上方第 [27]
行),其中一條斜線去除後,
讓這些留言看起來就像一般留言
第 [28
.. 34
行,如所言,與 struct I2cImplOp
幾乎直接對應
來自上方的 .fidl
檔案 (第 [16
.. 23
行)。
Astute C 程式設計師會立即查看 C++ 樣式 vector<voidptr> data
(原始版本)
.fidl
檔案行 [20]
) 會在 C 中處理:會轉換為指標
(「data_buffer
」) 和大小 (「data_size
」)。
在命名期間,基礎名稱為
data
(如.fidl
檔案中所示)。 如果是voidptr
的向量,轉譯器會附加_buffer
和_size
來轉換vector
轉換為 C 相容結構。 針對所有其他向量類型,轉譯器會改為附加_list
和_count
( 程式碼可讀性)。
常數
接著,我們看到 const uint32
常數已轉換為 #define
陳述式:
[20] // The maximum length of all read or all write transfers in bytes.
[21] #define I2C_IMPL_MAX_TOTAL_TRANSFER UINT32_C(4096)
[22] // The maximum number of I2cImplOp's that may be passed to Transact.
[23] #define I2C_IMPL_MAX_RW_OPS UINT32_C(8)
[24] #define I2C_IMPL_10_BIT_ADDR_MASK UINT32_C(0xF000)
在 C 版中,我們選擇了 #define
,而不是「傳遞」const uint32_t
表示法的原因:
#define
陳述式只會在編譯期間存在,且會在每個使用網站上內嵌,const uint32_t
會嵌入至二進位檔中#define
可針對編譯時間進行最佳化 (例如使用常數值進行數學運算)。
缺點是我們並未取得類型安全性,因此會顯示輔助巨集 (例如 UINT32_C());只會將常數轉換為適當的型別
通訊協定結構
現在來談談重點部分。
[36] struct i2c_impl_protocol_ops {
[37] uint32_t (*get_bus_base)(void* ctx);
[38] uint32_t (*get_bus_count)(void* ctx);
[39] zx_status_t (*get_max_transfer_size)(void* ctx, uint32_t bus_id, uint64_t* out_size);
[40] zx_status_t (*set_bitrate)(void* ctx, uint32_t bus_id, uint32_t bitrate);
[41] zx_status_t (*transact)(void* ctx, uint32_t bus_id, const i2c_impl_op_t* op_list, size_t op_count);
[42] };
這會建立結構定義,其中包含已定義於protocol
原始 .fidl
檔案 (第 [30]
、[34]
、[37]
、[44]
和 [43]
行)。
請注意,出現了複雜的名稱破壞 — 這就是透過這種方式
將 protocol
方法名稱設為 C 函式指標名稱,以便您瞭解
這些名稱:
Banjo | C | 規則 |
---|---|---|
Transact |
transact |
將首字母大寫轉換為小寫 |
GetBusBase |
get_bus_base |
如上所述,將駝峰式大小寫轉換為底線分隔樣式 |
GetBusCount |
get_bus_count |
同上 |
SetBitrate |
set_bitrate |
同上 |
GetMaxTransferSize |
get_max_transfer_size |
同上 |
接著,介面定義會納入使用情境感知結構:
[45] struct i2c_impl_protocol {
[46] i2c_impl_protocol_ops_t* ops;
[47] void* ctx;
[48] };
最後,我們會看到實際產生的這五個方法程式碼:
[53] static inline uint32_t i2c_impl_get_bus_base(const i2c_impl_protocol_t* proto) {
[54] return proto->ops->get_bus_base(proto->ctx);
[55] }
[56]
[57] // Number of buses that this I2cImpl supports.
[58] static inline uint32_t i2c_impl_get_bus_count(const i2c_impl_protocol_t* proto) {
[59] return proto->ops->get_bus_count(proto->ctx);
[60] }
[61]
[62] static inline zx_status_t i2c_impl_get_max_transfer_size(const i2c_impl_protocol_t* proto, uint32_t bus_id, uint64_t* out_size) {
[63] return proto->ops->get_max_transfer_size(proto->ctx, bus_id, out_size);
[64] }
[65]
[66] // Sets the bitrate for the i2c bus in KHz units.
[67] static inline zx_status_t i2c_impl_set_bitrate(const i2c_impl_protocol_t* proto, uint32_t bus_id, uint32_t bitrate) {
[68] return proto->ops->set_bitrate(proto->ctx, bus_id, bitrate);
[69] }
[70]
[71] // |Transact| assumes that all ops buf are not null.
[72] // |Transact| assumes that all ops length are not zero.
[73] // |Transact| assumes that at least the last op has stop set to true.
[74] static inline zx_status_t i2c_impl_transact(const i2c_impl_protocol_t* proto, uint32_t bus_id, const i2c_impl_op_t* op_list, size_t op_count) {
[75] return proto->ops->transact(proto->ctx, bus_id, op_list, op_count);
[76] }
前置字元和路徑
請注意前置字串 i2c_impl_
(來自介面名稱,.fidl
檔案行 [28]
)
加入方法名稱中;因此,Transact
成為 i2c_impl_transact
,依此類推。
這是 .fidl
名稱與 C 同等名稱之間的對應的一部分。
此外,library
名稱 (.fidl
檔案的第 [05]
行) 會轉換為
包含路徑:因此 library fuchsia.hardware.i2cimpl
表示 <fuchsia/hardware/i2cimpl/c/banjo.h>
的路徑。
C++
C++ 程式碼比 C 版本稍微複雜。 一起來看看。
Banjo 轉譯器會產生三個檔案:
第一個是上述的 C 檔案,其他兩個檔案位於
$BUILD_DIR/fidling/gen/sdk/banjo/fuchsia.hardware.i2cimpl/fuchsia.hardware.i2cimpl_banjo_c/fuchsia/hardware/i2cimpl/cpp/
i2cimpl.h
:程式應包含的檔案,i2cimpl-internal.h
:由i2cimpl.h
納入的內部檔案
「內部」檔案包含宣告和斷言,可以放心略過。
i2cimpl.h
的 C++ 版本長度相當長,我們會以更小的片段來探討。
這裡是「地圖」總覽就是我們接下來要探討的內容
數量:
Line | 章節 |
---|---|
1 | 樣板 |
20 | 自動產生的使用註解 |
61 | I2cImplProtocol 類別 |
112 | I2cImplProtocolClient 類別 |
樣板
樣板看起來會符合您的需求:
[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] // Generated from the fuchsia.hardware.i2cimpl banjo file
[007]
[008] #pragma once
[009]
[010] #include <ddktl/device-internal.h>
[011] #include <fuchsia/hardware/i2cimpl/c/banjo.h>
[012] #include <lib/ddk/device.h>
[013] #include <lib/ddk/driver.h>
[014] #include <zircon/assert.h>
[015] #include <zircon/compiler.h>
[016] #include <zircon/types.h>
[017]
[018] #include "banjo-internal.h"
這是 #include
大量 DDK 和 OS 標頭,包括:
- 標頭的 C 版本 (第
[011]
行,也就是 以上 C 一節的例子也適用於這個情況);以及 - 產生的
i2cimpl-internal.h
檔案 (第[018]
行)。
接下來是「自動產生的使用情況評論」專區;我們再回頭討論 稍後,會比較有用 實際的類別宣告內容
DDD 命名空間會包裝兩個類別宣告:
[057] namespace ddk {
...
[214] } // namespace ddk
I2cImplProtocolClient 包裝函式類別
I2cImplProtocolClient
類別是 i2c_impl_protocol_t
周圍的簡易包裝函式
結構 (如 C include 檔案第 [45]
行定義,我們剛才曾在
上述「通訊協定結構」)。
[112] class I2cImplProtocolClient {
[113] public:
[114] I2cImplProtocolClient()
[115] : ops_(nullptr), ctx_(nullptr) {}
[116] I2cImplProtocolClient(const i2c_impl_protocol_t* proto)
[117] : ops_(proto->ops), ctx_(proto->ctx) {}
[118]
[119] I2cImplProtocolClient(zx_device_t* parent) {
[120] i2c_impl_protocol_t proto;
[121] if (device_get_protocol(parent, ZX_PROTOCOL_I2C_IMPL, &proto) == ZX_OK) {
[122] ops_ = proto.ops;
[123] ctx_ = proto.ctx;
[124] } else {
[125] ops_ = nullptr;
[126] ctx_ = nullptr;
[127] }
[128] }
[129]
[130] I2cImplProtocolClient(zx_device_t* parent, const char* fragment_name) {
[131] i2c_impl_protocol_t proto;
[132] if (device_get_fragment_protocol(parent, fragment_name, ZX_PROTOCOL_I2C_IMPL, &proto) == ZX_OK) {
[133] ops_ = proto.ops;
[134] ctx_ = proto.ctx;
[135] } else {
[136] ops_ = nullptr;
[137] ctx_ = nullptr;
[138] }
[139] }
[140]
[141] // Create a I2cImplProtocolClient from the given parent device + "fragment".
[142] //
[143] // If ZX_OK is returned, the created object will be initialized in |result|.
[144] static zx_status_t CreateFromDevice(zx_device_t* parent,
[145] I2cImplProtocolClient* result) {
[146] i2c_impl_protocol_t proto;
[147] zx_status_t status = device_get_protocol(
[148] parent, ZX_PROTOCOL_I2C_IMPL, &proto);
[149] if (status != ZX_OK) {
[150] return status;
[151] }
[152] *result = I2cImplProtocolClient(&proto);
[153] return ZX_OK;
[154] }
[155]
[156] // Create a I2cImplProtocolClient from the given parent device.
[157] //
[158] // If ZX_OK is returned, the created object will be initialized in |result|.
[159] static zx_status_t CreateFromDevice(zx_device_t* parent, const char* fragment_name,
[160] I2cImplProtocolClient* result) {
[161] i2c_impl_protocol_t proto;
[162] zx_status_t status = device_get_fragment_protocol(parent, fragment_name,
[163] ZX_PROTOCOL_I2C_IMPL, &proto);
[164] if (status != ZX_OK) {
[165] return status;
[166] }
[167] *result = I2cImplProtocolClient(&proto);
[168] return ZX_OK;
[169] }
[170]
[171] void GetProto(i2c_impl_protocol_t* proto) const {
[172] proto->ctx = ctx_;
[173] proto->ops = ops_;
[174] }
[175] bool is_valid() const {
[176] return ops_ != nullptr;
[177] }
[178] void clear() {
[179] ctx_ = nullptr;
[180] ops_ = nullptr;
[181] }
[182]
[183] // First bus ID that this I2cImpl controls, zero-indexed.
[184] uint32_t GetBusBase() const {
[185] return ops_->get_bus_base(ctx_);
[186] }
[187]
[188] // Number of buses that this I2cImpl supports.
[189] uint32_t GetBusCount() const {
[190] return ops_->get_bus_count(ctx_);
[191] }
[192]
[193] zx_status_t GetMaxTransferSize(uint32_t bus_id, uint64_t* out_size) const {
[194] return ops_->get_max_transfer_size(ctx_, bus_id, out_size);
[195] }
[196]
[197] // Sets the bitrate for the i2c bus in KHz units.
[198] zx_status_t SetBitrate(uint32_t bus_id, uint32_t bitrate) const {
[199] return ops_->set_bitrate(ctx_, bus_id, bitrate);
[200] }
[201]
[202] // |Transact| assumes that all ops buf are not null.
[203] // |Transact| assumes that all ops length are not zero.
[204] // |Transact| assumes that at least the last op has stop set to true.
[205] zx_status_t Transact(uint32_t bus_id, const i2c_impl_op_t* op_list, size_t op_count) const {
[206] return ops_->transact(ctx_, bus_id, op_list, op_count);
[207] }
[208]
[209] private:
[210] i2c_impl_protocol_ops_t* ops_;
[211] void* ctx_;
[212] };
建構函式內容分為四種:
- 將
ops_
和ctx_
設為nullptr
的預設函式 ([114]
)。 - 初始化器 (
[116]
),會將指標導向i2c_impl_protocol_t
結構並填入資料 在結構中與其名稱中的ops_
和ctx
_ 欄位相同,以及 - 從
zx_device_t
擷取ops_
和ctx_
資訊的初始化器 ([119]
)。 - 初始化器 (
[130]
),但會從裝置片段取得ops_
和ctx_
。
建議使用最後兩個建構函式,而且使用方式如下:
ddk::I2cImplProtocolClient i2cimpl(parent);
if (!i2cimpl.is_valid()) {
return ZX_ERR_*; // return an appropriate error
}
ddk::I2cImplProtocolClient i2cimpl(parent, "i2c-impl-fragment");
if (!i2cimpl.is_valid()) {
return ZX_ERR_*; // return an appropriate error
}
提供三個便利的成員函式:
[171]
GetProto() 會將ctx_
和ops_
成員擷取到通訊協定結構中。[175]
is_valid() 會傳回bool
,指出類別是否已使用 通訊協定和[178]
clear() 會撤銷ctx_
和ops_
指標。
接著,請找出 .fidl
檔案中指定的四個成員函式:
[138]
GetBusBase() 和[138]
GetBusCount() 和[138]
GetMaxTransferSize() 和[138]
SetBitrate(),以及[134]
Transact()。
這些工作如同 include 檔案 C 版本的四個包裝函式, 也就是透過對應的函式指標,將引數傳遞至呼叫中。
事實上,請比較 C 版本的 i2c_impl_get_max_transfer_size():
[138] zx_status_t GetMaxTransferSize(size_t* out_size) const {
[139] return ops_->get_max_transfer_size(ctx_, out_size);
[140] }
的
[138] zx_status_t GetMaxTransferSize(size_t* out_size) const {
[139] return ops_->get_max_transfer_size(ctx_, out_size);
[140] }
如前文所述,這個類別只會儲存作業和結構定義指標, 以便稍後使用,讓透過包裝函式的呼叫會更優雅。
您也會發現,C++ 包裝函式沒有任何名稱破壞 如要使用色調,GetMaxTransferSize() 為 GetMaxTransferSize()。
I2cImplProtocol 混合類別
真是太簡單了。 下一部分將探討mixins 和 CRTPs - 或 Curlyly Recurring Template 圖案。
現在我們來瞭解這個「形狀」 用途):
[060] template <typename D, typename Base = internal::base_mixin>
[061] class I2cImplProtocol : public Base {
[062] public:
[063] I2cImplProtocol() {
[064] internal::CheckI2cImplProtocolSubclass<D>();
[065] i2c_impl_protocol_ops_.get_bus_base = I2cImplGetBusBase;
[066] i2c_impl_protocol_ops_.get_bus_count = I2cImplGetBusCount;
[067] i2c_impl_protocol_ops_.get_max_transfer_size = I2cImplGetMaxTransferSize;
[068] i2c_impl_protocol_ops_.set_bitrate = I2cImplSetBitrate;
[069] i2c_impl_protocol_ops_.transact = I2cImplTransact;
[070]
[071] if constexpr (internal::is_base_proto<Base>::value) {
[072] auto dev = static_cast<D*>(this);
[073] // Can only inherit from one base_protocol implementation.
[074] ZX_ASSERT(dev->ddk_proto_id_ == 0);
[075] dev->ddk_proto_id_ = ZX_PROTOCOL_I2C_IMPL;
[076] dev->ddk_proto_ops_ = &i2c_impl_protocol_ops_;
[077] }
[078] }
[079]
[080] protected:
[081] i2c_impl_protocol_ops_t i2c_impl_protocol_ops_ = {};
[082]
[083] private:
...
[085] static uint32_t I2cImplGetBusBase(void* ctx) {
[086] auto ret = static_cast<D*>(ctx)->I2cImplGetBusBase();
[087] return ret;
[088] }
...
[090] static uint32_t I2cImplGetBusCount(void* ctx) {
[091] auto ret = static_cast<D*>(ctx)->I2cImplGetBusCount();
[092] return ret;
[093] }
[094] static zx_status_t I2cImplGetMaxTransferSize(void* ctx, uint32_t bus_id, uint64_t* out_size) {
[095] auto ret = static_cast<D*>(ctx)->I2cImplGetMaxTransferSize(bus_id, out_size);
[096] return ret;
[097] }
...
[099] static zx_status_t I2cImplSetBitrate(void* ctx, uint32_t bus_id, uint32_t bitrate) {
[100] auto ret = static_cast<D*>(ctx)->I2cImplSetBitrate(bus_id, bitrate);
[101] return ret;
[102] }
...
[106] static zx_status_t I2cImplTransact(void* ctx, uint32_t bus_id, const i2c_impl_op_t* op_list, size_t op_count) {
[107] auto ret = static_cast<D*>(ctx)->I2cImplTransact(bus_id, op_list, op_count);
[108] return ret;
[109] }
[110] };
I2CImplProtocol
類別繼承自第二個範本參數指定的基本類別。
如未指定,就會預設為 internal::base_mixin
,不會產生特殊的神奇效果。
不過,如果已明確指定基礎類別,則應為 ddk::base_protocol
、
在此情況下,系統會新增額外斷言 (再次檢查是否只有一個組合是基本通訊協定)。
此外,特殊的 DDKTL 欄位會設為自動將這個通訊協定註冊為
驅動程式庫觸發 DdkAdd() 的基本通訊協定。
建構函式會呼叫內部驗證函式 CheckI2cImplProtocolSubclass() [32]
(在產生的 i2c-impl-internal.h
檔案中定義),其中包含數個 static_assert() 呼叫。
D
類別應實作五個成員函式 (I2cImplGetBusBase()、
I2cIImplGetBusCount()、I2cImplGetMaxTransferSize()、I2cImplSetBitrate() 和
I2cImplTransact()) 呼叫,如此靜態方法才能運作。如果它們不是由「D
」提供,則
在缺少靜態斷言的情況下,編譯器會產生嚴重範本錯誤。
靜態斷言會產生只有人類可理解的診斷錯誤,
接下來是五項函式指標作業成員 (get_bus_base
、get_bus_count
、
get_max_transfer_size
、set_bitrate
和 transact
) 繫結 (第 [065
.. 069]
行)。
最後,constexpr
運算式會視情況提供預設初始化。
使用 Mixin 類別
I2cImplProtocol
類別可以按照以下方式使用 (來自
//src/devices/i2c/drivers/intel-i2c/intel-i2c-controller.h
):
[135] class IntelI2cController : public IntelI2cControllerType,
[136] public ddk::I2cImplProtocol<IntelI2cController, ddk::base_protocol> {
[137] public:
[138] explicit IntelI2cController(zx_device_t* parent)
[139] : IntelI2cControllerType(parent), pci_(parent, "pci") {}
[140]
[141] static zx_status_t Create(void* ctx, zx_device_t* parent);
[142]
[143] void DdkInit(ddk::InitTxn txn);
...
[170] uint32_t I2cImplGetBusBase();
[171] uint32_t I2cImplGetBusCount();
[172] zx_status_t I2cImplGetMaxTransferSize(const uint32_t bus_id, size_t* out_size);
[173] zx_status_t I2cImplSetBitrate(const uint32_t bus_id, const uint32_t bitrate);
[174] zx_status_t I2cImplTransact(const uint32_t bus_id, const i2c_impl_op_t* op_list,
[175] const size_t op_count);
[176]
[177] void DdkUnbind(ddk::UnbindTxn txn);
[178] void DdkRelease();
[179]
[180] private:
...
這裡可以看到 class IntelI2cController
繼承自 DDK 的 I2cImplProtocol
,並提供
做為範本的引數 — 這就是「mixin」概念
如此一來,範本定義中的 IntelI2cController
類型就會取代 D
類別 (來自上方的 i2c-impl.h
標頭檔案、第 [086]
行、[091]
、[95]
、[100]
和
[107]
)。
看看 I2cImplGetMaxTransferSize() 函式為例, 就像讀取原始碼一樣
[094] static zx_status_t I2cImplGetMaxTransferSize(void* ctx, uint32_t bus_id, uint64_t* out_size) {
[095] auto ret = static_cast<IntelI2cController*>(ctx)->I2cImplGetMaxTransferSize(bus_id, out_size);
[096] return ret;
[097] }
這樣就能避免程式碼中出現投放至自行的樣板。
因為類型資訊會在 DDK 邊界清除,所以這是必要的轉換程序。
回想一下,結構定義 ctx
是 void *
指標。
自動產生的留言
Banjo 會在 include 檔案中自動產生註解,彙整以下內容 剛才談到:
[020] // DDK i2cimpl-protocol support
[021] //
[022] // :: Proxies ::
[023] //
[024] // ddk::I2cImplProtocolClient is a simple wrapper around
[025] // i2c_impl_protocol_t. It does not own the pointers passed to it.
[026] //
[027] // :: Mixins ::
[028] //
[029] // ddk::I2cImplProtocol is a mixin class that simplifies writing DDK drivers
[030] // that implement the i2c-impl protocol. It doesn't set the base protocol.
[031] //
[032] // :: Examples ::
[033] //
[034] // // A driver that implements a ZX_PROTOCOL_I2C_IMPL device.
[035] // class I2cImplDevice;
[036] // using I2cImplDeviceType = ddk::Device<I2cImplDevice, /* ddk mixins */>;
[037] //
[038] // class I2cImplDevice : public I2cImplDeviceType,
[039] // public ddk::I2cImplProtocol<I2cImplDevice> {
[040] // public:
[041] // I2cImplDevice(zx_device_t* parent)
[042] // : I2cImplDeviceType(parent) {}
[043] //
[044] // uint32_t I2cImplGetBusBase();
[045] //
[046] // uint32_t I2cImplGetBusCount();
[047] //
[048] // zx_status_t I2cImplGetMaxTransferSize(uint32_t bus_id, uint64_t* out_size);
[049] //
[050] // zx_status_t I2cImplSetBitrate(uint32_t bus_id, uint32_t bitrate);
[051] //
[052] // zx_status_t I2cImplTransact(uint32_t bus_id, const i2c_impl_op_t* op_list, size_t op_count);
[053] //
[054] // ...
[055] // };
使用 Banjo
我們已經看到為 I2C 驅動程式庫產生的程式碼,讓我們來看看 我們會說明如何運用該功能
@@@ 尚未完成
參考資料
@@@ 這裡,我們要列出所有內建關鍵字和原始類型
屬性
回想上述範例,protocol
區段具有兩個屬性。
@transport("Banjo")
和 @banjo_layout("ddk-protocol")
屬性。
傳輸屬性
所有 Banjo 通訊協定都必須具有 @transport("Banjo")
來表示 Banjo 是
而不是 FIDL
banjo_layout 屬性
protocol
之前的那一行是 banjo_layout
屬性:
[27] @banjo_layout("ddk-protocol")
[28] protocol I2cImpl {
這項屬性會套用至下一個項目;因此在本例中,整個 protocol
。
每個介面只能有一個版面配置。
目前支援 3 種 BanjoLayout
屬性類型:
ddk-protocol
ddk-interface
ddk-callback
為瞭解這些版面配置類型的運作方式,我們假設有兩個驅動程式
《A
》和《B
》。
驅動程式 A
會產生裝置,而 B
會附加至裝置 (使 B
設為 A
的子項)。
如果 B
,則 DDK 會查詢其父項的「通訊協定」透過 device_get_protocol() 建立
且會收到 ddk-protocol
。
ddk-protocol
是一組父項提供給其子項的回呼。
其中一個通訊協定函式可註冊「反向通訊協定」,其中
子項會提供一組回呼,讓父項改為觸發。
這是ddk-interface
。
從程式碼產生的角度來看,這兩個 (ddk-protocol
和 ddk-interface
)
看起來幾乎完全相同,只是有些微差異 (ddk-protocol
自動附加「通訊協定」一詞加入產生的結構體 / 類別結尾
而 ddk-interface
則不會)。
ddk-callback
比 ddk-interface
略小最佳化,當
介面只有一個函式
而不是產生兩個結構,例如:
struct interface {
void* ctx;
interface_function_ptr_table* callbacks;
};
struct interface_function_ptr_table {
void (*one_function)(...);
}
ddk-callback
會產生內嵌函式指標的單一結構:
struct callback {
void* ctx;
void (*one_function)(...);
};
async 屬性
如需 @async
屬性的範例,請參閱
fuchsia.hardware.block
Block
通訊協定。
在 protocol
區段中,我們看到 @async
屬性:
[254] protocol Block {
... /// comments (removed)
[268] @async
@async
屬性是讓通訊協定訊息無法同步的方法。
它會自動產生回呼類型,其中輸出引數是回呼的輸入內容。
原始方法的簽章不會指定任何輸出參數。
在上述通訊協定中,Queue
方法已宣告為:
[268] @async
[269] Queue(resource struct {
[270] @in_out
[271] txn BlockOp;
[272] }) -> (resource struct {
[273] status zx.Status;
[274] @mutable
[275] op BlockOp;
[276] });
與 @async
屬性搭配使用 (如上所述) 時,表示我們希望 Banjo
叫用回呼函式,這樣我們就能處理輸出資料 (第二
BlockOp
,代表來自區塊裝置的資料)。
以下說明運作方式
我們會透過第一個 BlockOp
引數將資料傳送至區塊裝置。
封鎖裝置一段時間後,裝置可能會依照要求產生資料。
因為我們指定 @async
,Banjo 會產生用來擷取回呼函式的函式
做為輸入內容
在 C 中,這兩行 (來自 block.h
檔案) 很重要:
[085] typedef void (*block_queue_callback)(void* ctx, zx_status_t status, block_op_t* op);
...
[211] void (*queue)(void* ctx, block_op_t* txn, block_queue_callback callback, void* cookie);
在 C++ 中,有兩個參照回呼的位置:
[113] static void BlockQueue(void* ctx, block_op_t* txn, block_queue_callback callback, void* cookie) {
[114] static_cast<D*>(ctx)->BlockQueue(txn, callback, cookie);
[115] }
和
[201] void Queue(block_op_t* txn, block_queue_callback callback, void* cookie) const {
[202] ops_->queue(ctx_, txn, callback, cookie);
[203] }
請注意,C++ 與 C 的類似,這是因為產生的程式碼包含 C 標頭檔案。
交易回呼包含下列引數:
引數 | 意義 |
---|---|
ctx |
Cookie |
status |
非同步回應的狀態 (由受呼叫者提供) |
op |
轉移的資料 |
這和我們直接使用 @banjo_layout("ddk-callback")
屬性有什麼不同
該怎麼辦?
首先,回應中沒有包含回呼和 Cookie 值的 struct
,而是內嵌兩者
做為引數
其次,提供的回呼是「一次性」函式。
假設應呼叫 100 次,每次叫用均應呼叫一次
本身的通訊協定方法
相反地,ddk-callback
提供的方法屬於「註冊一次」,
次」函式類型 (類似 ddk-interface
和 ddk-protocol
)。
因此,ddk-callback
和 ddk-interface
結構通常
並配對 register() 和 unregister() 呼叫,藉此通知父項裝置
何時應停止呼叫這些回呼。
另外要注意的是,
@async
必須針對每個項目呼叫其回呼 通訊協定方法叫用,並提供相關的 Cookie。 否則會導致發生未定義的行為 (例如資訊外洩、死結、 逾時或當機)。
雖然目前並非如此,C++ 和日後的語言繫結 (例如 Rust) 將提供「未來」/「承諾」產生程式碼 (以 避免錯誤
好的,還有一個關於
@async
的注意事項,因為@async
屬性只會 隨後立即執行改用其他方法
緩衝區屬性
此屬性適用於 vector
類型的通訊協定方法參數,以表示這些參數
當做緩衝區使用實際上,只會影響產生的參數名稱。
callee_allocated 屬性
如果套用至 vector
類型的通訊協定方法輸出參數,這個屬性會傳達
向量的內容應由方法呼叫的接收器分配。
derive_debug 屬性 (僅限 C 繫結)
套用至列舉宣告時,輔助 *_to_str()
函式
將產生用於 C 繫結的 C 繫結,而每個繫結都會分別傳回 const char*
列舉值例如,列舉用此屬性宣告的列舉,例如
為
@derive_debug
enum ExampleEnum {
VAL_ONE = 1;
VAL_TWO = 2;
};
會產生以下產生的定義
#ifndef FUNC_EXAMPLE_ENUM_TO_STR_
#define FUNC_EXAMPLE_ENUM_TO_STR_
static inline const char* example_enum_to_str(example_enum_t value) {
switch (value) {
case EXAMPLE_ENUM_VAL_ONE:
return "EXAMPLE_ENUM_VAL_ONE";
case EXAMPLE_ENUM_VAL_TWO:
return "EXAMPLE_ENUM_VAL_TWO";
}
return "UNKNOWN";
}
#endif
inner_pointer 屬性
在 vector
類型的通訊協定輸入參數環境中,這項屬性會轉動
將向量轉換為物件指標,而不是物件本身
in_out 屬性
在通訊協定方法輸入參數中加入這項屬性,就能有效變更該參數 把它改造成「自飛」參數。
可變動的屬性
請使用這項屬性,將 vector
或 string
類型的 struct
/union
欄位設為可變動欄位。
命名空間屬性
此屬性適用於 const
宣告,使 C 後端在前面加上
採用大寫 FIDL 程式庫名稱的常數名稱,例如改為使用 library_name_CONSTANT_K
(共 CONSTANT_K
個)。可能需要設定這個屬性,以免名稱與 FIDL hlcpp 常數發生衝突
繫結至相同版本
out_of_line_contents 屬性
這項屬性可將 struct
/union
中 vector
欄位的內容儲存在外部
容器的映像檔
保留_c_names 屬性
此屬性適用於 struct
宣告,使其欄位成為保留名稱
在執行 C 後端時不會變更
班鳩琴
Banjo 會為每個通訊協定產生 C++ 模擬類別。這個模擬可以傳遞給 測試。
建築
在 Zircon 中的測試會自動取得模擬標頭。超過 Zircon 的測試必須依賴
加上 _mock
後置字串的通訊協定目標,例如
//sdk/banjo/fuchsia.hardware.gpio:fuchsia.hardware.gpio_banjo_cpp_mock
。
使用模擬
測試程式碼必須包含含有 -mock
後置字串的通訊協定標頭,例如:
#include <fuchsia/hardware/gpio/cpp/banjo-mock.h>
。
請參考以下 Banjo 通訊協定程式碼片段:
[20] @transport("Banjo")
[21] @banjo_layout("ddk-protocol")
[22] protocol Gpio {
...
[53] /// Gets an interrupt object pertaining to a particular GPIO pin.
[54] GetInterrupt(struct {
[55] flags uint32;
[56] }) -> (resource struct {
[57] s zx.Status;
[58] irq zx.Handle:INTERRUPT;
[59] });
...
[82] };
以下是 Banjo 產生的模擬類別對應位元:
[034] class MockGpio : ddk::GpioProtocol<MockGpio> {
[035] public:
[036] MockGpio() : proto_{&gpio_protocol_ops_, this} {}
[037]
[038] virtual ~MockGpio() {}
[039]
[040] const gpio_protocol_t* GetProto() const { return &proto_; }
...
[067] virtual MockGpio& ExpectGetInterrupt(zx_status_t out_s, uint32_t flags, zx::interrupt out_irq) {
[068] mock_get_interrupt_.ExpectCall({out_s, std::move(out_irq)}, flags);
[069] return *this;
[070] }
...
[092] void VerifyAndClear() {
...
[098] mock_get_interrupt_.VerifyAndClear();
...
[103] }
...
[131] virtual zx_status_t GpioGetInterrupt(uint32_t flags, zx::interrupt* out_irq) {
[132] std::tuple<zx_status_t, zx::interrupt> ret = mock_get_interrupt_.Call(flags);
[133] *out_irq = std::move(std::get<1>(ret));
[134] return std::get<0>(ret);
[135] }
MockGpio 類別會實作 GPIO 通訊協定。ExpectGetInterrupt
用於設定 GpioGetInterrupt
的呼叫方式。GetProto
是用來取得
gpio_protocol_t
,可傳遞至測試中程式碼。這組驗證碼會呼叫 GpioGetInterrupt
這可確保函式使用正確的引數進行呼叫,且會傳回指定的值
製作者:ExpectGetInterrupt
。最後,測試可以呼叫 VerifyAndClear
,驗證所有預期結果
以及滿意的部分以下是使用此模擬的測試範例:
TEST(SomeTest, SomeTestCase) {
ddk::MockGpio gpio;
zx::interrupt interrupt;
gpio.ExpectGetInterrupt(ZX_OK, 0, zx::move(interrupt))
.ExpectGetInterrupt(ZX_ERR_INTERNAL, 100, zx::interrupt());
CodeUnderTest dut(gpio.GetProto());
EXPECT_OK(dut.DoSomething());
ASSERT_NO_FATAL_FAILURE(gpio.VerifyAndClear());
}
等式運算子覆寫
使用具有結構類型的 Banjo 模擬測試時,必須定義等式運算子覆寫值。適用對象
例如,如果是結構體類型 some_struct_type
,測試就必須使用
簽章
bool operator==(const some_struct_type& lhs, const some_struct_type& rhs);
在頂層命名空間中
自訂模擬
部分測試可能需要修改預設模擬行為。為瞭解決這個問題
預期和通訊協定方法為 virtual
,而所有 MockFunction
成員都是 protected
。
非同步方法
根據預設,Banjo 會從所有非同步方法發出回呼。