Software on Fuchsia interacts with driver components through their exposed entries in devfs. Once a client connects to the driver's devfs entry, it receives an instance of the FIDL service representing that driver.
In this section, you'll create a new eductl
executable that discovers and
interacts with the capabilities exposed by the qemu_edu
driver.
Create a new tools component
Create a new project directory in your Bazel workspace for a new binary tool:
mkdir -p fuchsia-codelab/qemu_edu/tools
After you complete this section, the project should have the following directory structure:
//fuchsia-codelab/qemu_edu/tools
|- BUILD.bazel
|- eductl.cc
Create the qemu_edu/tools/BUILD.bazel
file and add the following statement to
include the necessary build rules from the Fuchsia SDK:
qemu_edu/tools/BUILD.bazel
:
load(
"@fuchsia_sdk//fuchsia:defs.bzl",
"fuchsia_cc_binary",
"fuchsia_component",
"fuchsia_component_manifest",
"fuchsia_driver_tool",
"fuchsia_package",
)
Create a new qemu_edu/tools/eductl.cc
file with the following code to set up a
basic command line executable:
qemu_edu/tools/eductl.cc
:
#include <ctype.h>
#include <dirent.h>
#include <fcntl.h>
#include <getopt.h>
#include <lib/fdio/directory.h>
#include <libgen.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <filesystem>
int usage(const char* cmd) {
fprintf(stderr,
"\nInteract with the QEMU edu device:\n"
" %s live Performs a card liveness check\n"
" %s fact <n> Computes the factorial of n\n"
" %s help Print this message\n",
cmd, cmd, cmd);
return -1;
}
// Returns "true" if the argument matches the prefix.
// In this case, moves the argument past the prefix.
bool prefix_match(const char** arg, const char* prefix) {
if (!strncmp(*arg, prefix, strlen(prefix))) {
*arg += strlen(prefix);
return true;
}
return false;
}
constexpr long kBadParse = -1;
long parse_positive_long(const char* number) {
char* end;
long result = strtol(number, &end, 10);
if (end == number || *end != '\0' || result < 0) {
return kBadParse;
}
return result;
}
int main(int argc, char* argv[]) {
const char* cmd = basename(argv[0]);
// ...
return usage(cmd);
}
This executable supports two subcommands to execute the liveness check and factorial computation.
Add the following new rules to the bottom of the project's build configuration to build this new tool into a Fuchsia package:
qemu_edu/tools/BUILD.bazel
:
fuchsia_cc_binary(
name = "eductl",
srcs = [
"eductl.cc",
],
deps = [
"@fuchsia_sdk//pkg/component_incoming_cpp",
"@fuchsia_sdk//pkg/fdio",
"@fuchsia_sdk//pkg/fidl_cpp_wire",
],
)
fuchsia_driver_tool(
name = "eductl_tool",
binary = ":eductl",
visibility = ["//visibility:public"],
)
fuchsia_package(
name = "pkg",
package_name = "eductl",
tools = [
":eductl_tool",
],
visibility = ["//visibility:public"],
)
Implement the client tool
When clients open a connection to an entry in devfs, they receive an instance of
the FIDL protocol being served by the driver. Add the following code to eductl
to open a connection to the edu
device using its devfs path:
qemu_edu/tools/eductl.cc
:
#include <ctype.h>
#include <dirent.h>
#include <fcntl.h>
#include <getopt.h>
#include <lib/fdio/directory.h>
#include <libgen.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <filesystem>
#include <fidl/examples.qemuedu/cpp/wire.h>
constexpr char kDevfsRootPath[] = "/dev/sys/platform";
constexpr char kEduDevicePath[] = "qemu-edu";
// Search for the device file entry in devfs
std::optional<std::string> SearchDevicePath() {
for (auto const& dir_entry : std::filesystem::recursive_directory_iterator(kDevfsRootPath)) {
if (dir_entry.path().string().find(kEduDevicePath) != std::string::npos) {
return {dir_entry.path()};
}
}
return {};
}
// Open a FIDL client connection to the examples.qemuedu.Device
fidl::WireSyncClient<examples_qemuedu::Device> OpenDevice() {
auto device_path = SearchDevicePath();
if (!device_path.has_value()) {
fprintf(stderr, "Unable to locate %s device path in search of %s/*/\n", kEduDevicePath,
kDevfsRootPath);
return {};
}
zx::result endpoints = fidl::CreateEndpoints<examples_qemuedu::Device>();
if (endpoints.is_error()) {
fprintf(stderr, "Failed to create endpoints: %s\n", endpoints.status_string());
return {};
}
if (zx_status_t status = fdio_service_connect(device_path.value().c_str(),
endpoints->server.TakeChannel().release());
status != ZX_OK) {
fprintf(stderr, "Failed to connect to device: %s\n", zx_status_get_string(status));
return {};
}
return fidl::WireSyncClient(std::move(endpoints->client));
}
// ...
Add liveness_check()
and compute_factorial()
functions to call methods using
the examples.qemuedu/Device
FIDL protocol returned from OpenDevice()
.
Finally, update the tool's main()
function to call the appropriate device
function based on the argument passed on the command line:
qemu_edu/tools/eductl.cc
:
// ...
// Run a liveness check on the QEMU edu device.
// Returns 0 on success.
int liveness_check() {
auto client = OpenDevice();
if (!client.is_valid()) {
return -1;
}
auto liveness_check_result = client->LivenessCheck();
if (!liveness_check_result.ok()) {
fprintf(stderr, "Error: failed to get liveness check result: %s\n",
zx_status_get_string(liveness_check_result.status()));
return -1;
}
if (liveness_check_result->value()->result) {
printf("Liveness check passed!\n");
return 0;
} else {
printf("Liveness check failed!\n");
return -1;
}
}
// Compute the factorial of n using the QEMU edu device.
// Returns 0 on success.
int compute_factorial(long n) {
auto client = OpenDevice();
if (!client.is_valid()) {
return -1;
}
if (n >= std::numeric_limits<uint32_t>::max()) {
fprintf(stderr, "N is too large\n");
return -1;
}
uint32_t input = static_cast<uint32_t>(n);
auto compute_factorial_result = client->ComputeFactorial(input);
if (!compute_factorial_result.ok()) {
fprintf(stderr, "Error: failed to call compute factorial result: %s\n",
zx_status_get_string(compute_factorial_result.status()));
return -1;
}
printf("Factorial(%u) = %u\n", input, compute_factorial_result->value()->output);
return 0;
}
int main(int argc, char* argv[]) {
const char* cmd = basename(argv[0]);
// If no arguments passed, bail out after dumping
// usage information.
if (argc < 2) {
return usage(cmd);
}
const char* arg = argv[1];
if (prefix_match(&arg, "live")) {
return liveness_check();
} else if (prefix_match(&arg, "fact")) {
if (argc < 3) {
fprintf(stderr, "Expecting 1 argument\n");
return usage(cmd);
}
long n = parse_positive_long(argv[2]);
return compute_factorial(n);
}
return usage(cmd);
}
Update the tool's build configuration to depend on the FIDL bindings for the
examples.qemuedu
library:
qemu_edu/tools/BUILD.bazel
:
fuchsia_cc_binary(
name = "eductl",
srcs = [
"eductl.cc",
],
deps = [
"//fuchsia-codelab/qemu_edu/fidl:examples.qemuedu_cc",
"@fuchsia_sdk//pkg/component_incoming_cpp",
"@fuchsia_sdk//pkg/fdio",
"@fuchsia_sdk//pkg/fidl_cpp_wire",
],
)
Restart the emulator
Shut down any existing emulator instances:
ffx emu stop --all
Start a new instance of the Fuchsia emulator with driver framework enabled:
ffx emu start core.x64 --headless
Reload the driver
Use the bazel run
command to build and execute the driver component target:
bazel run //fuchsia-codelab/qemu_edu/drivers:pkg.component
Run the tool
Use the bazel run
command to build and execute the tool, passing the arguments
fact 12
to compute the factorial of 12:
bazel run //fuchsia-codelab/qemu_edu/tools:pkg.eductl_tool -- fact 12
The bazel run
command performs the following steps:
- Build the executable and package.
- Publish the package to a local package repository.
- Register the package repository with the target device.
- Use
ffx driver run-tool
to run the binary inside thedriver_playground
component.
The command prints output similar to the following with the computation result the factorial:
Factorial(12) = 479001600
Congratulations! You've successfully connected to your driver's exposed services from a separate client.