[i2c-hid] Migrate to new DdkInit/DdkUnbind hooks.

The DdkInit() hook is run after automatically after DdkAdd() is called.
By moving the post-add initialization logic into this hook,
we can guarantee the device will be invisible and not able to
be unbound until InitTxn.Reply() is called.

If InitTxn.Reply() is called with a bad status,
the device manager will begin unbinding the device.

When removing a device, we call DdkAsyncRemove() (rather than
DdkRemoveDeprecated), which will begin unbinding the device.

---------------
DDK lifecycle:
---------------
1) DdkAdd()
2) DdkInit()
3) InitTxn.Reply() // Device becomes visible
4) DdkUnbind()     // started by InitTxn.Reply(!ZX_OK) / DdkAsyncRemove
5) UnbindTxn.Reply()
6) DdkRelease()

TEST= fx test i2c-hid-test

BUG=37831
BUG=43261

Change-Id: If8028536483599da18b21cdd4c46ba7b6d4e1073
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/397715
Reviewed-by: David Gilhooley <dgilhooley@google.com>
Testability-Review: David Gilhooley <dgilhooley@google.com>
Commit-Queue: Jocelyn Dang <jocelyndang@google.com>
diff --git a/src/ui/input/drivers/i2c-hid/i2c-hid-test.cc b/src/ui/input/drivers/i2c-hid/i2c-hid-test.cc
index 9c70170..d976509 100644
--- a/src/ui/input/drivers/i2c-hid/i2c-hid-test.cc
+++ b/src/ui/input/drivers/i2c-hid/i2c-hid-test.cc
@@ -224,7 +224,7 @@
   }
 
   void TearDown() override {
-    device_->DdkUnbindDeprecated();
+    device_->DdkAsyncRemove();
     EXPECT_TRUE(ddk_.Ok());
 
     // This should delete the object, which means this test should not leak.
@@ -241,7 +241,12 @@
   ddk::I2cChannel channel_;
 };
 
-TEST_F(I2cHidTest, HidTestBind) { ASSERT_OK(device_->Bind(channel_)); }
+TEST_F(I2cHidTest, HidTestBind) {
+  ASSERT_OK(device_->Bind(channel_));
+  ASSERT_OK(ddk_.WaitUntilInitComplete());
+  EXPECT_TRUE(ddk_.init_reply().has_value());
+  EXPECT_OK(ddk_.init_reply().value());
+}
 
 TEST_F(I2cHidTest, HidTestQuery) {
   ASSERT_OK(device_->Bind(channel_));
@@ -291,6 +296,8 @@
 
   EXPECT_OK(ddk_.WaitUntilRemove());
   EXPECT_TRUE(ddk_.Ok());
+  EXPECT_TRUE(ddk_.init_reply().has_value());
+  EXPECT_NOT_OK(ddk_.init_reply().value());
 
   device_->DdkRelease();
 }
diff --git a/src/ui/input/drivers/i2c-hid/i2c-hid.cc b/src/ui/input/drivers/i2c-hid/i2c-hid.cc
index 7f34f6c..57a7e99 100644
--- a/src/ui/input/drivers/i2c-hid/i2c-hid.cc
+++ b/src/ui/input/drivers/i2c-hid/i2c-hid.cc
@@ -411,7 +411,10 @@
   if (irq_.is_valid()) {
     irq_.destroy();
   }
-  thrd_join(worker_thread_, NULL);
+  if (worker_thread_started_) {
+    worker_thread_started_ = false;
+    thrd_join(worker_thread_, NULL);
+  }
 
   {
     fbl::AutoLock lock(&ifc_lock_);
@@ -419,9 +422,9 @@
   }
 }
 
-void I2cHidbus::DdkUnbindDeprecated() {
+void I2cHidbus::DdkUnbindNew(ddk::UnbindTxn txn) {
   Shutdown();
-  DdkRemoveDeprecated();
+  txn.Reply();
 }
 
 void I2cHidbus::DdkRelease() { delete this; }
@@ -481,8 +484,20 @@
     i2c_.GetInterrupt(0, &irq_);
   }
 
+  status = DdkAdd("i2c-hid");
+  if (status != ZX_OK) {
+    zxlogf(ERROR, "i2c-hid: could not add device: %d", status);
+    return status;
+  }
+  return ZX_OK;
+}
+
+void I2cHidbus::DdkInit(ddk::InitTxn txn) {
+  init_txn_ = std::move(txn);
+
   auto worker_thread = [](void* arg) -> int {
     auto dev = reinterpret_cast<I2cHidbus*>(arg);
+    dev->worker_thread_started_ = true;
     zx_status_t status = ZX_OK;
     // Retry the first transaction a few times; in some cases (e.g. on Slate) the device was powered
     // on explicitly during enumeration, and there is a warmup period after powering on the device
@@ -497,11 +512,14 @@
       zx::nanosleep(zx::deadline_after(zx::msec(100)));
       zxlogf(INFO, "i2c-hid: Retrying reading HID descriptor");
     }
+    ZX_ASSERT(dev->init_txn_);
+    // This will make the device visible and able to be unbound.
+    dev->init_txn_->Reply(status);
+    // No need to remove the device, as replying to init_txn_ with an error will schedule
+    // unbinding.
     if (status != ZX_OK) {
-      dev->DdkRemoveDeprecated();
       return thrd_error;
     }
-    dev->DdkMakeVisible();
 
     if (dev->irq_.is_valid()) {
       dev->WorkerThreadIrq();
@@ -509,27 +527,21 @@
       dev->WorkerThreadNoIrq();
     }
     // If |stop_worker_thread_| is not set, than we exited the worker thread because
-    // of an error and not a shutdown. Call DdkRemove directly.
+    // of an error and not a shutdown. Call DdkAsyncRemove directly. This is a valid
+    // call even if the device is currently unbinding.
     if (!dev->stop_worker_thread_) {
-      dev->DdkRemoveDeprecated();
+      dev->DdkAsyncRemove();
       return thrd_error;
     }
     return thrd_success;
   };
 
-  status = DdkAdd("i2c-hid", DEVICE_ADD_INVISIBLE);
-  if (status != ZX_OK) {
-    zxlogf(ERROR, "i2c-hid: could not add device: %d", status);
-    return status;
-  }
-
   int rc = thrd_create_with_name(&worker_thread_, worker_thread, this, "i2c-hid-worker-thread");
   if (rc != thrd_success) {
-    DdkRemoveDeprecated();
-    return ZX_ERR_INTERNAL;
+    return init_txn_->Reply(ZX_ERR_INTERNAL);
   }
-
-  return ZX_OK;
+  // If the worker thread was created successfully, it is in charge of replying to the init txn,
+  // which will make the device visible.
 }
 
 static zx_status_t i2c_hid_bind(void* ctx, zx_device_t* parent) {
diff --git a/src/ui/input/drivers/i2c-hid/i2c-hid.h b/src/ui/input/drivers/i2c-hid/i2c-hid.h
index 8466336..f167769 100644
--- a/src/ui/input/drivers/i2c-hid/i2c-hid.h
+++ b/src/ui/input/drivers/i2c-hid/i2c-hid.h
@@ -10,6 +10,7 @@
 #include <threads.h>
 
 #include <memory>
+#include <optional>
 
 #include <ddktl/device.h>
 #include <ddktl/protocol/hidbus.h>
@@ -45,7 +46,7 @@
 } __PACKED;
 
 class I2cHidbus;
-using DeviceType = ddk::Device<I2cHidbus, ddk::UnbindableDeprecated>;
+using DeviceType = ddk::Device<I2cHidbus, ddk::Initializable, ddk::UnbindableNew>;
 
 class I2cHidbus : public DeviceType, public ddk::HidbusProtocol<I2cHidbus, ddk::base_protocol> {
  public:
@@ -68,26 +69,30 @@
   zx_status_t HidbusGetProtocol(uint8_t* protocol) { return ZX_ERR_NOT_SUPPORTED; }
   zx_status_t HidbusSetProtocol(uint8_t protocol) { return ZX_OK; }
 
-  void DdkUnbindDeprecated();
+  void DdkInit(ddk::InitTxn txn);
+  void DdkUnbindNew(ddk::UnbindTxn txn);
   void DdkRelease();
   zx_status_t Bind(ddk::I2cChannel i2c);
 
   zx_status_t ReadI2cHidDesc(I2cHidDesc* hiddesc);
 
-  void Shutdown();
-
   // Must be called with i2c_lock held.
   void WaitForReadyLocked() __TA_REQUIRES(i2c_lock_);
 
   zx_status_t Reset(bool force) __TA_EXCLUDES(i2c_lock_);
 
  private:
+  void Shutdown();
+
   I2cHidDesc hiddesc_ = {};
 
   // Signaled when reset received.
   fbl::ConditionVariable i2c_reset_cnd_;
 
-  std::atomic<bool> stop_worker_thread_ = false;
+  std::optional<ddk::InitTxn> init_txn_;
+
+  std::atomic_bool worker_thread_started_ = false;
+  std::atomic_bool stop_worker_thread_ = false;
   thrd_t worker_thread_;
   zx::interrupt irq_;
   // The functions to be run in the worker thread. They are responsible for initializing the