RFC-0197:复合组的节点组

RFC-0197:复合体的节点组
状态已接受
区域
  • Driver SDK
说明

支持在运行时定义复合项。

问题
Gerrit 更改
作者
审核人
提交日期(年-月-日)2022-11-01
审核日期(年-月-日)2022-09-26

摘要

此 RFC 提出了一种设计,说明如何在驱动程序框架 v2 (DFv2) 中在运行时定义复合节点。

设计初衷

背景

在驱动程序框架 v1 (DFv1) 中,驱动程序可以在运行时通过 AddComposite() 函数创建复合节点。驱动程序为复合元件的父级和绑定属性定义绑定规则,驱动程序管理器将复合节点创建为驱动程序的子级。单独的驱动程序会绑定到复合节点。

在 DFv2 中,复合驱动程序通过绑定规则定义复合节点,这些规则是使用驱动程序绑定语言编写的静态文件。在运行时加载驱动程序时,Driver Runner 会收集与复合绑定规则匹配的父节点,然后创建复合节点。复合驱动程序会绑定到节点。

问题

由于复合驱动程序只能静态定义复合节点,因此无法完全替换 DFv1 的 AddComposite() 功能。这会阻止某些驱动程序迁移到 DFv2。

某些复合节点只能在运行时知晓,因此无法在复合驱动程序中进行定义。例如,ACPI 总线驱动程序会在运行时创建 ACPI 复合节点。启动总线驱动程序后,它会读取 ACPI 表,并使用这些信息创建节点。

静态绑定规则还会导致难以编写不依赖于开发板的复合驱动程序,因为绑定规则需要在构建时了解节点属性。例如,触摸复合驱动程序可能需要 GPIO 引脚的节点来实现特定功能。由于 GPIO 引脚因开发板而异,因此使用该节点编写不依赖于开发板的绑定规则会更困难。

利益相关方

协调员:cpu@google.com

审核员:surajmalhotra@google.com (FDF)、dgilhooley@google.com (FDF)

咨询对象:驾驶员团队成员

社交

RFC 的草稿已在 tq-drivers 之间共享

设计

要求

由于设计目标是创建一种机制,以允许驱动程序在运行时定义复合节点,因此以下内容必须是动态的:

  • 复合节点中每个父级的绑定规则
  • 用于将复合节点与复合驱动程序匹配的绑定属性
  • 复合节点中的父级。在运行时,系统可能会确定复合节点是否应包含特定父节点

此外,DFv1 和 DFv2 需要支持该机制。在向 DFv2 过渡的过程中,所有 DFv1 驱动程序都需要从 AddComposite() 迁移到复合驱动程序。

概览

此提案会向驱动程序框架添加一个新 API,驱动程序可以使用该 API 定义一组设备节点。

当驱动程序定义节点组时,流程如下:

  1. 驱动程序管理器会请求驱动程序索引查找与节点组匹配的复合驱动程序
  2. 找到匹配的复合驱动程序后,驱动程序管理器会为每个节点表示法找到匹配的设备节点
  3. 每个节点表示法都找到匹配项后,驱动程序管理器会创建一个复合节点,并将其作为父级节点绑定到复合驱动程序。主节点和节点名称由复合驱动程序提供

device-group-bind-diagram

节点表示法

组中的每个节点表示法均通过以下方式定义:

  • 绑定规则 - 用于将节点表示法与设备节点进行匹配的规则
  • 绑定属性 - 节点表示法中的绑定属性,将用于与复合驱动程序的静态绑定规则进行匹配

节点组绑定规则

绑定规则由接受和拒绝的绑定属性值列表组成。为了与绑定规则匹配,绑定属性必须包含所有已接受的绑定属性值,且不包含任何已遭拒的绑定属性值。例如,如果节点组节点包含以下绑定规则:

  • 接受 fuchsia.BIND_PROTOCOL 值 15 和 17
  • 拒绝 fuchsia.BIND_PLATFORM_DEV_VID 值“Intel”

然后,如果设备的 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;
}

与复合驱动程序匹配

匹配过程是通过将复合驱动程序的绑定规则应用于节点表示的绑定属性来完成的。如果满足以下条件,则匹配成功:

  • 所有节点表示法都必须与复合绑定规则中的节点匹配
  • 所有非可选复合绑定规则节点都必须与节点表示法匹配
  • 匹配不能含糊不清的字词:
    • 每个节点表示法都只能与一个复合绑定规则节点相对应
    • 节点表示法不能与复合绑定规则中的同一节点匹配
  • 节点不需要按顺序匹配

如果出现模糊的情况,系统会输出警告消息。

composite_bind_diagram

节点组 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 触控驱动程序

DFv1 定义

在 DFv1 中,focaltech 触控驱动程序包含以下绑定规则:using 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 引脚 ID 匹配,并为 GPIO 引脚类型提供绑定属性。这样,复合驱动程序就可以包含与其所处开发板无关的绑定规则。

假设有 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() 调用都将迁移到节点组。兼容 shim 将负责在 DFv1 和 DFv2 之间建立桥接。

安全注意事项

一个问题是,由于节点组是动态定义的,因此我们无法静态审核开发板配置。您可以操控节点拓扑,而无需绑定到任何节点。

为解决此问题,开发者可以使用开发板驱动程序添加节点组。您可以通过功能限制此行为。此外,我们日后会将重要数据迁移到设备树和 ACPI 等声明式格式,以便更轻松地进行审核。

隐私注意事项

测试

为此,我们将编写集成测试和单元测试。

文档

复合设备概念文档将更新。此外,您还可以在编写开发板驱动程序的教程中了解如何创建节点组。

缺点、替代方案和未知情况

虽然 AddNodeGroup() 提供了 AddComposite() 中的所有功能,但仍有可能存在它无法解决的缺口或用例。此外,当我们开始将 AddComposite() 用例迁移到节点组时,可能会发现更多边缘用例。

还有一些更复杂的情况,例如 ACPI 总线,它会通过 ACPI 表动态枚举并添加复合元件。由于许多驱动程序都绑定到 ACPI 复合元件,因此我们可能需要一次迁移多个驱动程序。

在先技术和参考文档

驱动程序框架

复合节点

驱动程序绑定