[docs][drivers] Update FIDL tutorial to DFv2 Remove the legacy DFv1 caution banner and update the driver FIDL tutorial to use DFv2 APIs (fdf::DriverBase2, outgoing()->AddService, fdf::MakeOffer2, AddChild, context.incoming().Connect, and fdf::error) as well as DFv2 component manifest declarations and prerequisite links. Bug: 534366492 Test: None, documentation change. Change-Id: Ib2ce2391f437b0ae1c18cf4e133d56b9ad32681b Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/1872266 Fuchsia-Auto-Submit: Justin Mattson <jmatt@google.com> Reviewed-by: Garratt Gallagher <garratt@google.com> Commit-Queue: Justin Mattson <jmatt@google.com>
diff --git a/docs/development/drivers/tutorials/fidl-tutorial.md b/docs/development/drivers/tutorials/fidl-tutorial.md index 0b7e2c0..6eea6e6 100644 --- a/docs/development/drivers/tutorials/fidl-tutorial.md +++ b/docs/development/drivers/tutorials/fidl-tutorial.md
@@ -1,32 +1,31 @@ # FIDL tutorial -Caution: This page may contain information that is specific to the legacy -version of the driver framework (DFv1). - -This guide explains how to go about adding exporting a FIDL protocol from a -driver and utilize it in another driver. This guide assumes familiarity with the -following concepts: +This guide explains how to export a FIDL service from a parent driver and +utilize it in a child driver. This guide assumes familiarity with the following +concepts: * [FIDL](/docs/development/languages/fidl/README.md) -* [Driver Binding](/docs/development/drivers/concepts/device_driver_model/driver-binding.md) -* [DDKTL](/docs/development/drivers/concepts/driver_development/using-ddktl.md) -* [New C++ FIDL Bindings](/docs/development/languages/fidl/tutorials/cpp/README.md) +* [Driver binding](/docs/concepts/drivers/driver_binding.md) +* [Write a minimal driver](/docs/development/drivers/developer_guide/write-a-minimal-dfv2-driver.md) +* [Driver communication](/docs/concepts/drivers/driver_communication.md) +* [C++ FIDL Bindings](/docs/development/languages/fidl/tutorials/cpp/README.md) + +For a complete working example in the Fuchsia source tree, see the +[Zircon transport example](/examples/drivers/transport/zircon/v2/). ## FIDL Protocol Definition -The following snippets will utilize this FIDL protocol: +The following snippets utilize this FIDL definition: -``` +```fidl library fidl.examples.echo; const MAX_STRING_LENGTH uint64 = 32; -// The discoverable annotation is required, otherwise the protocol bindings -// will not have a name string generated. @discoverable -protocol Echo { +closed protocol Echo { /// Returns the input. - EchoString(struct { + strict EchoString(struct { value string:<MAX_STRING_LENGTH, optional>; }) -> (struct { response string:<MAX_STRING_LENGTH, optional>; @@ -40,136 +39,154 @@ ## Parent Driver (The Server) -We approximate here how a parent driver which implements the protocol being -called into would be written. Although not shown, we assume this class is -utilizing the DDKTL. +The parent driver implements the `fidl.examples.echo/Echo` protocol, serves +`fidl.examples.echo.EchoService` in its outgoing directory, and offers that +service when adding a child node. +### Driver Manifest + +In the parent driver's manifest (`.dml`), declare and expose the FIDL service +capability: + +```json5 +capabilities: [ + { service: "fidl.examples.echo.EchoService" }, +], +expose: [ + { + service: "fidl.examples.echo.EchoService", + from: "self", + }, +], ``` -// This class implement the fuchsia.examples.echo/Echo FIDL protocol using the -// new C++ FIDL bindings -class Device : public fidl::WireServer<fidl_examples_echo::Echo> { - // This is the main entry point for the driver. - static zx_status_t Bind(void* ctx, zx_device_t* parent) { - // When creating the device, we initialize it with a dispatcher provided by - // the driver framework. This dispatcher is allows us to schedule - // asynchronous work on the same thread as other drivers. You may opt to - // create your own dispatcher which is serviced on a thread you spawn if you - // desire instead. - auto* dispatcher = fdf::Dispatcher::GetCurrent()->async_dispatcher(); - auto device = std::make_unique<Device>(parent, dispatcher); +### Server Code - // We add the FIDL protocol we wish to export to our child to our outgoing - // directory. When a connection is attempted we will bind the server end of - // the channel pair to our server implementation. - zx::result = device->outgoing_.AddService<fidl_examples_echo::EchoService>( - fidl_examples_echo::EchoService::InstanceHandler({ - .echo = device->bindings_.CreateHandler(device.get(), dispatcher, - fidl::kIgnoreBindingClosure), - })); +```cpp +#include <fidl/fidl.examples.echo/cpp/wire.h> +#include <lib/driver/component/cpp/driver_base2.h> +#include <lib/driver/component/cpp/driver_export2.h> +#include <lib/driver/component/cpp/node_add_args.h> +#include <lib/driver/logging/cpp/logger.h> - // Utilizing the server end of the endpoint pair we created above, we bind - // it to our outgoing directory. - result = device->outgoing_.Serve(std::move(endpoints->server)); +// This class implements the fidl.examples.echo/Echo FIDL protocol using the +// C++ Wire bindings. +class ParentDriver : public fdf::DriverBase2, + public fidl::WireServer<fidl_examples_echo::Echo> { + public: + ParentDriver() : fdf::DriverBase2("parent") {} + + zx::result<> Start(fdf::DriverContext context) override { + // Add the FIDL service we wish to export to our child to our outgoing + // directory. When a connection is attempted we bind the server end of + // the channel pair to our server implementation using the driver's default + // dispatcher. + fidl_examples_echo::EchoService::InstanceHandler handler({ + .echo = bindings_.CreateHandler(this, dispatcher(), + fidl::kIgnoreBindingClosure), + }); + + zx::result result = + outgoing()->AddService<fidl_examples_echo::EchoService>(std::move(handler)); if (result.is_error()) { - zxlogf(ERROR, "Failed to service the outgoing directory"); - return result.status_value(); + fdf::error("Failed to add EchoService to outgoing directory: {}", result); + return result.take_error(); } - // We declare our outgoing protocols here. These will be utilize to - // help the framework populate node properties which can be used for - // binding. - std::array offers = { - fidl_examples_echo::Service::Name, - }; - - status = device->DdkAdd(ddk::DeviceAddArgs("parent") - // The device must be spawned in a separate - // driver host. - .set_flags(DEVICE_ADD_MUST_ISOLATE) - .set_fidl_service_offers(offers) - // The client side of outgoing directory is - // provided to the framework. This will be - // forwarded to the new driver host that spawns to - // allow the child driver which binds the ability - // to connect to our outgoing FIDL protocols. - .set_outgoing_dir(endpoints->client.TakeChannel())); - if (status == ZX_OK) { - [[maybe_unused]] auto ptr = device.release(); - } else { - zxlogf(ERROR, "Failed to add device"); + // Declare our outgoing service offer and add the child node. The driver + // framework uses this offer to route the service to the child and + // automatically populate node properties for binding. + std::vector<fuchsia_driver_framework::NodeProperty2> properties = {}; + zx::result child_result = AddChild( + "echo_child", properties, + std::array{fdf::MakeOffer2<fidl_examples_echo::EchoService>()}); + if (child_result.is_error()) { + fdf::error("Failed to add child node: {}", child_result); + return child_result.take_error(); } + controller_.Bind(std::move(child_result.value()), dispatcher()); - return status; + return zx::ok(); } private: - // This is the implementation of the only method our FIDL protocol requires. - void EchoString(EchoStringRequestView request, EchoStringCompleter::Sync& completer) override { + // Implementation of the method required by our FIDL protocol. + void EchoString(EchoStringRequestView request, + EchoStringCompleter::Sync& completer) override { completer.Reply(request->value); } - // This is a helper class which we use to serve the outgoing directory. - component::OutgoingDirectory outgoing_; - // This ensures that the fidl connections don't outlive the device object. + fidl::WireClient<fuchsia_driver_framework::NodeController> controller_; + // Ensures that the FIDL connections do not outlive the driver instance. fidl::ServerBindingGroup<fidl_examples_echo::Echo> bindings_; }; + +FUCHSIA_DRIVER_EXPORT2(ParentDriver); ``` ## Child Driver (The Client) -### Binding +### Driver Manifest -The first important thing to discuss is how the child driver will bind. It can -bind due to any number of node properties, but if you wish to bind based -on the FIDL service the parent offers, you can bind using `fuchsia.Service`: +In the child driver's manifest (`.dml`), declare that the driver uses the +service: -``` -fuchsia.Service == "fidl.examples.echo.EchoService"; +```json5 +use: [ + { service: "fidl.examples.echo.EchoService" }, +], ``` -You can add additional bind constraints if you desire. Note that the -`fuchsia.Service` property is automatically added when the parent driver declares -FIDL service offers at the time of adding the child node. +Note that the child driver's bind rules are automatically generated from its +DML. ### Client Code -The follow code snippet would be found in a child driver which has successfully -bound to the parent driver described above. +The following code snippet shows how a child driver that has bound to the parent +node connects to and calls the `Echo` protocol: +```cpp +#include <fidl/fidl.examples.echo/cpp/wire.h> +#include <lib/driver/component/cpp/driver_base2.h> +#include <lib/driver/component/cpp/driver_export2.h> +#include <lib/driver/logging/cpp/logger.h> + +class ChildDriver : public fdf::DriverBase2 { + public: + ChildDriver() : fdf::DriverBase2("child") {} + + zx::result<> Start(fdf::DriverContext context) override { + // Connect to the protocol offered by our parent driver through our incoming + // namespace. We do not need to specify the service or protocol name string + // because Connect is templated on the generated service member type. + zx::result client_end = + context.incoming().Connect<fidl_examples_echo::EchoService::Echo>(); + if (client_end.is_error()) { + fdf::error("Failed to connect to Echo protocol: {}", client_end); + return client_end.take_error(); + } + + // Turn the client side of the endpoint pair into a synchronous client. + fidl::WireSyncClient client{std::move(client_end.value())}; + + // Utilize our client to make calls. + constexpr std::string_view kInput = "Test String"; + + fidl::WireResult result = + client->EchoString(fidl::StringView::FromExternal(kInput)); + if (!result.ok()) { + fdf::error("Failed to call EchoString: {}", result.error()); + return zx::error(result.status()); + } + if (result->response.get() != kInput) { + fdf::error("Unexpected response: Actual: \"{}\", Expected: \"{}\"", + result->response.get(), kInput); + return zx::error(ZX_ERR_INTERNAL); + } + + return zx::ok(); + } +}; + +FUCHSIA_DRIVER_EXPORT2(ChildDriver); ``` -zx_status_t CallEcho() { - // The following method allows us to connect to the protocol we desire. This - // works by providing the server end of our endpoint pair to the framework. It - // will push this channel through the outgoing directory to our parent driver - // which will then bind it to its server implementation. We do not need to - // name the protocol because the method is templated on the channel type and - // it is able to automatically derive the name from the type. - zx::result client_end = DdkConnectFidlProtocol<fidl_examples_echo::EchoService::Echo>(); - if (client_end.is_error()) { - zxlogf(ERROR, "Failed to connect fidl protocol: %s", client_end.status_string()); - return client_end.status_value(); - } - - // We turn the client side of the endpoint pair into a synchronous client. - fidl::WireSyncClient client{std::move(client_end.value())}; - - // We can now utilize our client to make calls! - constexpr std::string_view kInput = "Test String"; - - auto result = client->EchoString(fidl::StringView::FromExternal(std::string_view(kInput))); - if (!result.ok()) { - zxlogf(ERROR, "Failed to call EchoString"); - return result.status(); - } - if (result->response.get() != kInput) { - zxlogf(ERROR, "Unexpected response: Actual: \"%.*s\", Expected: \"%.*s\"", - static_cast<int>(result->response.size()), result->response.data(), - static_cast<int>(kInput.size()), kInput.data()); - return ZX_ERR_INTERNAL; - } - - return ZX_OK; -} -``` -