简介
在本部分,我们将介绍复合设备。 复合设备是由其他设备组成的设备。
这些设备解决了硬件级合成问题, 其中,“设备”(从用户的角度来看)是由 不同的硬件块
例如:
- 由 I2C 设备和 GPIO 组成的触摸板;
- 由 MAC 芯片和一个或多个 PHY 组成的以太网设备,或者
- 由音频控制器和一组编解码器组成的音频设备。
在这些情况下,主板了解硬件的关系 驱动程序(无论是静态的还是通过动态方式,如 ACPI)。
我们将在示例中使用 astro-audio
设备:
此设备具有以下特点:
- I2C 总线接口
- 两组 GPIO(一组用于故障,另一组用于启用)
- 用于批量数据传输的 MMIO(内存映射 I/O)以及
- IRQ(中断请求)行,用于对驱动程序生成中断。
请注意,ZX_PROTOCOL_I2C
和 ZX_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:
在 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 设备匹配规则
请遵循以下规则:
- 最后一个元素必须描述目标设备本身。
- 其余元素必须与从根目录到 目标设备。 系统可能会跳过某些设备,但每个元素都必须 必须匹配。
最后,我们将它们合并到一个名为 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_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 中的另一半