[zircon][display] Add events to TestFidlClient.

TestFidlClient can now use an independent loop, adding fidelity to the
tests and allowing us to halt the controller and the client
independently. This is needed to test the locking protocol as it is
changed to something statically verifiable.

Test: display-core-unittests-test under fx emu ASAN.
Bug: 42686
Change-Id: I96d3898f4af63d96ac07c21d52adc539a3b8ef8a
diff --git a/zircon/system/dev/display/display/test/fidl_client.cc b/zircon/system/dev/display/display/test/fidl_client.cc
index 7bc2ace..d1f9599 100644
--- a/zircon/system/dev/display/display/test/fidl_client.cc
+++ b/zircon/system/dev/display/display/test/fidl_client.cc
@@ -1,8 +1,12 @@
 #include "fidl_client.h"
 
+#include <lib/async/cpp/task.h>
+
 #include <ddk/debug.h>
+#include <fbl/auto_lock.h>
 
 namespace fhd = ::llcpp::fuchsia::hardware::display;
+namespace sysmem = ::llcpp::fuchsia::sysmem;
 
 namespace display {
 
@@ -21,8 +25,14 @@
   manufacturer_name_ = fbl::String(info.manufacturer_name.data());
   monitor_name_ = fbl::String(info.monitor_name.data());
   monitor_serial_ = fbl::String(info.monitor_serial.data());
+  image_config_.height = modes_[0].vertical_resolution;
+  image_config_.width = modes_[0].horizontal_resolution;
+  image_config_.pixel_format = pixel_formats_[0];
+  image_config_.type = fhd::typeSimple;
 }
 
+uint64_t TestFidlClient::display_id() const { return displays_[0].id_; }
+
 bool TestFidlClient::CreateChannel(zx_handle_t provider, bool is_vc) {
   zx::channel device_server, device_client;
   zx::channel dc_server, dc_client;
@@ -52,14 +62,17 @@
       return false;
     }
   }
+
+  fbl::AutoLock lock(mtx());
   dc_ = std::make_unique<fhd::Controller::SyncClient>(std::move(dc_client));
   device_handle_.reset(device_client.release());
   return true;
 }
 
-bool TestFidlClient::Bind() {
-  zxlogf(INFO, "TestFidlClient::Bind waiting for displays\n");
-  while (displays_.is_empty()) {
+bool TestFidlClient::Bind(async_dispatcher_t* dispatcher) {
+  dispatcher_ = dispatcher;
+  while (displays_.is_empty() || !has_ownership_) {
+    fbl::AutoLock lock(mtx());
     auto result = dc_->HandleEvents({
         .displays_changed =
             [this](::fidl::VectorView<fhd::Info> added, ::fidl::VectorView<uint64_t> removed) {
@@ -83,8 +96,75 @@
     }
   }
 
-  zxlogf(INFO, "Turning on vsync\n");
+  fbl::AutoLock lock(mtx());
+
+  wait_events_.set_object(dc_->channel().get());
+  wait_events_.set_trigger(ZX_CHANNEL_READABLE);
+  EXPECT_OK(wait_events_.Begin(dispatcher));
   return dc_->EnableVsync(true).ok();
 }
 
+void TestFidlClient::OnEventMsgAsync(async_dispatcher_t* dispatcher, async::WaitBase* self,
+                                     zx_status_t status, const zx_packet_signal_t* signal) {
+  if (status != ZX_OK) {
+    return;
+  }
+
+  if (!(signal->observed & ZX_CHANNEL_READABLE)) {
+    return;
+  }
+
+  fbl::AutoLock lock(mtx());
+  auto result = dc_->HandleEvents({
+      .displays_changed = [](::fidl::VectorView<fhd::Info>,
+                             ::fidl::VectorView<uint64_t>) { return ZX_OK; },
+      // The FIDL bindings do not know that the caller holds mtx(), so we can't TA_REQ(mtx()) here.
+      .vsync =
+          [this](uint64_t, uint64_t, ::fidl::VectorView<uint64_t>) TA_NO_THREAD_SAFETY_ANALYSIS {
+            vsync_count_++;
+            return ZX_OK;
+          },
+      .client_ownership_change = [](bool) { return ZX_OK; },
+      .unknown = []() { return ZX_ERR_STOP; },
+  });
+
+  if (result != ZX_OK) {
+    zxlogf(ERROR, "Failed to handle events: %d\n", result);
+    return;
+  }
+
+  if (wait_events_.object() == ZX_HANDLE_INVALID) {
+    return;
+  }
+  // Re-arm the wait.
+  self->Begin(dispatcher);
+}
+
+TestFidlClient::~TestFidlClient() {
+  if (dispatcher_) {
+    // Cancel must be issued from the dispatcher thread.
+    sync_completion_t done;
+    auto task = new async::Task();
+    task->set_handler(
+        [this, task, done_ptr = &done](async_dispatcher_t*, async::Task*, zx_status_t) {
+          wait_events_.Cancel();
+          wait_events_.set_object(ZX_HANDLE_INVALID);
+
+          sync_completion_signal(done_ptr);
+          delete task;
+        });
+    if (task->Post(dispatcher_) != ZX_OK) {
+      delete task;
+      wait_events_.Cancel();
+      wait_events_.set_object(ZX_HANDLE_INVALID);
+    } else {
+      while (true) {
+        if (sync_completion_wait(&done, ZX_MSEC(10)) == ZX_OK) {
+          break;
+        }
+      }
+    }
+  }
+}
+
 }  // namespace display
diff --git a/zircon/system/dev/display/display/test/fidl_client.h b/zircon/system/dev/display/display/test/fidl_client.h
index e0cee33..87530ae 100644
--- a/zircon/system/dev/display/display/test/fidl_client.h
+++ b/zircon/system/dev/display/display/test/fidl_client.h
@@ -7,11 +7,14 @@
 
 #include <fuchsia/hardware/display/llcpp/fidl.h>
 #include <lib/fidl/cpp/message.h>
+#include <lib/zircon-internal/thread_annotations.h>
 #include <zircon/pixelformat.h>
 #include <zircon/types.h>
 
 #include <memory>
 
+#include <fbl/mutex.h>
+
 #include "base.h"
 
 namespace display {
@@ -30,17 +33,33 @@
     fbl::String manufacturer_name_;
     fbl::String monitor_name_;
     fbl::String monitor_serial_;
+
+    ::llcpp::fuchsia::hardware::display::ImageConfig image_config_;
   };
 
   TestFidlClient() {}
+  ~TestFidlClient();
 
   bool CreateChannel(zx_handle_t provider, bool is_vc);
-  bool Bind();
+  // Enable vsync for a display and wait for events using |dispatcher|.
+  bool Bind(async_dispatcher_t* dispatcher) TA_EXCL(mtx());
+  uint64_t display_id() const;
 
   fbl::Vector<Display> displays_;
-  std::unique_ptr<::llcpp::fuchsia::hardware::display::Controller::SyncClient> dc_;
+  std::unique_ptr<::llcpp::fuchsia::hardware::display::Controller::SyncClient> dc_
+      TA_GUARDED(mtx());
   zx::handle device_handle_;
-  bool has_ownership_;
+  bool has_ownership_ = false;
+  size_t vsync_count_ TA_GUARDED(mtx()) = 0;
+
+  fbl::Mutex* mtx() const { return &mtx_; }
+
+ private:
+  mutable fbl::Mutex mtx_;
+  async_dispatcher_t* dispatcher_ = nullptr;
+  void OnEventMsgAsync(async_dispatcher_t* dispatcher, async::WaitBase* self, zx_status_t status,
+                       const zx_packet_signal_t* signal) TA_EXCL(mtx());
+  async::WaitMethod<TestFidlClient, &TestFidlClient::OnEventMsgAsync> wait_events_{this};
 };
 
 }  // namespace display
diff --git a/zircon/system/dev/display/display/test/integration-test.cc b/zircon/system/dev/display/display/test/integration-test.cc
index e73700e..4380559c5 100644
--- a/zircon/system/dev/display/display/test/integration-test.cc
+++ b/zircon/system/dev/display/display/test/integration-test.cc
@@ -2,12 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <lib/async-loop/cpp/loop.h>
 #include <lib/async-testing/test_loop.h>
 #include <lib/async/cpp/task.h>
 #include <lib/async/default.h>
 #include <zircon/pixelformat.h>
 #include <zircon/types.h>
 
+#include <fbl/auto_lock.h>
 #include <zxtest/zxtest.h>
 
 #include "base.h"
@@ -18,22 +20,25 @@
 class IntegrationTest : public TestBase {};
 
 TEST_F(IntegrationTest, ClientsCanBail) {
-  async::TestLoop loop;
+  async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
+  loop.StartThread("test_thread", nullptr);
   TestFidlClient client;
   ASSERT_TRUE(client.CreateChannel(ddk().FidlClient().get(), false));
-  ASSERT_TRUE(client.Bind());
+  ASSERT_TRUE(client.Bind(loop.dispatcher()));
   loop.RunUntilIdle();
 }
 
 TEST_F(IntegrationTest, MustUseUniqueEvenIDs) {
-  async::TestLoop loop;
+  async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
+  loop.StartThread("test_thread", nullptr);
   TestFidlClient client;
   ASSERT_TRUE(client.CreateChannel(ddk().FidlClient().get(), false));
-  ASSERT_TRUE(client.Bind());
+  ASSERT_TRUE(client.Bind(loop.dispatcher()));
   zx::event event_a, event_b, event_c;
   ASSERT_OK(zx::event::create(0, &event_a));
   ASSERT_OK(zx::event::create(0, &event_b));
   ASSERT_OK(zx::event::create(0, &event_c));
+  fbl::AutoLock lock(client.mtx());
   EXPECT_OK(client.dc_->ImportEvent(std::move(event_a), 123).status());
   // ImportEvent is one way. Expect the next call to fail.
   EXPECT_OK(client.dc_->ImportEvent(std::move(event_b), 123).status());