驅動程式繫結

如要編寫繫結規則,請參閱繫結規則教學課程

在 Fuchsia 中,驅動程式庫架構會在系統中維護驅動程式和裝置的樹狀結構。在這個樹狀結構中,裝置代表 OS 可用的某些硬體存取權。驅動程式會發布並繫結至裝置。舉例來說,USB 驅動程式庫可能會繫結至 PCI 裝置 (其父項),並發布乙太網路裝置 (其子項)。為判斷驅動程式庫可繫結的裝置,每個驅動程式庫都有繫結規則,每個裝置則有一組屬性。繫結規則會定義條件,以比對要繫結的裝置屬性。

繫結規則和所參照的條件是由網域專屬語言定義。繫結編譯器會使用這項語言,並產生繫結規則的位元碼。這個語言有兩種來源檔案:規則和程式庫。程式庫用於在驅動程式之間共用屬性定義,以及繫結規則。編譯器也會從繫結程式庫產生 FIDL 檔案,讓驅動程式可在程式碼中參照裝置屬性。

請注意,在這個遷移階段,系統不支援在繫結程式庫中定義裝置屬性鍵 (請參閱下文)。舊版驅動程式庫繫結系統 (lib/ddk/binding.h) 中的鍵則可擴充。這些鍵會硬式編碼至繫結編譯器,並位於 fuchsia 命名空間下。舉例來說,PCI 供應商 ID 金鑰為 fuchsia.BIND_PCI_VID。最終,硬式編碼鍵會從這個命名空間中移除,所有節點屬性鍵都會在繫結程式庫中定義。

編譯器

編譯器會採用程式庫來源清單和一個規則來源。例如:

fx bindc compile \
  --include src/devices/bind/fuchsia.usb/fuchsia.usb.bind \
  --output tools/bindc/examples/gizmo.h \
  tools/bindc/examples/gizmo.bind

目前會產生驅動程式庫可納入的 C 標頭檔案。標頭檔案會定義巨集:

ZIRCON_DRIVER(Driver, Ops, VendorName, Version);
  • Driver 是驅動程式庫名稱。
  • Opszx_driver_ops,也就是驅動程式庫作業掛鉤
  • VendorName 是代表驅動程式庫供應商名稱的字串。
  • Version 是代表驅動程式庫版本的字串。

詳情請參閱驅動程式庫開發說明文件

繫結規則

繫結規則會定義呼叫驅動程式庫 bind() 勾點的條件。繫結規則中的每個陳述式都是裝置屬性的條件,必須為真,驅動程式庫才能繫結。如果繫結規則執行完畢且所有條件都成立,裝置協調器就會呼叫驅動程式庫 bind() 勾點。

繫結規則應視為驅動程式庫應繫結的條件宣告式運算式。因此,條件運算式的執行順序與最終評估無關。您可以將繫結規則視為布林公式。

陳述式分為四種:

  • 條件陳述式<key> == <value> (或 <key> != <value>) 形式的等式 (或不等式) 運算式。
  • 接受陳述式是特定鍵的允許值清單。
  • If 陳述式可提供簡單的分支。
  • True 和 false 陳述式可用於明確評估繫結規則。

範例

這個範例繫結規則位於 //tools/bindc/examples/gizmo.bind

using fuchsia.usb;

// The device must be a USB device.
fuchsia.BIND_PROTOCOL == fuchsia.usb.BIND_PROTOCOL.INTERFACE;

if fuchsia.BIND_USB_VID == fuchsia.usb.BIND_USB_VID.INTEL {
  // If the device's vendor is Intel, the device class must be audio.
  fuchsia.BIND_USB_CLASS == fuchsia.usb.BIND_USB_CLASS.AUDIO;
} else if fuchsia.BIND_USB_VID == fuchsia.usb.BIND_USB_VID.REALTEK {
  // If the device's vendor is Realtek, the device class must be one of the following values:
  accept fuchsia.BIND_USB_CLASS {
    fuchsia.usb.BIND_USB_CLASS.COMM,
    fuchsia.usb.BIND_USB_CLASS.VIDEO,
  }
} else {
  // If the vendor is neither Intel or Realtek, do not bind.
  false;
}

語言限制

為提升易讀性,並確保繫結規則能簡單呈現驅動程式庫應繫結的條件,我們對語言設下一些限制。

  • 不得留空。 空區塊應表示驅動程式庫會繫結或中止,這點並不明確。作者應使用明確的 truefalse 陳述式。

  • If 陳述式必須有 else 區塊,且為終端機。 這項限制會明確指出執行分支,進而提升可讀性。由於 if 陳述式後方不得有任何陳述式,因此很容易追蹤繫結規則的路徑。

  • 是非陳述式必須是其範圍內唯一的陳述式。繫結規則並非必要程式,評估順序也不重要。如果將布林陳述式 (尤其是 true) 與其他條件混用,可能會導致不明確的情況。

文法

rule = using-list , ( statement )+ ;

using-list = ( using , ";" )* ;

using = "using" , compound-identifier , ( "as" , IDENTIFIER ) ;

statement = condition , ";" | accept | if-statement | true | false ;

condition = compound-identifier , condition-op , value ;

condition-op = "==" | "!=" ;

accept = "accept" , compound-identifier , "{" ( value , "," )+ "}" ;

if-statement = "if" , condition , "{" , ( statement )+ , "}" ,
                ( "else if" , "{" , ( statement )+ , "}" )* ,
                "else" , "{" , ( statement )+ , "}" ;

true = "true" , ";" ;

false = "false" , ";" ;

compound-identifier = IDENTIFIER ( "." , IDENTIFIER )* ;

value = compound-identifier | STRING-LITERAL | NUMERIC-LITERAL | "true" | "false" ;

識別碼符合規則運算式 [a-zA-Z]([a-zA-Z0-9_]*[a-zA-Z0-9])?,且不得與任何關鍵字相符。關鍵字清單如下:

accept
as
else
false
if
true
using

字串常值會比對規則運算式 ”[^”]*”,數字常值則會比對規則運算式 [0-9]+0x[0-9A-F]+

繫結編譯器會忽略 (視為空白字元) 以 // 為前置字元的任何行,以及以 /**/ 分隔的任何多行。

複合式繫結

除了將驅動程式繫結至裝置,Fuchsia 中的驅動程式也可以使用繫結規則,從節點建立複合裝置。繫結規則遵循與非複合繫結相同的語言規格,但會分成包含名稱和一組陳述式的父項。

繫結規則中只能有一個主要父項。複合式驅動程式庫會在與主要父項相同的驅動程式代管程序中啟動。

複合式繫結規則檔案範例位於 //tools/bindc/examples/composite-gizmo.bind

composite gizmo_pci;

using fuchsia.pci;
using fuchsia.platform;
using fuchsia.tee;

primary parent "pci" {
  fuchsia.BIND_PROTOCOL == fuchsia.pci.BIND_PROTOCOL.DEVICE;
}

parent "tee" {
  if fuchsia.BIND_PROTOCOL == fuchsia.tee.BIND_PROTOCOL.DEVICE {
    fuchsia.BIND_PLATFORM_DEV_VID == fuchsia.platform.BIND_PLATFORM_DEV_VID.GENERIC;
  } else {
    fuchsia.BIND_PLATFORM_DEV_VID == fuchsia.platform.BIND_PLATFORM_DEV_VID.QEMU;
  }
}

複合繫結的文法如下:

composite-bind-rules = [composite-device], using-list , ( parent )+ ;

composite-device = “composite” , IDENTIFIER;

parent = [ "primary" ], "parent" , STRING-LITERAL , "{" , ( statement )+ , "}"

建立目標

如要在 Fuchsia 建構系統中宣告繫結規則,請使用下列建構目標:

driver_bind_rules("bind") {
  rules = <bind rules filename>
  bind_output = <generated bind binary filename>
  deps = [ <list of bind library targets> ]
}

詳情請參閱 //build/繫結/bind.gni

測試

繫結編譯器支援繫結規則的資料驅動單元測試架構,可讓您與驅動程式庫隔離測試繫結規則。繫結規則的測試案例包含裝置規格和預期結果 (繫結或中止)。測試案例會以 JSON 規格檔案的形式傳遞至繫結編譯器,而編譯器會執行偵錯工具,藉此執行每個測試案例。

JSON 規格必須是測試案例物件清單,每個物件包含:

  • name 測試案例名稱的字串。
  • expected預期結果。必須是 “match”“abort”
  • device 說明裝置屬性的字串鍵/值組合清單。這與偵錯工具的裝置規格類似 (請參閱這個範例)。

如果測試的裝置是複合裝置,裝置中的每個節點都可以有測試案例物件清單。單元測試的 JSON 規格會改為節點物件清單。每個節點物件都包含:

  • node節點名稱的字串。必須與繫結規則測試中的節點相符。
  • tests 測試案例物件清單。

範例

這是測試案例範例,完整測試集位於 //tools/bindc/examples/test.json。這個案例會檢查繫結規則是否與具有所列屬性的裝置 (即 Intel USB 音訊裝置) 相符。

[
  {
    "name": "Intel",
    "expected": "match",
    "device": {
      "fuchsia.BIND_PROTOCOL": "fuchsia.usb.BIND_PROTOCOL.INTERFACE",
      "fuchsia.BIND_USB_VID": "fuchsia.usb.BIND_USB_VID.INTEL",
      "fuchsia.BIND_USB_CLASS": "fuchsia.usb.BIND_USB_CLASS.AUDIO"
    }
  }
]

以下是複合繫結節點的範例,其中包含測試案例。完整測試集位於 `//tools/bindc/examples/composite-tests.json。每個測試案例都會檢查節點的繫結規則是否符合具有所列屬性的裝置。

[
    {
        "node": "pci",
        "tests": [
            {
                "name": "Match",
                "expected": "match",
                "device": {
                    "fuchsia.BIND_PROTOCOL": "fuchsia.pci.BIND_PROTOCOL.DEVICE"
                }
            },
            {
                "name": "Abort pci",
                "expected": "abort",
                "device": {
                    "fuchsia.BIND_PROTOCOL": "fuchsia.tee.BIND_PROTOCOL.DEVICE"
                }
            }
        ]
    }
]

建構

定義測試版本目標,如下所示:

bind_test("example_bind_test") {
  rules = <bind rules filename>
  tests = <test specification filename>
  deps = [ <list of bind library targets> ]
}

或者,您也可以直接在現有 bind_rules 中新增 tests 引數,產生測試目標。名稱為原始目標名稱加上 _test。舉例來說,下列程式碼會產生 example_bind_test

driver_bind_rules("example_bind") {
  rules = "meta/gizmo.bind"
  bind_output = “gizmo.bindbc”
  tests = "meta/tests.json"
  deps = [ "//src/devices/bind/fuchsia.usb" ]
}

執行

如果您已為測試定義建構目標,則可照常使用 fx test 執行測試。

fx test example_bind_test

否則可以直接執行繫結工具。例如:

fx bindc test \
  tools/bindc/examples/gizmo.bind \
  --test-spec tools/bindc/examples/tests.json \
  --include src/devices/bind/fuchsia.usb/fuchsia.usb.bind

繫結程式庫

繫結程式庫會定義驅動程式可指派給子項的一組屬性。此外,繫結規則可能會參照繫結程式庫。

命名空間

繫結程式庫會先定義其命名空間:

library <vendor>.<library>;

每個命名空間都必須以供應商開頭,且每個供應商應確保自己的命名空間內沒有衝突。不過,這項語言允許一個供應商擴充另一個供應商的程式庫。Google 會使用 fuchsia 處理公開程式庫。

程式庫導入的任何值都會加上命名空間。舉例來說,下列程式庫定義了新的 PCI 裝置 ID GIZMO_VER_1

library gizmotronics.gizmo;

using fuchsia.pci as pci;

extend uint pci.device_id {
  GIZMO_VER_1 = 0x4242,
};

如要參照這個值,驅動程式庫作者應使用完整名稱,如下所示。

using fuchsia.pci as pci;
using gizmotronics.gizmo;

pci.device_id == gizmotronics.gizmo.device_id.GIZMO_VER_1

鍵和值

裝置屬性定義與其他語言中的變數宣告類似。

<type> <name>;
Or:
<type> <name> {
  <value>,
  <value>,
  
};

繫結程式庫也可以擴充其他程式庫的屬性。

extend <type> <name> {
  <value>,
  
};

每個鍵都有類型,對應至該鍵的所有值都必須屬於該類型。語言支援原始型別:uintstringbool 其中之一;以及列舉 (enum)。定義鍵時,除非值是由外部來源 (例如硬體) 提供,否則應優先使用列舉。

定義基本值時,請使用 <identifier> = <literal> 格式,列舉時則只需要 ID。使用相同常值定義多個原始值是有效的。

文法

library = library-header , using-list , declaration-list ;

library-header = "library" , compound-identifier , ";" ;

using-list = ( using , ";" )* ;

using = "using" , compound-identifier , ( "as" , IDENTIFIER ) ;

compound-identifier = IDENTIFIER ( "." , IDENTIFIER )* ;

declaration-list = ( declaration , ";" )* ;

declaration = primitive-declaration | enum-declaration ;

primitive-declaration = ( "extend" ) , type , compound-identifier ,
                        ( "{" primitive-value-list "}" ) ;

type = "uint" | "string" | "bool";

primitive-value-list = ( IDENTIFIER , "=" , literal , "," )* ;

enum-declaration = ( "extend" ) , "enum" , compound-identifier ,
                   ( "{" , enum-value-list , "}" ) ;

enum-value-list = ( IDENTIFIER , "," )* ;

literal = STRING-LITERAL | NUMERIC-LITERAL | "true" | "false" ;

識別碼符合規則運算式 [a-zA-Z]([a-zA-Z0-9_]*[a-zA-Z0-9])?,且不得與任何關鍵字相符。關鍵字清單如下:

as
bool
enum
extend
library
string
uint
using

字串常值會比對規則運算式 ”[^”]*”,數字常值則會比對規則運算式 [0-9]+0x[0-9A-F]+

繫結編譯器會忽略 (視為空白字元) 以 // 為前置字元的任何行,以及以 /**/ 分隔的任何多行。

建立目標

如要在 Fuchsia 建構系統中宣告繫結程式庫,請使用下列建構目標:

bind_library(<library name>) {
  source = <bind library filename>
  public_deps = [ <list of bind library targets> ]
}

詳情請參閱 //build/繫結/bind.gni