RFC-0197:复合组的节点组

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

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

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

摘要

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

设计初衷

背景

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

在 DFv2 中,复合驱动程序通过绑定规则(以驱动程序绑定语言编写的静态文件)定义复合节点。当驱动程序在运行时加载时,驱动程序运行器会收集与复合绑定规则匹配的父节点,然后创建复合节点。复合驱动程序绑定到节点。

问题

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

某些复合节点仅在运行时已知,因此无法在复合驱动程序中定义。例如,ACPI 总线驱动程序会在运行时创建 ACPI 复合节点。当总线驱动程序启动时,它会读取 ACPI 表并使用该信息来创建节点。

静态绑定规则还使得难以编写与主板无关的复合驱动程序,因为绑定规则需要在构建时了解节点属性。例如,触摸复合驱动程序可能需要一个用于特定功能的 GPIO 针脚节点。由于 GPIO 引脚因电路板而异,因此很难使用该节点编写与电路板无关的绑定规则。

利益相关方

Facilitator: 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 触控驱动程序包含以下绑定规则:使用 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 复合设备,我们可能需要一次性迁移多个驱动程序。

在先技术和参考资料

驱动程序框架

复合节点

驱动程序绑定