与司机互动

Fuchsia 上的软件通过 devfs 中的公开条目与驱动程序组件进行交互。客户端连接到驱动程序的 devfs 条目后,便会接收表示该驱动程序的 FIDL 服务的实例。

在本部分中,您将创建一个新的 eductl 可执行文件,用于发现 qemu_edu 驱动程序公开的功能并与之互动。

创建新的工具组件

在 Bazel 工作区中,为新的二进制工具创建新的项目目录:

mkdir -p fuchsia-codelab/qemu_edu/tools

完成本部分后,项目应具有以下目录结构:

//fuchsia-codelab/qemu_edu/tools
                  |- BUILD.bazel
                  |- eductl.cc

创建 qemu_edu/tools/BUILD.bazel 文件并添加以下语句,以包含 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",
)

使用以下代码创建新的 qemu_edu/tools/eductl.cc 文件,以设置基本的命令行可执行文件:

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);
}

此可执行文件支持两个子命令来执行活跃性检查和阶乘计算。

在项目的 build 配置底部添加以下新规则,将这个新工具构建到 Fuchsia 软件包中:

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"],
)

实现客户端工具

当客户端打开与 devfs 中条目的连接时,会收到驱动程序提供的 FIDL 协议的实例。将以下代码添加到 eductl,以便使用 edu 设备的 devfs 路径打开连接:

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));
}

// ...

添加了 liveness_check()compute_factorial() 函数,以便使用从 OpenDevice() 返回的 examples.qemuedu/Device FIDL 协议来调用方法。最后,更新该工具的 main() 函数,以根据命令行中传递的参数调用相应的设备函数:

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);
}

更新工具的 build 配置,使其依赖于 examples.qemuedu 库的 FIDL 绑定:

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",
    ],
)

重启模拟器

关停所有现有的模拟器实例:

ffx emu stop --all

启动启用了驱动程序框架的 Fuchsia 模拟器的新实例:

ffx emu start core.x64 --headless

重新加载驱动程序

使用 bazel run 命令构建并执行驱动程序组件目标:

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

运行该工具

使用 bazel run 命令构建并执行该工具,同时传递参数 fact 12 以计算 12 的阶乘:

bazel run //fuchsia-codelab/qemu_edu/tools:pkg.eductl_tool -- fact 12

bazel run 命令会执行以下步骤:

  1. 构建可执行文件和软件包。
  2. 将软件包发布到本地软件包代码库。
  3. 向目标设备注册软件包代码库。
  4. 使用 ffx driver run-tooldriver_playground 组件内运行二进制文件。

该命令会输出类似于以下内容的输出,其中计算结果为阶乘:

Factorial(12) = 479001600

恭喜!您已成功从单独的客户端连接到驱动程序的公开服务。