Google celebrates Hispanic Heritage Month. See how.

Interact with the driver

Fuchsia components interact with driver components through their exposed entries in devfs. Once a client component 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 component 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 tools component:

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
                  |- meta
                  |   |- eductl.cml
                  |- 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(
    "@rules_fuchsia//fuchsia:defs.bzl",
    "fuchsia_cc_binary",
    "fuchsia_component",
    "fuchsia_component_manifest",
    "fuchsia_package",
)

Create a new qemu_edu/tools/meta/eductl.cml component manifest file to the project with the following contents:

qemu_edu/tools/meta/eductl.cml:

{
    include: [
        "syslog/client.shard.cml",
    ],
    program: {
        runner: 'elf',
        binary: 'bin/eductl',
        args: [
          'factorial',
          '12',
        ]
    },
    use: [
        {
            directory: "dev",
            rights: [ "rw*" ],
            path: "/dev",
        },
    ],
}

This component requests the dev directory capability, which enables it to discover and access entries in devfs. 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>

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 component into a Fuchsia package:

qemu_edu/tools/BUILD.bazel:

fuchsia_cc_binary(
    name = "eductl",
    srcs = [
        "eductl.cc",
    ],
    deps = [
        "@fuchsia_sdk//pkg/fdio",
        "@fuchsia_sdk//pkg/fidl_cpp_wire",
    ],
)

fuchsia_component_manifest(
    name = "manifest",
    src = "meta/eductl.cml",
    includes = [
        "@fuchsia_sdk//pkg/syslog:client",
    ],
)

fuchsia_component(
    name = "component",
    manifest = ":manifest",
    deps = [
        ":eductl",
    ],
)

fuchsia_package(
    name = "pkg",
    package_name = "eductl",
    visibility = ["//visibility:public"],
    deps = [
        ":component",
    ],
)

Implement the client tool

When client components 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 the tools component 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 <fidl/fuchsia.examples.qemuedu/cpp/wire.h>

constexpr char kEduDevicePath[] =
    "/dev/sys/platform/platform-passthrough/PCI0/bus/00:06.0_/qemu-edu";

// Open a FIDL client connection to the fuchsia.examples.qemuedu.Device
fidl::WireSyncClient<fuchsia_examples_qemuedu::Device> OpenDevice() {
  int device = open(kEduDevicePath, O_RDWR);

  if (device < 0) {
    fprintf(stderr, "Failed to open qemu edu device: %s\n", strerror(errno));
    return {};
  }

  fidl::ClientEnd<fuchsia_examples_qemuedu::Device> client_end;
  zx_status_t st = fdio_get_service_handle(device, client_end.channel().reset_and_get_address());
  if (st != ZX_OK) {
    fprintf(stderr, "Failed to get service handle: %s\n", zx_status_get_string(st));
    return {};
  }

  return fidl::BindSyncClient(std::move(client_end));
}

// ...

Add liveness_check() and compute_factorial() functions to call methods using the fuchsia.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 tools component's build configuration to depend on the FIDL bindings for the fuchsia.examples.qemuedu library:

qemu_edu/tools/BUILD.bazel:

fuchsia_cc_binary(
    name = "eductl",
    srcs = [
        "eductl.cc",
    ],
    deps = [
        "//fuchsia-codelab/qemu_edu/fidl:fuchsia.examples.qemuedu_cc",
        "@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 workstation_eng.qemu-x64 --headless \
 --kernel-args "driver_manager.use_driver_framework_v2=true" \
 --kernel-args "driver_manager.root-driver=fuchsia-boot:///#meta/platform-bus.cm" \
 --kernel-args "devmgr.enable-ephemeral=true"

Reload the driver

Use the bazel run command to build and execute the driver component target:

bazel run --config=fuchsia_x64 //fuchsia-codelab/qemu_edu/drivers:pkg.component

Run the tool

Use the bazel run command to build and execute the tools component target:

bazel run --config=fuchsia_x64 //fuchsia-codelab/qemu_edu/tools:pkg.component

The bazel run command performs the following steps:

  1. Build the component and package.
  2. Publish the package to a local package repository.
  3. Register the package repository with the target device.
  4. Use ffx component run --recreate to run the component inside the ffx-laboratory.

Inspect the system log and verify that you can see the driver responding to a request from the eductl component, followed by the tool printing the result:

ffx log --filter qemu_edu --filter eductl
[universe-pkg-drivers:root.sys.platform.platform-passthrough.PCI0.bus.00_06_0_][qemu-edu,driver][I]: [fuchsia-codelab/qemu_edu/qemu_edu.cc:232] Replying with factorial=479001600
[ffx-laboratory:eductl][][I] Factorial(12) = 479001600

Congratulations! You've successfully connected to your driver from a separate client component using its exposed services.