驱动程序组件通过功能向其他驱动程序和非驱动程序组件提供功能和服务。这样,Fuchsia 的组件框架便可以在必要时将这些功能路由到目标组件。
在本部分中,您将公开 qemu_edu
驱动程序的阶乘功能,并从系统中其他位置运行的组件中使用这些功能。
创建新的 FIDL 库
在 Bazel 工作区中为新的 FIDL 库创建一个新的项目目录:
mkdir -p fuchsia-codelab/qemu_edu/fidl
完成本部分后,项目应具有以下目录结构:
//fuchsia-codelab/qemu_edu/fidl
|- BUILD.bazel
|- qemu_edu.fidl
创建 qemu_edu/fidl/BUILD.bazel
文件并添加以下语句,以包含 Fuchsia SDK 中的必要构建规则:
qemu_edu/fidl/BUILD.bazel
:
load(
"@rules_fuchsia//fuchsia:defs.bzl",
"fuchsia_fidl_library",
"fuchsia_fidl_llcpp_library",
)
定义驱动程序服务协议
该驱动程序使用自定义 FIDL 协议公开 edu
设备的功能。将新的 qemu_edu/qemu_edu.fidl
文件添加到项目工作区,并添加以下内容:
qemu_edu/fidl/qemu_edu.fidl
:
library examples.qemuedu;
using zx;
/// The Device protocol provides access to the functions of the QEMU edu hardware.
@discoverable
open protocol Device {
/// Computes the factorial of 'input' using the edu device and returns the
/// result.
flexible ComputeFactorial(struct {
input uint32;
}) -> (struct {
output uint32;
}) error zx.Status;
/// Performs a liveness check and return true if the device passes.
flexible LivenessCheck() -> (struct {
result bool;
}) error zx.Status;
};
/// The Service enables the driver framework to offer the Device protocol to
/// other components.
service Service {
device client_end:Device;
};
此 FIDL 协议提供了两种与 edu
设备上的阶乘计算和活跃性检查硬件寄存器交互的方法。
将以下 build 规则添加到项目的 build 配置底部,以编译 FIDL 库并生成 C++ 绑定:
fuchsia_fidl_library()
:声明examples.qemuedu
FIDL 库并描述其包含的 FIDL 源文件。fuchsia_fidl_llcpp_library()
:介绍为组件与此 FIDL 库交互而生成的 C++ 线程绑定。
qemu_edu/fidl/BUILD.bazel
:
fuchsia_fidl_library(
name = "examples.qemuedu",
srcs = [
"qemu_edu.fidl",
],
library = "examples.qemuedu",
visibility = ["//visibility:public"],
deps = [
"@fuchsia_sdk//fidl/zx",
],
)
fuchsia_fidl_llcpp_library(
name = "examples.qemuedu_cc",
library = ":examples.qemuedu",
visibility = ["//visibility:public"],
deps = [
"@fuchsia_sdk//fidl/zx:zx_llcpp_cc",
"@fuchsia_sdk//pkg/fidl_cpp_wire",
],
)
实现驱动程序服务协议
定义 FIDL 协议后,您需要更新驱动程序,以实现此协议并将其提供给其他组件。
完成本部分后,项目应具有以下目录结构:
//fuchsia-codelab/qemu_edu/drivers
|- BUILD.bazel
|- meta
| |- qemu_edu.cml
|- edu_device.cc
|- edu_device.h
|- edu_server.cc
|- edu_server.h
|- qemu_edu.bind
|- qemu_edu.cc
|- qemu_edu.h
在项目目录中创建一个新的 qemu_edu/drivers/edu_server.h
文件,并在其中添加以下内容,以包含 examples.qemuedu
库的 FIDL 绑定,并创建一个新的 QemuEduServer
类来实现 FIDL 协议的服务器端:
qemu_edu/drivers/edu_server.h
:
#ifndef FUCHSIA_CODELAB_QEMU_EDU_SERVER_H_
#define FUCHSIA_CODELAB_QEMU_EDU_SERVER_H_
#include <fidl/examples.qemuedu/cpp/wire.h>
#include <lib/driver/logging/cpp/structured_logger.h>
#include "edu_device.h"
namespace qemu_edu {
// FIDL server implementation for the `examples.qemuedu/Device` protocol.
class QemuEduServer : public fidl::WireServer<examples_qemuedu::Device> {
public:
explicit QemuEduServer(std::weak_ptr<edu_device::QemuEduDevice> device)
: device_(std::move(device)) {}
static fidl::ServerBindingRef<examples_qemuedu::Device> BindDeviceClient(
async_dispatcher_t* dispatcher, std::weak_ptr<edu_device::QemuEduDevice> device,
fidl::ServerEnd<examples_qemuedu::Device> request) {
// Bind each connection request to a unique FIDL server instance
auto server_impl = std::make_unique<QemuEduServer>(device);
return fidl::BindServer(dispatcher, std::move(request), std::move(server_impl),
std::mem_fn(&QemuEduServer::OnUnbound));
}
// This method is called when a server connection is torn down.
void OnUnbound(fidl::UnbindInfo info, fidl::ServerEnd<examples_qemuedu::Device> server_end) {
if (info.is_peer_closed()) {
FDF_LOG(DEBUG, "Client disconnected");
} else if (!info.is_user_initiated()) {
FDF_LOG(ERROR, "Client connection unbound: %s", info.status_string());
}
}
// fidl::WireServer<examples_qemuedu::Device>
void ComputeFactorial(ComputeFactorialRequestView request,
ComputeFactorialCompleter::Sync& completer) override;
void LivenessCheck(LivenessCheckCompleter::Sync& completer) override;
void handle_unknown_method(fidl::UnknownMethodMetadata<examples_qemuedu::Device> metadata,
fidl::UnknownMethodCompleter::Sync& completer) override;
private:
std::weak_ptr<edu_device::QemuEduDevice> device_;
};
} // namespace qemu_edu
#endif // FUCHSIA_CODELAB_QEMU_EDU_SERVER_H_
在项目目录中创建一个新的 qemu_edu/drivers/edu_server.cc
文件,并在其中添加以下内容,以实现 examples.qemuedu/Device
协议方法并将其映射到设备资源方法:
qemu_edu/drivers/edu_server.cc
:
#include "edu_server.h"
namespace qemu_edu {
// Driver Service: Compute factorial on the edu device
void QemuEduServer::ComputeFactorial(ComputeFactorialRequestView request,
ComputeFactorialCompleter::Sync& completer) {
auto edu_device = device_.lock();
if (!edu_device) {
FDF_LOG(ERROR, "Unable to access device resources.");
completer.ReplyError(ZX_ERR_BAD_STATE);
return;
}
uint32_t input = request->input;
edu_device->ComputeFactorial(
input, [completer = completer.ToAsync()](zx::result<uint32_t> result_status) mutable {
if (result_status.is_error()) {
completer.ReplyError(result_status.error_value());
return;
}
uint32_t factorial = result_status.value();
completer.ReplySuccess(factorial);
});
}
// Driver Service: Complete a liveness check on the edu device
void QemuEduServer::LivenessCheck(LivenessCheckCompleter::Sync& completer) {
auto edu_device = device_.lock();
if (!edu_device) {
FDF_LOG(ERROR, "Unable to access device resources.");
completer.ReplyError(ZX_ERR_BAD_STATE);
return;
}
constexpr uint32_t kChallenge = 0xdeadbeef;
constexpr uint32_t kExpectedResponse = ~(kChallenge);
auto status = edu_device->LivenessCheck(kChallenge);
if (status.is_error()) {
FDF_LOG(ERROR, "Unable to send liveness check request.");
completer.ReplyError(status.error_value());
return;
}
const bool alive = (status.value() == kExpectedResponse);
FDF_SLOG(INFO, "Replying with", KV("result", alive));
completer.ReplySuccess(alive);
}
} // namespace qemu_edu
更新驱动程序的组件清单,以声明并将 FIDL 协议作为功能公开:
qemu_edu/drivers/meta/qemu_edu.cml
:
{
include: [
"syslog/client.shard.cml",
],
program: {
runner: 'driver',
binary: 'driver/libqemu_edu.so',
bind: 'meta/bind/qemu_edu.bindbc',
// Identifies the device categories, for compatibility tests. This
// example driver uses the 'misc' category; real drivers should
// select a more specific category.
device_categories: [
{ category: 'misc', subcategory: '' },
],
},
use: [
{ service: 'fuchsia.hardware.pci.Service' },
],
// Provide the device capability to other components
capabilities: [
{ service: 'examples.qemuedu.Service' },
],
expose: [
{
service: 'examples.qemuedu.Service',
from: 'self',
},
],
}
更新驱动程序的 build 配置,使其依赖于新 examples.qemuedu
库的 FIDL 绑定:
qemu_edu/drivers/BUILD.bazel
:
fuchsia_cc_driver(
name = "qemu_edu",
srcs = [
"edu_device.cc",
"edu_device.h",
"edu_server.cc",
"edu_server.h",
"qemu_edu.cc",
"qemu_edu.h",
],
deps = [
"//fuchsia-codelab/qemu_edu/fidl:examples.qemuedu_cc",
"@fuchsia_sdk//fidl/fuchsia.hardware.pci:fuchsia.hardware.pci_llcpp_cc",
"@fuchsia_sdk//pkg/driver_component_cpp",
"@fuchsia_sdk//pkg/driver_devfs_cpp",
"@fuchsia_sdk//pkg/hwreg",
"@fuchsia_sdk//pkg/mmio",
],
)
导出和提供协议
qemu_edu
驱动程序使用 devfs 使 examples.qemuedu/Device
协议可供其他组件发现。如需发现系统中提供哪些驱动程序服务,非驱动程序组件会查找设备文件系统(通常挂载到组件的命名空间中的 /dev
),并扫描此文件系统下的目录和文件。
当提供与已知设备类匹配的协议 ID 时,驱动程序管理器可以将 devfs 中的条目别名为特定设备类条目(例如 /dev/class/input
)。如果非驱动程序组件不知道 devfs 中驱动程序服务的确切路径,而只知道特定类型。在本练习中,edu
设备不属于任何已知类别,因此您将此条目配置为未分类设备。
更新驱动程序组件的清单以请求 fuchsia.devics.fs.Exporter
capability:
qemu_edu/drivers/meta/qemu_edu.cml
:
{
include: [
"syslog/client.shard.cml",
],
program: {
runner: 'driver',
binary: 'driver/libqemu_edu.so',
bind: 'meta/bind/qemu_edu.bindbc',
// Identifies the device categories, for compatibility tests. This
// example driver uses the 'misc' category; real drivers should
// select a more specific category.
device_categories: [
{ category: 'misc', subcategory: '' },
],
},
use: [
{ service: 'fuchsia.hardware.pci.Service' },
],
// Provide the device capability to other components
capabilities: [
{ service: 'examples.qemuedu.Service' },
],
expose: [
{
service: 'examples.qemuedu.Service',
from: 'self',
},
],
}
更新驱动程序的 Start()
方法,以开始向与设备节点的拓扑路径匹配的新 devfs 条目提供 examples.qemuedu/Device
协议:
qemu_edu/drivers/qemu_edu.cc
:
#include "qemu_edu.h"
#include <lib/driver/component/cpp/driver_export.h>
#include "edu_server.h"
// Initialize this driver instance
zx::result<> QemuEduDriver::Start() {
// ...
// Report the version information from the edu device.
auto version_reg = device_->IdentificationRegister();
FDF_SLOG(INFO, "edu device version", KV("major", version_reg.major_version()),
KV("minor", version_reg.minor_version()));
// Serve the examples.qemuedu/Service capability.
examples_qemuedu::Service::InstanceHandler handler({
.device = fit::bind_member<&QemuEduDriver::Serve>(this),
});
auto add_result = outgoing()->AddService<examples_qemuedu::Service>(std::move(handler));
if (add_result.is_error()) {
FDF_SLOG(ERROR, "Failed to add Device service", KV("status", add_result.status_string()));
return add_result.take_error();
}
if (zx::result result = ExportToDevfs(); result.is_error()) {
FDF_SLOG(ERROR, "Failed to export to devfs", KV("status", result.status_string()));
return result.take_error();
}
// Create and export a devfs entry for the driver service.
return zx::ok();
}
其他组件现在可以发现 qemu_edu
驱动程序的功能。
重新构建驱动程序
使用 bazel build
命令验证驱动程序是否在代码更改后成功构建:
bazel build //fuchsia-codelab/qemu_edu/drivers:pkg
恭喜!您已成功从 Fuchsia 驱动程序公开 FIDL 服务。