复合设备

简介

在本部分,我们将介绍复合设备。复合设备是指由其他设备组成的设备。

这些设备可处理硬件级合成的情况,即从用户的角度来看,“设备”由多个不同的硬件块实现。

例如:

  • 由 I2C 设备和 GPIO 组成的触摸板,
  • 由 MAC 芯片和一个或多个 PHY 组成的以太网设备,或者
  • 由一个音频控制器和一组编解码器组成的音频设备。

在这些情况下,板驱动程序在启动时就知道硬件的关系(无论是以静态方式还是通过 ACPI 等动态方式)。

在我们的示例中,我们将使用 astro-audio 设备:

图:I2C 总线上具有 GPIO 的复合硬件设备

此设备具有以下特点:

  • I2C 总线接口
  • 两组 GPIO(一组用于故障,一组用于启用)
  • 用于批量数据传输的 MMIO(内存映射 I/O),以及
  • IRQ(中断请求)行,以便为驱动程序生成中断。

请注意,ZX_PROTOCOL_I2CZX_PROTOCOL_GPIO 协议用于传输数据;也就是说,I2C 消息和 GPIO 引脚状态通过相应的驱动程序发送和接收。

ZX_PROTOCOL_PDEV 部分有所不同。 在这里,该协议仅用于授予对 MMIO 和 IRQ 的访问权限(图中的绿色对勾标记);实际的 MMIO 数据和中断不由 PDEV 处理,而是由 astro-audio 驱动程序本身直接处理。

创建复合设备

如需创建复合设备,您需要设置许多数据结构。

绑定说明

我们需要多个绑定指令 (zx_bind_inst_t),用于告诉我们匹配的设备。

对于 astro-audio 设备,我们有:

static const zx_bind_inst_t i2c_match[] = {
    BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_I2C),
    BI_ABORT_IF(NE, BIND_I2C_BUS_ID, ASTRO_I2C_3),
    BI_MATCH_IF(EQ, BIND_I2C_ADDRESS, I2C_AUDIO_CODEC_ADDR),
};

static const zx_bind_inst_t fault_gpio_match[] = {
    BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_GPIO),
    BI_MATCH_IF(EQ, BIND_GPIO_PIN, GPIO_AUDIO_SOC_FAULT_L),
};

static const zx_bind_inst_t enable_gpio_match[] = {
    BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_GPIO),
    BI_MATCH_IF(EQ, BIND_GPIO_PIN, GPIO_SOC_AUDIO_EN),
};

这些绑定指令用于查找设备。

我们有三个绑定指令数组:I2C (i2c_match[]) 设备和两个 GPIO(fault_gpio_match[]enable_gpio_match[])。

然后,系统会将这些说明放入结构数组 (device_fragment_part_t) 中,该数组定义了每个 fragment:

图:收集到 fragment 数组中的绑定指令

astro-audio 设备中,我们有:

static const device_fragment_part_t i2c_fragment[] = {
    { countof(i2c_match), i2c_match },
};

static const device_fragment_part_t fault_gpio_fragment[] = {
    { countof(fault_gpio_match), fault_gpio_match },
};

static const device_fragment_part_t enable_gpio_fragment[] = {
    { countof(enable_gpio_match), enable_gpio_match },
};

此时,我们有三个 fragment 设备:i2c_fragment[]fault_gpio_fragment[]enable_gpio_fragment[]

fragment 设备匹配规则

以下规则适用:

  1. 最后一个元素必须描述目标设备本身。
  2. 其余元素必须按顺序匹配路径上的设备(从根到目标设备)。其中一些设备可能会被跳过,但必须匹配每个元素。

最后,我们会将它们组合成一个名为 fragments[] 且类型为 device_fragment_t 的聚合:

图:将片段收集到聚合中

现在,这为我们提供单个标识符 fragments[],可以在创建复合设备时使用。

astro-audio 中,如下所示:

static const device_fragment_t fragments[] = {
    { "i2c", countof(i2c_fragment), i2c_fragment },
    { "gpio-fault", countof(fault_gpio_fragment), fault_gpio_fragment },
    { "gpio-enable", countof(enable_gpio_fragment), enable_gpio_fragment },
};

正在创建设备

对于简单(非复合)设备,我们使用 device_add()

对于复合设备,我们使用 device_add_composite_deprecated()

zx_status_t device_add_composite_deprecated(
    zx_device_t* dev,
    const char* name,
    const zx_device_prop_t* props,
    size_t props_count,
    const device_fragment_t* fragments,
    size_t fragments_count,
    uint32_t coresident_device_index);

参数如下所示:

参数 含义
dev 家长设备
name 设备名称
props 属性(请参阅“声明驱动程序”
props_count props 中的条目数量
fragments 各个 fragment 设备
fragments_count fragments 中的条目数量
coresident_device_index 要使用哪个驱动程序主机

dev 值必须是与“sys”设备(即平台总线驱动程序的设备)对应的 zx_device_t

请注意,coresident_device_index 用于指示新设备应使用哪个驱动程序主机。 如果您指定 UINT32_MAX,设备将位于新的驱动程序主机中。

请注意,astro-audio 使用的是 pbus_composite_device_add(),而不是 device_add_composite_Deprecated()。不同之处在于 pbus_composite_device_add() 是平台总线驱动程序提供的 API,用于封装 device_add_composite_deprecation() 并插入一个额外的 fragment,用于通过直接访问资源进行传输,例如 BMM。

使用复合设备

从编程的角度来看,复合设备的作用与普通设备类似,但它没有班卓琴协议。每个 inividual fragment 都可以提供协议和元数据,但为了便于兼容性,不应直接访问这些 fragment。

而是可以通过调用 device_get_fragment_protocol()device_get_fragment_metadata() 来直接访问每个 fragment 的协议和元数据

bool device_get_fragment_protocol (
     zx_device_t* parent,
     const char* fragment_name,
     uint32_t proto_id, void* out);

参数如下所示:

参数 含义
parent 指向代表父级的 zx_device_t 的指针
fragment_name 您要提取的 fragment 的名称
proto_id 要检索的协议的 ID
out 指向要返回的协议的指针
foo_protocol_t proto;
auto status = device_get_fragment_protocol(&composite, "fragment-name", ZX_PROTOCOL_FOO, &proto);
if (status != ZX_OK) {
    zxlogf(ERROR, "could not get protocol");
    return status;
}

同样,对于元数据:

bool device_get_fragment_metadata (
     zx_device_t* parent,
     const char* fragment_name,
     uint32_t type, void* buf,
     size_t buflen, size_t* actual);

参数如下所示:

参数 含义
parent 指向代表父级的 zx_device_t 的指针
fragment_name 您要提取的 fragment 的名称
type 要检索的协议的 ID
buf 指向要填充的数据集的指针
buflen 可写入 buf 的字节数上限
actual 指向 size_t 的指针,其中填充了实际尺寸
std::vector<uint8_t> data(50);
size_t actual = 0;
auto status = device_get_fragment_metadata(&composite, "fragment-name",
                                           DEVICE_METADATA_FOO, data.data(),
                                           data.size(), &actual);
if (status != ZX_OK) {
    zxlogf(ERROR, "could not get metadata");
    return status;
}

提供给 device_get_fragment_protocol()device_get_fragment_metadata() 的 fragment 的名称与板驱动程序向 device_add_composite_deprecation() 调用提供的 device_fragment_t 条目中的 fragment 的名称相同。

高级主题

在这里,我们将讨论一些专业 / 高级主题。

复合设备和代理

astro-audio 驱动程序中实际发生的情况比最初显示的稍微复杂一点:

图:使用代理的复合硬件设备

fragment 会绑定到内部驱动程序(位于 fragment 目录中)。

驱动程序在必要时跨进程边界处理代理。此代理使用 DEVICE_ADD_MUST_ISOLATE 机制(在隔离设备部分中引入)。

使用 DEVICE_ADD_MUST_ISOLATE 添加设备时,最终会创建两台设备:与父设备处于同一进程的普通设备,以及代理。

代理在新驱动程序主机中创建;如果常规设备的驱动程序为 normal.so,则其驱动程序为 normal.proxy.so。该驱动程序应实现一个 create() 方法,以调用 device_add() 并存储为其指定的 IPC 通道。该通道稍后将用于与常规设备通信,以满足代理子项的请求。

常规设备会实现 rxrpc 钩子,每当从与代理共享的通道收到消息时,驱动程序运行时都会调用该钩子。

因此,为了实现新的协议代理,必须修改 fragment.proxy.so 驱动程序,通过向常规设备发送消息来处理所需的协议,并修改 fragment.so 驱动程序以适当地提供这些消息。

fragment 代理在 fragment-proxy.cc 中实现,另一半在 fragment.cc 中实现。