# Expose the driver capabilities

Driver components offer the features and services they provide to other drivers
and non-driver components through [capabilities](https://fuchsia.dev/docs/concepts/components/v2/capabilities/README).
This enables Fuchsia's component framework to route those capabilities to the
target component when necessary. For more details, see the
[Driver Communication](https://fuchsia.dev/docs/concepts/drivers/driver_communication) guide.

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(
        "@rules_fuchsia//fuchsia:defs.bzl",
        "fuchsia_fidl_library",
    )

## Define a device service

The driver exposes the capabilities of the `edu` device using a custom FIDL
service. This service contains one or more protocols that clients can connect
to. 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 file defines the `Device` protocol with two methods, and the
`DeviceService` which makes the `Device` protocol available to clients.

Add the following build rule to the bottom of the project's build configuration
to compile the FIDL library and generate C++ bindings:

- `fuchsia_fidl_library()`: Declares the `examples.qemuedu` FIDL library and describes the FIDL source files it includes.

`qemu_edu/fidl/BUILD.bazel`:

    fuchsia_fidl_library(
        name = "examples.qemuedu",
        srcs = [
            "qemu_edu.fidl",
        ],
        library = "examples.qemuedu",
        cc_bindings = [
            "cpp",
            "cpp_wire",
            "cpp_driver_wire",
            "cpp_driver",
        ],
        target_compatible_with = ["@platforms//os:fuchsia"],
        visibility = ["//visibility:public"],
        deps = [
            "@fuchsia_sdk//fidl/zx",
        ],
    )

## Implement the device service

With the FIDL service defined, you'll need to update the driver to implement
the `Device` protocol and serve the `DeviceService` 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 `Device` 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 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_cpp",
    "@fuchsia_sdk//fidl/fuchsia.hardware.pci:fuchsia.hardware.pci_cpp",
            "@fuchsia_sdk//pkg/driver_component_cpp",
            "@fuchsia_sdk//pkg/driver_devfs_cpp",
            "@fuchsia_sdk//pkg/hwreg",
            "@fuchsia_sdk//pkg/driver_mmio_cpp",
        ],
    )

## Export and serve the service

The `qemu_edu` driver makes the `fuchsia.examples.qemuedu.DeviceService`
discoverable to other components by serving it from its outgoing directory.
To do this, the driver must:

- Declare this service as a `capability` in its component manifest.
- `expose` this capability to the component framework in its component manifest.

This allows other components, including non-driver components, to request the
service by its type without needing to know the topological path of the
driver. The component framework is responsible for routing the service connection
from the client to the driver that provides it.

Update the driver's component manifest to declare and expose the device service
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',
    },
    \],
    }

With the manifest updated, the component framework can now route requests for
this service. The final step is for the driver to implement the server for the
service and serve it on its outgoing directory.

Update the driver's `Start()` method to begin serving the `fuchsia.examples.qemuedu.DeviceService`
from the component's outgoing directory:

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

      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.
[](https://issues.fuchsia.dev/issues/new?component=1439230&template=1881640&description=Issue+on:+https://fuchsia.dev/docs/development/drivers/tutorials/sdk_build_driver/driver-service.md "Report a bug or suggest improvements on this page") [](https://cs.opensource.google/fuchsia/fuchsia/+/main:docs/development/drivers/tutorials/sdk_build_driver/driver-service.md "View the source code of this page") [](https://ci.android.com/edit?repo=fuchsia/fuchsia/main&file=docs/development/drivers/tutorials/sdk_build_driver/driver-service.md&cloneRepo=fuchsia/fuchsia:refs/heads/main&fullPath=fuchsia/docs/development/drivers/tutorials/sdk_build_driver/driver-service.md "Edit the source code of this page")