RFC-0197:复合组的节点组

RFC-0197:复合的节点组
状态已接受
领域
  • 驱动程序 SDK
说明

支持在运行时定义组合。

问题
  • 95821
Gerrit 更改
  • 726075
作者
  • spqchan@google.com
审核人
提交日期(年-月-日)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)

咨询人员:驾驶员团队成员

社交

RFC 的草稿在 tq-driver 之间共享

设计

要求

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

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

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

概览

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

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

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

设备组绑定图

节点表示法

组中的每个节点表示形式通过以下内容定义:

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

节点组绑定规则

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

  • 接受 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;
}

匹配复合驱动程序

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

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

如果出现不明确的情况,系统将输出一条警告消息。

组合图

节点组 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 之间进行桥接。

安全注意事项

一个问题是,由于节点组是动态定义的,我们无法静态审核板级配置。您可以在不绑定到任何节点的情况下操纵节点拓扑。

为了解决这个问题,板驱动程序将是可以添加节点组的驱动程序。这可以通过 capability 加以限制。此外,将来,我们会将重要数据迁移到声明式格式(例如设备树和 ACPI),以便简化审核。

隐私注意事项

测试

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

文档

复合设备概念文档将更新。此外,创建节点组可以在编写板驱动程序的教程中演示。

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

虽然 AddNodeGroup()AddComposite() 中提供了所有功能,但仍然有可能存在一些问题或用例无法解决的问题。此外,当我们开始将 AddComposite() 案例迁移到节点组时,可能会发现更多极端情况。

还有一些更复杂的用例,例如通过 ACPI 表动态枚举并添加复合项的 ACPI 总线。由于绑定到 ACPI 组合的驱动程序有很多,因此我们可能必须一次迁移多个驱动程序。

早期技术和参考资料

驱动程序框架

复合节点

驱动程序绑定