公开驱动程序功能

驱动程序组件通过capabilities提供它们为其他驱动程序和非驱动程序组件提供的功能和服务。这样,Fuchsia 的组件框架就可以在必要时将这些功能路由到目标组件。驱动程序还可以将其功能导出到 devfs,以便其他组件以装载在组件命名空间中的文件节点的形式发现它们。

在本部分中,您将公开 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(
    "@fuchsia_sdk//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 配置的底部,以编译 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 驱动程序使 examples.qemuedu/Device 协议可被使用 devfs 的其他组件发现。为了发现系统中有哪些驱动程序服务,非驱动程序组件将查询设备文件系统(通常装载到组件命名空间中的 /dev),并扫描此文件系统下的目录和文件。

如果提供了与已知设备类匹配的协议 ID,驱动程序管理器可以将 devfs 中的条目别名设置为特定的设备类条目(例如 /dev/class/input)。如果非驱动程序组件不知道 devfs 中驱动程序服务的确切路径,而是不知道具体的类型。 在本练习中,edu 设备不符合已知类别,因此您需要将此条目配置为未分类设备。

更新驱动程序组件的清单以请求 fuchsia.devics.fs.Exporter 功能:

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() 方法,以开始将 examples.qemuedu/Device 协议提供给与设备节点的拓扑路径匹配的新 devfs 条目:

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 服务。