RFC-0197:複合節點的節點群組

RFC-0197:複合型節點群組
狀態已接受
區域
  • 驅動程式 SDK
說明

支援在執行階段定義複合元素。

問題
  • 95821
變更
作者
審查人員
提交日期 (年/月)2022-11-01
審查日期 (年/月)2022-09-26

摘要

這個 RFC 提出了在驅動程式架構 v2 (DFv2) 中,如何在執行階段定義複合節點的設計。

提振精神

背景

在驅動程式架構 v1 (DFv1) 中,驅動程式可透過 AddComposite() 函式在執行階段建立複合節點。驅動程式為複合的父項和繫結屬性定義繫結規則,驅動程式管理器會建立複合節點做為驅動程式庫的子項。獨立的驅動程式庫會繫結至複合節點。

在 DFv2 中,複合式驅動程式會透過繫結規則定義複合節點,繫結規則是以驅動程式庫繫結語言寫入的靜態檔案。驅動程式在執行階段載入時,驅動程式執行器會收集符合複合式繫結規則的父項節點,然後建立複合節點。複合驅動程式庫會繫結至節點。

問題

由於複合驅動程式只能靜態定義複合節點,因此無法完全取代 DFv1 的 AddComposite() 功能。這樣做可避免部分驅動程式遷移至 DFv2。

部分複合節點只能在執行階段得知,因此無法在複合驅動程式中定義。例如,ACPI 匯流排驅動程式會在執行階段建立 ACPI 複合節點。匯流排驅動程式啟動後,系統會讀取 ACPI 資料表中,並使用該資訊建立節點。

靜態繫結規則也使您在編寫跨板通用的複合式驅動程式時更加困難,因為繫結規則必須在建構期間瞭解節點屬性。舉例來說,觸控式複合驅動程式庫可能會要求使用 GPIO 接腳的節點執行特定功能。由於 GPIO 接腳會因主機板而異,因此要使用該節點撰寫跨板通用繫結規則變得比較困難。

相關人員

講師:cpu@google.com

審查人員:surajmalhotra@google.com (FDF)、dgilhooley@google.com (FDF)

顧問:駕駛團隊成員

社群媒體化:

與 tq 驅動程式共用的 RFC 草稿

設計

相關規定

由於這項設計的目標是建立機制,讓驅動程式在執行階段定義複合節點,因此必須具有動態性質:

  • 複合式節點中每個父項的繫結規則
  • 將複合節點與複合驅動程式庫比對的繫結屬性
  • 複合式節點中的父項。如果複合節點應包含特定父項,可在執行階段確定

此外,DFv1 和 DFv2 必須支援這項機制。改用 DFv2 時,所有 DFv1 驅動程式都必須從 AddComposite() 遷移至複合驅動程式。

總覽

本提案在驅動程式架構中加入新的 API,驅動程式可用於定義一組裝置節點。

驅動程式庫定義節點群組時,程序如下:

  1. 驅動程式管理器會要求驅動程式庫索引找出與節點群組相符的複合驅動程式庫
  2. 找到相符的複合驅動程式庫後,驅動程式管理器會尋找每個節點表示法相符的裝置節點
  3. 當每個節點表示法都符合後,驅動程式管理器會建立複合式節點,並以父項做為父項,並將其繫結至複合式驅動程式庫。主要節點和節點名稱由複合式驅動程式庫提供

裝置群組繫結圖表

節點表示法

群組中的每個節點表示法定義如下:

  • 繫結規則:將節點表示法與裝置節點比對的規則
  • 繫結屬性 - 節點表示法中的繫結屬性,會用來比對複合式驅動程式庫的靜態繫結規則

節點群組繫結規則

繫結規則包含已接受和遭拒的繫結屬性值清單。如要符合繫結規則,繫結屬性必須包含所有系統接受的繫結屬性值,而非任何遭拒的屬性。例如,如果節點群組節點包含繫結規則:

  • 接受 fuchsia.BIND_PROTOCOL 值 15 和 17
  • 拒絕「Intel」這個 fuchsia.BIND_PLATFORM_DEV_VID

如果裝置含有 fuchsia.BIND_PROTOCOL 屬性的值為 15 或 17,且不含 fuchsia.BIND_PLATFORM_DEV_VID 屬性值的「Intel」值,裝置就會繫結至節點。

複合式繫結規則中的選用節點

由於某些父項的可用性只會在執行階段得知,因此複合式驅動程式必須能夠支援選用的節點。為此,您必須更新繫結語言,讓複合驅動程式才能將節點標示為選用:

primary node "sysmem" {
  fuchsia.BIND_PROTOCOL == fuchsia.sysmem.BIND_PROTOCOL.DEVICE;
}

optional node "acpi" {
  fuchsia.BIND_PROTOCOL == fuchsia.acpi.BIND_PROTOCOL.DEVICE;
}

比對複合驅動程式

比對程序是透過將複合驅動程式庫繫結規則套用至節點表示法的繫結屬性。滿足以下條件即視為比對成功:

  • 所有節點表示法都必須與複合式繫結規則中的節點相符
  • 所有非選用的複合式繫結規則節點都必須與節點表示法相符
  • 比對結果不能不明確:
    • 每個節點表示法都只能對應一個複合式繫結規則節點
    • 節點表示法無法與複合式繫結規則中的相同節點相符
  • 節點不必是按照順序比對

如果發生不明情況,系統會列印警告訊息。

複合繫結圖表

Node Group API

驅動程式必須使用 FIDL,透過 fuchsia.driver.framework FIDL 程式庫中的 NodeGroupManager 通訊協定新增節點群組:

device_group.fidl

@discoverable
protocol NodeGroupManager {
    AddNodeGroup(fuchsia.driver.framework.NodeGroup) -> (struct {}) error zx.status;
};

節點群組以 FIDL 表示:

/// Represents the conditions for evaluating the device
/// group properties.
type Condition = strict enum {
    ACCEPT = 0;
    REJECT = 1;
};

/// Represents a bind rule for a node group node.
type BindRule = struct {
    /// Property key.
    key NodePropertyKey;

    /// Condition for evaluating the property values in
    /// the matching process. The values are accepted or
    /// rejected based on the condition.
    condition Condition;

    /// A list of property values. Must not be empty. The property
    /// values must be the same type.
    values vector<NodePropertyValue>:MAX_PROPERTY_COUNT;
};

/// Struct that represents a node in a node group.
type NodeRepresentation = struct {
    /// Bind rules for the node group node. Keys must be unique.
    bind_rules: vector<BindRule>:MAX_PROPERTY_COUNT;

    /// Properties used for matching composite bind rules. Keys must be unique.
    bind_properties vector<NodeProperty>:MAX_PROPERTY_COUNT;
};

/// Struct that represents a node group.
type NodeGroup = table {
    /// The node group's name.
    1: name string:MAX;

    /// The nodes in the node group.
    2: nodes vector<NodeRepresentation>:MAX;
};

*NodeProperty 是在 Topology.fidl 中定義

範例:Focaltech Touch Driver

DFv1 定義

在 DFv1 中,focaltech 觸控驅動程式包含以下繫結規則:使用 fuchsia.platform;

fuchsia.BIND_COMPOSITE == 1;
fuchsia.BIND_PLATFORM_DEV_VID == fuchsia.platform.BIND_PLATFORM_DEV_VID.GENERIC;
fuchsia.BIND_PLATFORM_DEV_DID == fuchsia.platform.BIND_PLATFORM_DEV_DID.FOCALTOUCH;

複合裝置定義於 astro-touch

// Composite binding rules for focaltech touch driver.
const zx_bind_inst_t ft_i2c_match[] = {
    BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_I2C),
    BI_ABORT_IF(NE, BIND_I2C_BUS_ID, ASTRO_I2C_2),
    BI_MATCH_IF(EQ, BIND_I2C_ADDRESS, I2C_FOCALTECH_TOUCH_ADDR),
};
const zx_bind_inst_t goodix_i2c_match[] = {
    BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_I2C),
    BI_ABORT_IF(NE, BIND_I2C_BUS_ID, ASTRO_I2C_2),
    BI_MATCH_IF(EQ, BIND_I2C_ADDRESS, I2C_GOODIX_TOUCH_ADDR),
};
static const zx_bind_inst_t gpio_int_match[] = {
    BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_GPIO),
    BI_MATCH_IF(EQ, BIND_GPIO_PIN, GPIO_TOUCH_INTERRUPT),
};
static const zx_bind_inst_t gpio_reset_match[] = {
    BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_GPIO),
    BI_MATCH_IF(EQ, BIND_GPIO_PIN, GPIO_TOUCH_RESET),
};

static const device_fragment_part_t ft_i2c_fragment[] = {
    {countof(ft_i2c_match), ft_i2c_match},
};
static const device_fragment_part_t goodix_i2c_fragment[] = {
    {countof(goodix_i2c_match), goodix_i2c_match},
};
static const device_fragment_part_t gpio_int_fragment[] = {
    {countof(gpio_int_match), gpio_int_match},
};
static const device_fragment_part_t gpio_reset_fragment[] = {
    {countof(gpio_reset_match), gpio_reset_match},
};

static const device_fragment_t ft_fragments[] = {
    {"i2c", countof(ft_i2c_fragment), ft_i2c_fragment},
    {"gpio-int", countof(gpio_int_fragment), gpio_int_fragment},
    {"gpio-reset", countof(gpio_reset_fragment), gpio_reset_fragment},
};
static const device_fragment_t goodix_fragments[] = {
    {"i2c", countof(goodix_i2c_fragment), goodix_i2c_fragment},
    {"gpio-int", countof(gpio_int_fragment), gpio_int_fragment},
    {"gpio-reset", countof(gpio_reset_fragment), gpio_reset_fragment},
};

const zx_device_prop_t props[] = {
    {BIND_PLATFORM_DEV_VID, 0, PDEV_VID_GENERIC},
    {BIND_PLATFORM_DEV_PID, 0, PDEV_PID_ASTRO},
    {BIND_PLATFORM_DEV_DID, 0, PDEV_DID_FOCALTOUCH},
};

然後使用 DdkAddComposite() API 新增:

const composite_device_desc_t comp_desc = {
    .props = props,
    .props_count = std::size(props),
    .fragments = ft3x27_touch_fragments,
    .fragments_count = std::size(ft3x27_touch_fragments),
    .primary_fragment = "i2c",
};

zx_status_t status = DdkAddComposite("ft3x27-touch", &comp_desc);
if (status != ZX_OK) {
   zxlogf(ERROR, "%s(ft3x27): CompositeDeviceAdd failed: %d", __func__, status);
   return status;
}
具有節點群組的複合驅動程式

使用節點群組時,節點表示法繫結規則可以與 GPIO PIN 碼類型進行比對,並為 GPIO PIN 碼類型提供繫結屬性。這可讓複合式驅動程式庫包含適用於各面板的繫結規則。

假設有 fuchsia.gpio 繫結程式庫:

library fuchsia.gpio;

extend uint fuchsia.BIND_PROTOCOL {
  DEVICE = 20,
  IMPL = 21,
};

enum FUNCTION {
   TOUCH_INTERRUPT,
   TOUCH_RESET,
};

節點群組的定義如下:

node {
   bind_rules {
     fuchsia.BIND_FIDL_PROTOCOL == fuchsia.i2c.BIND_FIDL_PROTOCOL.DEVICE;
     fuchsia.BIND_I2C_BUS_ID == fuchsia.i2c.BIND_I2C_BUS_ID.ASTRO_2;
     fuchsia.BIND_I2C_ADDRESS == fuchsia.i2c.BIND_I2C_ADDRESS.FOCALTECH_TOUCH;
   },
   bind_properties {
     fuchsia.BIND_FIDL_PROTOCOL: fuchsia.i2c.BIND_FIDL_PROTOCOL.DEVICE,
     fuchsia.gpio.FUNCTION: fuchsia.platform.BIND_PLATFORM_DEV_DID.FOCALTOUCH,
   }
}

node {
   bind_rules {
     fuchsia.BIND_PROTOCOL == fuchsia.gpio.BIND_PROTOCOL.DEVICE;
     fuchsia.BIND_GPIO_PIN == fuchsia.amlogic.platform.s905d3.GPIOZ_PIN_ID.PIN_6;
   },
   bind_properties {
     fuchsia.BIND_PROTOCOL: fuchsia.gpio.BIND_PROTOCOL.DEVICE,
     fuchsia.gpio.FUNCTION: fuchsia.gpio.FUNCTION.TOUCH_INTERRUPT,
     fuchsia.gpio.BIND_PLATFORM_DEV_DID:
        fuchsia.platform.BIND_PLATFORM_DEV_DID.FOCALTOUCH,
   }
}

node {
   bind_rules {
     fuchsia.BIND_PROTOCOL == fuchsia.gpio.BIND_PROTOCOL.DEVICE;
     fuchsia.BIND_GPIO_PIN == fuchsia.amlogic.platform.s905d3.GPIOZ_PIN_ID.PIN_9;
   },
   bind_properties {
     fuchsia.BIND_PROTOCOL: fuchsia.gpio.BIND_PROTOCOL.DEVICE,
     fuchsia.gpio.FUNCTION: fuchsia.gpio.FUNCTION.TOUCH_RESET,
     fuchsia.BIND_PLATFORM_DEV_DID:
         fuchsia.platform.BIND_PLATFORM_DEV_DID.FOCALTOUCH,
   }
}

透過 driver2 程式庫 (DFv1 適用的 DDK),驅動程式庫程式碼就能新增節點群組:

const fdf::BindRule i2c_bind_rules[] = {
  fdf::BindRule::Accept(
      BIND_FIDL_PROTOCOL, bind_fuchsia_i2c::BIND_FIDL_PROTOCOL_DEVICE),
  fdf::BindRule::Accept(
      BIND_I2C_BUS_ID, bind_fuchsia_i2c::BIND_I2C_BUS_ID_ASTRO_2),
  fdf::BindRule::Accept(
      BIND_I2C_ADDRESS, bind_fuchsia_i2c::BIND_I2C_ADDRESS_FOCALTECH_TOUCH),
};

const fdf::NodeProperty i2c_bind_properties[] = {
  fdf::MakeProperty(BIND_FIDL_PROTOCOL,
     bind_fuchsia_i2c::BIND_FIDL_PROTOCOL_DEVICE),
  fdf::MakeProperty(BIND_PLATFORM_DEV_DID,
     bind_fuchsia_platform::BIND_PLATFORM_DEV_DID_FOCALTOUCH),

};

const fdf::BindRule gpio_interrupt_bind_rules[] = {
  fdf::BindRule::Accept(
      BIND_PROTOCOL, bind_fuchsia_gpio::BIND_PROTOCOL_DEVICE),
  fdf::BindRule::Accept(
      BIND_GPIO_PIN, bind_fuchsia_amlogic_platform_s905d2::GPIOZ_PIN_ID_PIN_4),
}

const fdf::NodeProperty gpio_interrupt_bind_properties[] = {
  fdf::MakeProperty(BIND_PROTOCOL, bind_fuchsia_gpio::BIND_PROTOCOL_DEVICE),
  fdf::MakeProperty(bind_fuchsia_gpio::FUNCTION,
     bind_fuchsia_gpio::FUNCTION_TOUCH_INTERRUPT),
  fdf::MakeProperty(BIND_PLATFORM_DEV_DID,
     bind_fuchsia_platform::BIND_PLATFORM_DEV_DID_FOCALTOUCH),
};

const fdf::BindRule gpio_reset_bind_rules[] = {
  fdf::BindRule::Accept(
      BIND_PROTOCOL, bind_fuchsia_gpio::BIND_PROTOCOL_DEVICE),
  fdf::BindRule::Accept(
      BIND_GPIO_PIN, bind_fuchsia_amlogic_platform_s905d2::GPIOZ_PIN_ID_PIN_9),
};

const fdf::NodeProperty gpio_reset_bind_properties[] = {
  fdf::MakeProperty(BIND_PROTOCOL, bind_fuchsia_gpio::BIND_PROTOCOL_DEVICE),
  fdf::MakeProperty(bind_fuchsia_gpio::FUNCTION,
       bind_fuchsia_gpio::FUNCTION_TOUCH_RESET),
  fdf::MakeProperty(BIND_PLATFORM_DEV_DID,
     bind_fuchsia_platform::BIND_PLATFORM_DEV_DID_FOCALTOUCH),
};

auto focaltech_touch_device =
  fdf::NodeGroup(i2c_bind_rules, i2c_bind_properties)
      .AddNodeRepresentation(gpio_interrupt_bind_rules,
          gpio_interrupt_bind_properties)
      .AddNodeRepresentation(gpio_reset_bind_rules, gpio_reset_bind_properties);
fdf::AddNodeGroup(node, focaltech_touch_device);

接著,Focaltech 驅動程式庫組合繫結規則需要更新:

composite ft3x27_touch;

using fuchsia.amlogic.platform.s905d2;
using fuchsia.gpio;
using fuchsia.i2c;
using fuchsia.platform;

primary node "i2c" {
  fuchsia.BIND_FIDL_PROTOCOL == fuchsia.i2c.BIND_FIDL_PROTOCOL.DEVICE;
  fuchsia.BIND_PLATFORM_DEV_DID ==
      fuchsia.platform.BIND_PLATFORM_DEV_DID.FOCALTOUCH;
}

node "gpio-int" {
  fuchsia.BIND_PROTOCOL == fuchsia.gpio.BIND_PROTOCOL.DEVICE;
  fuchsia.gpio.GPIO_FUNCTION == fuchsia.gpio.GPIO_FUNCTION.TOUCH_INTERRUPT;
  fuchsia.BIND_PLATFORM_DEV_DID ==
      fuchsia.platform.BIND_PLATFORM_DEV_DID.FOCALTOUCH;
}

node "gpio-reset" {
  fuchsia.BIND_PROTOCOL == fuchsia.gpio.BIND_PROTOCOL.DEVICE;
  fuchsia.gpio.GPIO_FUNCTION == fuchsia.gpio.GPIO_FUNCTION.TOUCH_RESET;
  fuchsia.BIND_PLATFORM_DEV_DID ==
      fuchsia.platform.BIND_PLATFORM_DEV_DID.FOCALTOUCH;
}

DFv2 複合驅動程式變更

節點群組會取代 DFv2 複合驅動程式目前的機制。日後,您只能透過節點群組建立複合式節點。在 DFv2 中完全實作節點群組後,我們會將所有複合驅動程式遷移至其,並移除目前的機制。

實作

變更將涉及支援節點群組使用 fuchsia.driver.framework FIDL API。驅動程式管理器和索引都必須更新,以便處理和追蹤所有節點群組。

如要支援選用節點,您必須更新繫結編譯器,使其支援繫結語言中的 optional 關鍵字,並將資訊編碼為位元碼。

遷移至節點群組

所有複合式組合都是透過節點群組建立,因此 DFv1 和 DFv2 中的所有現有複合在完全實作之後都必須遷移至節點群組。

在 DFv1 中,AddComposite() 的所有使用情形都會替換為 DDK 中的 AddNodeGroup() 函式呼叫。包括將驅動程式庫遷移至複合繫結規則的複合驅動程式庫,然後透過 AddNodeGroup() 建立複合驅動程式。

在 DFv2 中,必須為每個複合驅動程式庫建立相符的節點群組。

由於兩者都支援目前的複合驅動程式庫實作,因此驅動程式庫索引和相符的節點可能會發生衝突。例如,如果節點與複合式驅動程式庫或節點群組中的節點相符,驅動程式庫索引可能會只傳回其中一個相符項目。如要避免這種情況,其中一種方法是優先比對節點群組,而非複合式驅動程式。

為避免發生迴歸問題,每個遷移的驅動程式庫都會以手動方式與測試進行驗證。遷移並非適用於 DFv1 的機械程序,因此需要花更多時間驗證驅動程式。

一旦所有複合驅動程式都遷移至節點群組,我們就能移除目前的實作項目。

效能

這不會影響效能,因為這類似於 DFv1 中的複合節點的建立方式。

人體工學

直接透過 FIDL 繫結建立節點群組並非符合人體工學的需求。為簡化內容並提高可讀性,我們會在 driver2 中建立輔助程式庫,用於定義繫結規則和屬性。

大多數的節點群組日後將以 DSL 的形式定義,例如 ACPI 和裝置樹狀結構。因此,並非優先採用人體工學透過程式碼車載驅動程式中的程式碼編寫節點群組並非優先要務

回溯相容性

並確保該版本與 DFv1 和 DFv2 相容。為瞭解決這個問題,我們也可以實作 DFv1 的節點群組。DFv1 驅動程式可透過 DDK 新增節點群組。所有 AddComposite() 呼叫都會遷移至節點群組。compat 填充將負責建立 DFv1 和 DFv2 之間的橋接。

安全性考量

值得一提的是,由於節點群組是以動態方式定義,因此我們無法靜態稽核主機板設定。可以在不繫結任何節點的情況下操控節點拓撲。

為解決這個問題,主面板驅動程式庫會是可以新增節點群組的驅動程式。這可以透過功能限制。此外,我們日後會將重要資料遷移至宣告式格式 (例如裝置樹狀結構和 ACPI),以簡化稽核作業。

隱私權注意事項

測試

這會撰寫整合測試和單元測試。

說明文件

系統會更新複合裝置概念說明文件。此外,您也可以在編寫主面板驅動程式庫的教學課程中展示建立節點群組。

缺點、替代方案和未知

雖然 AddNodeGroup() 提供 AddComposite() 的所有功能,但仍然可能會有缺漏或用途無法解決的問題。此外,當我們開始將 AddComposite() 案例遷移至節點群組時,可能會發現更多極端情況。

有些情況則較為複雜,例如 ACPI 匯流排,透過 ACPI 資料表動態列舉並新增複合資料。由於有許多驅動程式繫結至 ACPI 複合,因此我們可能會一次遷移多個驅動程式。

先前的圖片和參考資料

駕駛架構

複合節點

駕駛座繫結