本指南将帮助您摆脱使用固定 Moniker 标识驱动程序实例的反模式。
具体来说,本指南适用于执行以下任何操作的非驱动程序组件客户端:
- 使用拓扑路径,例如
/dev/sys/platform/ram-nand/nand-ctl - 使用硬编码的类路径(例如 /dev/class/camera/000)
- 连接到给定 devfs 条目的
device_controller并调用GetTopologicalPath()。
在上述每种情况下,客户端都需要从服务/fidl 接口提供程序事先已知(或看起来已知)且始终假定服务存在的环境迁移到聚合服务,在该聚合服务中,实例将异步添加和移除,并且实例名称不能用于标识。
方法 1:连接到第一个可用的服务实例
这不是通常首选的解决方案,因为它忽略了系统的动态特性,并且在存在多个服务实例时会出错。 不过,对于仅预期有一个服务提供程序的简单工具和测试,这可能就足够了。以下代码会无限期地等待服务实例出现:
C++
SyncServiceMemberWatcher<fidl_example::Service::Device> watcher;
zx::result<ClientEnd<fidl_example::Echo>> result = watcher.GetNextInstance(false);
Rust
use fuchsia_component::client::Service;
let client = Service::open(fuchsia_example::EchoServiceMarker)?
.watch_for_any()
.await?
.connect_to_device()?;
方法 2:查询每个服务实例的信息
客户端不应通过使用唯一标识符连接到实例来标识实例,而应监控自己感兴趣的服务,然后查询每个实例以获取身份信息。此信息可能来自服务提供的现有协议,也可能向服务添加新协议以帮助进行标识。
使用服务提供的现有 fidl 协议
许多 fidl 协议已经指定了有关设备的信息(例如,fuchsia.camera/Control 协议提供了一个 GetInfo 调用)。 如果所服务的协议目前不包含身份信息,则可以对其进行修改以添加此类特定于设备的信息。
library fuchsia.hardware.something;
protocol Device {
...
GetDeviceId() -> (struct {
device_id uint32;
}) error zx.Status;
}
service Service {
device client_end:Device;
};
向服务添加其他协议
向服务添加其他 GetInfo 协议,该协议可以包含设备身份信息。例如:
library fuchsia.hardware.something;
Protocol Device { ... }
Protocol GetInfo {
GetDeviceId() -> (struct {
device_id uint32;
}) error zx.Status;
}
service Service {
device client_end:Device;
info client_end:GetInfo;
};
将标识接口与主通信接口分离具有以下几个优势:
- 标识接口主要由驱动程序库处理,因为它提供了一组静态信息。
- 标识接口最好能处理多个同步连接以帮助查询,而大多数驱动程序无法处理与其其他接口的多个同步连接。
- 分离标识逻辑可让日后的工作更轻松地将标识步骤集成到框架中。
如果节点需要拓扑信息,则已有协议来访问该信息,请参阅下一部分。
访问服务实例的拓扑信息。
为了便于将接口从使用 dev-topological 或使用 fuchsia_device::Controller 接口调用 GetTopologicalPath 转换为使用其他接口,我们提供了一个工具来访问 devfs 或服务实例的拓扑路径。 此工具旨在作为迁移到使用 fuchsia.driver.token.NodeToken 协议提供总线拓扑令牌的服务的步骤。
使用 GetTopologicalPath 工具将客户端转换为服务
使用 fdf_topology::GetTopologicalPath 可让您停止直接访问拓扑路径,并允许您将驱动程序转换为服务
将您的类名称添加到许可名单:
const std::list<std::string> kClassesThatAllowTopologicalPath({
"block",
"example_echo",
"devfs_service_test",
});
添加 device_topology 依赖项:
deps = [
"//src/devices/lib/client:device_topology",
],
并使用服务目录和实例名称调用 GetTopologicalPath:
// Open the service directory
zx::result dir = component::OpenDirectory("/svc/fuchsia.examples.EchoService");
ZX_ASSERT(dir.status_value() == ZX_OK);
// For simplicity in this example, just get the first instance in the service directory:
auto watch_result = device_watcher::WatchDirectoryForItems<std::string>(
*dir, [](std::string_view name) -> std::optional<std::string> { return std::string(name); });
ZX_ASSERT(watch_result.status_value() == ZX_OK);
std::string instance_name = std::move(watch_result.value());
// Now check the topological path of that instance:
zx::result<std::string> path_result = fdf_topology::GetTopologicalPath(*dir, instance_name);
ZX_ASSERT(path_result.is_ok());
if (path_result.value() == kSomePathYouWant) {
//Open the instance
}
执行此迁移步骤应该相对机械化,并且可以让您的客户端停止使用 devfs。 接下来的两个步骤将需要更详细地了解您的客户端为何使用拓扑路径。
将 NodeToken 添加到您的协议或服务
获取有关总线拓扑信息的推荐方法是使用 NodeToken:
library fuchsia.example;
using fuchsia.driver.token;
Protocol Device {
compose fuchsia.driver.token.NodeToken;
}
service Service {
device client_end:Device;
};
如需了解详情,请参阅总线拓扑。
迁移为使用总线拓扑,而不是拓扑路径
此处的实现取决于您为何需要使用拓扑信息。 例如,如果您需要连接到总线上的特定设备地址,则可以匹配总线类型和地址编号。
以下示例使用 ServiceMemberWatcher 过滤特定拓扑:
// Define a callback function to be called when the correct device is found
void OnInstanceFound(ClientEnd<fuchsia_examples::Echo> client_end) {}
// Define a filter for choosing the correct device:
void FilterOnBusTopology(ClientEnd<fuchsia_examples::Echo> client_end) {
fidl::WireResult result = fidl::WireCall(client_end)->Get();
ZX_ASSERT(result.ok());
zx::result topo_client = component::Connect<fuchsia.driver.token.NodeBusTopology>();
ZX_ASSERT(topo_client.is_ok());
fidl::WireResult topo_result = fidl::WireCall(topo_client.value())->Get(result.value().value()->token.get());
ZX_ASSERT(topo_result.ok());
// Check topology:
std::vector<fuchsia.driver.framework.BusInfo> bus_info
= topo_result.value().value()->path.get();
if (bus_info[0].bus() == fuchsia_driver_framework::BusType::I2C &&
bus_info[0].address() == 3) {
OnInstanceFound(std::move(client_end));
}
}
// Optionally define an idle function, which will be called when all
// existing instances have been enumerated:
void AllExistingEnumerated() {...}
// Create the ServiceMemberWatcher:
ServiceMemberWatcher<fuchsia_examples::EchoService::MyDevice> watcher;
watcher.Begin(get_default_dispatcher(), &FilterOnBusTopology, &AllExistingEnumerated);
// If you want to stop watching for new service entries:
watcher.Cancel()
您还需要从驱动程序管理器将 fuchsia.driver.token.NodeBusTopology 协议路由到您的客户端。
如有疑问或需要了解状态更新,请与我们联系: