Driver components offer the features and services they provide to other drivers and non-driver components through capabilities. This enables Fuchsia's component framework to route those capabilities to the target component when necessary. Drivers can also export their capabilities to devfs to enable other components to discover them as file nodes mounted in the component's namespace.
In this section, you'll expose the qemu_edu
driver's factorial capabilities
and consume those from a component running elsewhere in the system.
Create a new FIDL library
Create a new project directory in your Bazel workspace for a new FIDL library:
mkdir -p fuchsia-codelab/qemu_edu/fidl
After you complete this section, the project should have the following directory structure:
//fuchsia-codelab/qemu_edu/fidl
|- BUILD.bazel
|- qemu_edu.fidl
Create the qemu_edu/fidl/BUILD.bazel
file and add the following statement to
include the necessary build rules from the Fuchsia SDK:
qemu_edu/fidl/BUILD.bazel
:
load(
"@fuchsia_sdk//fuchsia:defs.bzl",
"fuchsia_fidl_library",
"fuchsia_fidl_llcpp_library",
)
Define a driver service protocol
The driver exposes the capabilities of the edu
device using a custom FIDL
protocol. Add a new qemu_edu/qemu_edu.fidl
file to your project workspace with
the following contents:
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;
};
This FIDL protocol provides two methods to interact with the factorial
computation and liveness check hardware registers on the edu
device.
Add the following build rules to the bottom of the project's build configuration to compile the FIDL library and generate C++ bindings:
fuchsia_fidl_library()
: Declares theexamples.qemuedu
FIDL library and describes the FIDL source files it includes.fuchsia_fidl_llcpp_library()
: Describes the generated C++ wirebindings for components to interact with this FIDL library.
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",
],
)
Implement the driver service protocol
With the FIDL protocol defined, you'll need to update the driver to implement and serve this protocol to other components.
After you complete this section, the project should have the following directory structure:
//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
Create the new qemu_edu/drivers/edu_server.h
file in your project directory
with the following contents to include the FIDL bindings for the
examples.qemuedu
library and create a new QemuEduServer
class to
implement the server end of the FIDL protocol:
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_
Create the new qemu_edu/drivers/edu_server.cc
file in your project directory
with the following contents to implement the examples.qemuedu/Device
protocol methods and map them to the device resource methods:
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
Update the driver's component manifest to declare and expose the FIDL protocol as a 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',
},
],
}
Update the driver's build configuration to depend on the FIDL bindings for the
new examples.qemuedu
library:
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",
],
)
Export and serve the protocol
The qemu_edu
driver makes the examples.qemuedu/Device
protocol
discoverable to other components using devfs. To discover which driver services
are available in the system, a non-driver component would look up the device
filesystem (usually mounted to /dev
in a component’s namespace) and scan for
the directories and files under this filesystem.
Driver manager can alias entries in devfs to a specific device class entry
(for example, /dev/class/input
) when a matching protocol ID to a known
device class is provided. If a non-driver component does not know the exact path
of the driver service in devfs, but rather a specific type.
For this exercise, the edu
device does not conform to a known class so you'll
configure this entry as an unclassified device.
Update the driver component's manifest to request the 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',
},
],
}
Update the driver's Start()
method to begin serving the examples.qemuedu/Device
protocol
to a new devfs entry that matches the device node's topological path:
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();
}
The qemu_edu
driver's capabilities are now discoverable by other components.
Rebuild the driver
Use the bazel build
command to verify that the driver builds successfully with
your code changes:
bazel build //fuchsia-codelab/qemu_edu/drivers:pkg
Congratulations! You've successfully exposed FIDL services from a Fuchsia driver.