复合设备

简介

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

这些设备解决了硬件级合成问题, 其中,“设备”(从用户的角度来看)是由 不同的硬件块

例如:

  • 由 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

图:将 fragment 收集到一个聚合中

现在,这为我们提供了单个标识符 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_deprecated() 的平台总线驱动程序提供, 插入一个额外的 fragment,用于转接直接访问资源 例如 MMIO、IRQ 和 BTI。

使用复合设备

从编程的角度来看,复合设备就像普通设备一样, 但它没有班卓琴的协议每个原始 fragment 都可以提供 但为了保持兼容性,这些片段不应 访问它们。

相反,可以通过以下方法针对每个片段直接访问协议和元数据: 调用 device_get_fragment_protocol()device_get_fragment_metadata()

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() 与 提供给 device_add_composite_deprecation()device_fragment_t 条目 调用。

高级主题

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

复合设备和代理

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 中的另一半