[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;
-}
-```
-