[ddk] Add support to call compatibility tests

This change adds fidl support to run compatibility tests from isolated
devmgr. This change adds test drivers and runs the compatibility tests
on the test driver, and in-turn verifies the compatibility tests.

Test: runtests -t ddk-runcompatibilityhook-test

Change-Id: I5f97bff639fb19357363c3c9b97c4148902f20dc
diff --git a/zircon/system/core/devmgr/devcoordinator/device.cc b/zircon/system/core/devmgr/devcoordinator/device.cc
index e5ab5c2..c3cf8f3 100644
--- a/zircon/system/core/devmgr/devcoordinator/device.cc
+++ b/zircon/system/core/devmgr/devcoordinator/device.cc
@@ -7,6 +7,7 @@
 #include <ddk/driver.h>
 #include <fbl/auto_call.h>
 #include <lib/fidl/coding.h>
+#include <fuchsia/device/manager/c/fidl.h>
 #include "../shared/fidl_txn.h"
 #include "../shared/log.h"
 #include "coordinator.h"
@@ -290,6 +291,7 @@
                                            fidl_txn_t* txn);
 static zx_status_t fidl_RemoveDevice(void* ctx, fidl_txn_t* txn);
 static zx_status_t fidl_MakeVisible(void* ctx, fidl_txn_t* txn);
+static zx_status_t fidl_RunCompatibilityTests(void* ctx, int64_t hook_wait_time, fidl_txn_t* txn);
 static zx_status_t fidl_BindDevice(void* ctx, const char* driver_path_data, size_t driver_path_size,
                                    fidl_txn_t* txn);
 static zx_status_t fidl_GetTopologicalPath(void* ctx, fidl_txn_t* txn);
@@ -324,6 +326,7 @@
 
     .DmMexec = fidl_DmMexec,
     .DirectoryWatch = fidl_DirectoryWatch,
+    .RunCompatibilityTests = fidl_RunCompatibilityTests,
 };
 
 zx_status_t Device::HandleRead() {
@@ -504,6 +507,10 @@
         log(ERROR,
             "Driver Compatibility test failed for %s: "
             "Thread creation failed\n", GetTestDriverName());
+        if (test_reply_required_) {
+            dh_send_complete_compatibility_tests(this,
+                                    fuchsia_device_manager_CompatibilityTestStatus_ERR_INTERNAL);
+        }
         return ZX_ERR_NO_RESOURCES;
     }
     thrd_detach(t);
@@ -513,13 +520,22 @@
 int Device::RunCompatibilityTests() {
     const char* test_driver_name = GetTestDriverName();
     log(INFO, "%s: Running ddk compatibility test for driver %s \n", __func__, test_driver_name);
+    auto cleanup = fbl::MakeAutoCall([this]() {
+        if (test_reply_required_) {
+            dh_send_complete_compatibility_tests(this, test_status_);
+        }
+        test_event().reset();
+        set_test_state(Device::TestStateMachine::kTestDone);
+        set_test_reply_required(false);
+    });
     // Device should be bound for test to work
     if (!(flags & DEV_CTX_BOUND) || children().is_empty()) {
         log(ERROR,
             "devcoordinator: Driver Compatibility test failed for %s: "
             "Parent Device not bound\n",
             test_driver_name);
-        return ZX_ERR_INTERNAL;
+        test_status_ = fuchsia_device_manager_CompatibilityTestStatus_ERR_BIND_NO_DDKADD;
+        return -1;
     }
     zx_status_t status = zx::event::create(0, &test_event());
     if (status != ZX_OK) {
@@ -527,14 +543,10 @@
             "devcoordinator: Driver Compatibility test failed for %s: "
             "Event creation failed : %d\n",
             test_driver_name, status);
-        return ZX_ERR_NO_RESOURCES;
+        test_status_ = fuchsia_device_manager_CompatibilityTestStatus_ERR_INTERNAL;
+        return -1;
     }
 
-    auto cleanup = fbl::MakeAutoCall([this]() {
-        test_event().reset();
-        set_test_state(Device::TestStateMachine::kTestDone);
-    });
-
     // Issue unbind on all its children.
     for (auto itr = children().begin(); itr != children().end();) {
         auto& child = *itr;
@@ -548,7 +560,8 @@
                 "devcoordinator: Driver Compatibility test failed for %s: "
                 "Sending unbind to %s failed\n",
                 test_driver_name, child.name().data());
-            return ZX_ERR_INTERNAL;
+            test_status_ = fuchsia_device_manager_CompatibilityTestStatus_ERR_INTERNAL;
+            return -1;
         }
     }
 
@@ -564,13 +577,15 @@
                 "Timed out waiting for device to be removed. Check if device_remove was "
                 "called in the unbind routine of the driver: %d\n",
                 test_driver_name, status);
+            test_status_ = fuchsia_device_manager_CompatibilityTestStatus_ERR_UNBIND_TIMEOUT;
         } else {
             log(ERROR,
                 "devcoordinator: Driver Compatibility test failed for %s: "
                 "Error waiting for device to be removed.\n",
                 test_driver_name);
+            test_status_ = fuchsia_device_manager_CompatibilityTestStatus_ERR_INTERNAL;
         }
-        return ZX_ERR_BAD_STATE;
+        return -1;
     }
     this->set_test_state(Device::TestStateMachine::kTestBindSent);
     this->coordinator->HandleNewDevice(fbl::WrapRefPtr(this));
@@ -586,27 +601,29 @@
                  "Timed out waiting for driver to be bound. Check if Bind routine "
                  "of the driver is doing blocking I/O: %d\n",
                  test_driver_name, status);
+             test_status_ = fuchsia_device_manager_CompatibilityTestStatus_ERR_BIND_TIMEOUT;
          } else {
              log(ERROR,
                     "devcoordinator: Driver Compatibility test failed for %s: "
                     "Error waiting for driver to be bound: %d\n",
                     test_driver_name, status);
+             test_status_ = fuchsia_device_manager_CompatibilityTestStatus_ERR_INTERNAL;
          }
-         return ZX_ERR_BAD_STATE;
+         return -1;
     }
     this->set_test_state(Device::TestStateMachine::kTestBindDone);
     if (this->children().is_empty()) {
-       log(ERROR,
+        log(ERROR,
            "devcoordinator: Driver Compatibility test failed for %s: "
            "Driver Bind routine did not add a child. Check if Bind routine "
            "Called DdkAdd() at the end.\n", test_driver_name);
-       return -1;
+        test_status_ = fuchsia_device_manager_CompatibilityTestStatus_ERR_BIND_NO_DDKADD;
+        return -1;
     }
+    log(ERROR, "devcoordinator: Driver Compatibility test succeeded for %s\n", test_driver_name);
     // TODO(ravoorir): Test Suspend and Resume hooks
-    log(ERROR, "%s: Driver Compatibility test %s for %s\n", __func__,
-        this->test_state() == Device::TestStateMachine::kTestBindDone ? "Succeeded" : "Failed",
-        test_driver_name);
-    return ZX_OK;
+    test_status_ = fuchsia_device_manager_CompatibilityTestStatus_OK;
+    return 0;
 }
 
 // Handlers for the messages from devices
@@ -701,6 +718,20 @@
     return fuchsia_device_manager_CoordinatorBindDevice_reply(txn, status);
 }
 
+static zx_status_t fidl_RunCompatibilityTests(void* ctx, int64_t hook_wait_time, fidl_txn_t* txn) {
+    auto dev = fbl::WrapRefPtr(static_cast<Device*>(ctx));
+    fbl::RefPtr<Device>& real_parent = dev;
+    zx_status_t status = ZX_OK;
+    if (dev->flags & DEV_CTX_PROXY) {
+        real_parent = dev->parent();
+    }
+    zx::duration test_time = zx::nsec(hook_wait_time);
+    real_parent->set_test_time(test_time);
+    real_parent->set_test_reply_required(true);
+    status = real_parent->DriverCompatibiltyTest();
+    return fuchsia_device_manager_CoordinatorRunCompatibilityTests_reply(txn, status);
+}
+
 static zx_status_t fidl_GetTopologicalPath(void* ctx, fidl_txn_t* txn) {
     char path[fuchsia_device_manager_DEVICE_PATH_MAX + 1];
 
diff --git a/zircon/system/core/devmgr/devcoordinator/device.h b/zircon/system/core/devmgr/devcoordinator/device.h
index c6ab921..7f10593 100644
--- a/zircon/system/core/devmgr/devcoordinator/device.h
+++ b/zircon/system/core/devmgr/devcoordinator/device.h
@@ -369,6 +369,9 @@
     void set_test_time(zx::duration& test_time) {
         test_time_ = test_time;
     }
+    void set_test_reply_required(bool required) {
+        test_reply_required_ = required;
+    }
     zx::duration& test_time() {
         return test_time_;
     }
@@ -440,10 +443,14 @@
     // For attaching as an open connection to the proxy device,
     // or once the device becomes visible.
     zx::channel client_remote_;
+
+    // For compatibility tests.
     fbl::Mutex test_state_lock_;
     TestStateMachine test_state_ __TA_GUARDED(test_state_lock_) = TestStateMachine::kTestNotStarted;
     zx::event test_event_;
     zx::duration test_time_;
+    fuchsia_device_manager_CompatibilityTestStatus test_status_;
+    bool test_reply_required_ = false;
 };
 
 } // namespace devmgr
diff --git a/zircon/system/core/devmgr/devcoordinator/fidl.cc b/zircon/system/core/devmgr/devcoordinator/fidl.cc
index 7d25af4..e4cd84f 100644
--- a/zircon/system/core/devmgr/devcoordinator/fidl.cc
+++ b/zircon/system/core/devmgr/devcoordinator/fidl.cc
@@ -154,6 +154,22 @@
     return msg.Write(dev->channel()->get(), 0);
 }
 
+zx_status_t dh_send_complete_compatibility_tests(const Device* dev, zx_status_t status) {
+    FIDL_ALIGNDECL char wr_bytes[
+            sizeof(fuchsia_device_manager_DeviceControllerCompleteCompatibilityTestsRequest)];
+    fidl::Builder builder(wr_bytes, sizeof(wr_bytes));
+
+    auto req = builder.New<fuchsia_device_manager_DeviceControllerCompleteCompatibilityTestsRequest>();
+    ZX_ASSERT(req != nullptr);
+    req->hdr.ordinal = fuchsia_device_manager_DeviceControllerCompleteCompatibilityTestsOrdinal;
+    // TODO(teisenbe): Allocate and track txids
+    req->hdr.txid = 1;
+    req->status = status;
+
+    fidl::Message msg(builder.Finalize(), fidl::HandlePart(nullptr, 0));
+    return msg.Write(dev->channel()->get(), 0);
+}
+
 zx_status_t dh_send_unbind(const Device* dev) {
     FIDL_ALIGNDECL char wr_bytes[sizeof(fuchsia_device_manager_DeviceControllerUnbindRequest)];
     fidl::Builder builder(wr_bytes, sizeof(wr_bytes));
diff --git a/zircon/system/core/devmgr/devcoordinator/fidl.h b/zircon/system/core/devmgr/devcoordinator/fidl.h
index cdf5997..78dab3d 100644
--- a/zircon/system/core/devmgr/devcoordinator/fidl.h
+++ b/zircon/system/core/devmgr/devcoordinator/fidl.h
@@ -23,6 +23,7 @@
 zx_status_t dh_send_connect_proxy(const Device* dev, zx::channel proxy);
 zx_status_t dh_send_suspend(const Device* dev, uint32_t flags);
 zx_status_t dh_send_unbind(const Device* dev);
+zx_status_t dh_send_complete_compatibility_tests(const Device* dev, zx_status_t test_status_);
 zx_status_t dh_send_create_composite_device(Devhost* dh, const Device* composite_dev,
                                             const CompositeDevice& composite,
                                             const uint64_t* component_local_ids, zx::channel rpc);
diff --git a/zircon/system/core/devmgr/devhost/api.cc b/zircon/system/core/devmgr/devhost/api.cc
index b60a604..1329f97 100644
--- a/zircon/system/core/devmgr/devhost/api.cc
+++ b/zircon/system/core/devmgr/devhost/api.cc
@@ -221,6 +221,12 @@
     return devhost_device_unbind(dev);
 }
 
+zx_status_t device_run_compatibility_tests(const fbl::RefPtr<zx_device_t>& dev,
+                                           int64_t hook_wait_time) {
+    ApiAutoLock lock;
+    return devhost_device_run_compatibility_tests(dev, hook_wait_time);
+}
+
 zx_status_t device_open(const fbl::RefPtr<zx_device_t>& dev, fbl::RefPtr<zx_device_t>* out,
                         uint32_t flags) {
     ApiAutoLock lock;
diff --git a/zircon/system/core/devmgr/devhost/devhost.cc b/zircon/system/core/devmgr/devhost/devhost.cc
index 6065e31..12c87d8 100644
--- a/zircon/system/core/devmgr/devhost/devhost.cc
+++ b/zircon/system/core/devmgr/devhost/devhost.cc
@@ -819,6 +819,23 @@
     return call_status;
 }
 
+zx_status_t devhost_device_run_compatibility_tests(const fbl::RefPtr<zx_device_t>& dev,
+                                                   int64_t hook_wait_time) {
+    const zx::channel& rpc = *dev->rpc;
+    if (!rpc.is_valid()) {
+        return ZX_ERR_IO_REFUSED;
+    }
+    log_rpc(dev, "run-compatibility-test");
+    zx_status_t call_status;
+    zx_status_t status = fuchsia_device_manager_CoordinatorRunCompatibilityTests(
+                          rpc.get(), hook_wait_time, &call_status);
+    log_rpc_result("run-compatibility-test", status, call_status);
+    if (status != ZX_OK) {
+        return status;
+    }
+    return call_status;
+}
+
 zx_status_t devhost_load_firmware(const fbl::RefPtr<zx_device_t>& dev, const char* path,
                                   zx_handle_t* vmo, size_t* size) {
     if ((vmo == nullptr) || (size == nullptr)) {
diff --git a/zircon/system/core/devmgr/devhost/devhost.h b/zircon/system/core/devmgr/devhost/devhost.h
index a153edd..51dc0f1 100644
--- a/zircon/system/core/devmgr/devhost/devhost.h
+++ b/zircon/system/core/devmgr/devhost/devhost.h
@@ -136,6 +136,8 @@
                                 const char* drv_libname) REQ_DM_LOCK;
 zx_status_t devhost_device_rebind(const fbl::RefPtr<zx_device_t>& dev) REQ_DM_LOCK;
 zx_status_t devhost_device_unbind(const fbl::RefPtr<zx_device_t>& dev) REQ_DM_LOCK;
+zx_status_t devhost_device_run_compatibility_tests(const fbl::RefPtr<zx_device_t>& dev,
+                                                   int64_t hook_wait_time) REQ_DM_LOCK;
 zx_status_t devhost_device_create(zx_driver_t* drv, const char* name, void* ctx,
                                   const zx_protocol_device_t* ops,
                                   fbl::RefPtr<zx_device_t>* out) REQ_DM_LOCK;
diff --git a/zircon/system/core/devmgr/devhost/device-controller-connection.cc b/zircon/system/core/devmgr/devhost/device-controller-connection.cc
index 052b599..2056c4f 100644
--- a/zircon/system/core/devmgr/devhost/device-controller-connection.cc
+++ b/zircon/system/core/devmgr/devhost/device-controller-connection.cc
@@ -82,6 +82,18 @@
     return BindReply(dev, txn, ZX_ERR_NOT_SUPPORTED);
 }
 
+zx_status_t fidl_CompleteCompatibilityTests(void* raw_ctx,
+                                fuchsia_device_manager_CompatibilityTestStatus test_status) {
+    auto ctx = static_cast<DevhostRpcReadContext*>(raw_ctx);
+    const auto& dev = ctx->conn->dev();
+    fs::FidlConnection conn(fidl_txn_t{}, ZX_HANDLE_INVALID, 0);
+    if (dev->PopTestCompatibilityConn(&conn)) {
+        fuchsia_device_ControllerRunCompatibilityTests_reply(conn.Txn(), test_status);
+    }
+
+    return ZX_OK;
+}
+
 zx_status_t fidl_ConnectProxy(void* raw_ctx, zx_handle_t raw_shadow) {
     auto ctx = static_cast<DevhostRpcReadContext*>(raw_ctx);
     zx::channel shadow(raw_shadow);
@@ -139,6 +151,7 @@
     .Unbind = fidl_Unbind,
     .RemoveDevice = fidl_RemoveDevice,
     .Suspend = fidl_Suspend,
+    .CompleteCompatibilityTests = fidl_CompleteCompatibilityTests,
 };
 
 const fuchsia_io_Directory_ops_t kDefaultDirectoryOps = []() {
diff --git a/zircon/system/core/devmgr/devhost/rpc-server.cc b/zircon/system/core/devmgr/devhost/rpc-server.cc
index 9ec4e6d..ce7cc7d 100644
--- a/zircon/system/core/devmgr/devhost/rpc-server.cc
+++ b/zircon/system/core/devmgr/devhost/rpc-server.cc
@@ -576,6 +576,13 @@
     return device_bind(conn->dev, drv_libname);
 }
 
+static zx_status_t fidl_DeviceControllerRunCompatibilityTests(void* ctx, int64_t hook_wait_time,
+                                                              fidl_txn_t* txn) {
+    auto conn = static_cast<DevfsConnection*>(ctx);
+    conn->dev->PushTestCompatibilityConn(fs::FidlConnection::CopyTxn(txn));
+    return device_run_compatibility_tests(conn->dev, hook_wait_time);
+}
+
 static zx_status_t fidl_DeviceControllerUnbind(void* ctx, fidl_txn_t* txn) {
     auto conn = static_cast<DevfsConnection*>(ctx);
     zx_status_t status = device_unbind(conn->dev);
@@ -672,6 +679,7 @@
     .SetDriverLogFlags = fidl_DeviceControllerSetDriverLogFlags,
     .DebugSuspend = fidl_DeviceControllerDebugSuspend,
     .DebugResume = fidl_DeviceControllerDebugResume,
+    .RunCompatibilityTests = fidl_DeviceControllerRunCompatibilityTests,
 };
 
 zx_status_t devhost_fidl_handler(fidl_msg_t* msg, fidl_txn_t* txn, void* cookie) {
diff --git a/zircon/system/core/devmgr/devhost/zx-device.cc b/zircon/system/core/devmgr/devhost/zx-device.cc
index e439120..27e09bc 100644
--- a/zircon/system/core/devmgr/devhost/zx-device.cc
+++ b/zircon/system/core/devmgr/devhost/zx-device.cc
@@ -30,6 +30,21 @@
     return true;
 }
 
+void zx_device::PushTestCompatibilityConn(const fs::FidlConnection& conn) {
+    fbl::AutoLock<fbl::Mutex> lock(&test_compatibility_conn_lock_);
+    test_compatibility_conn_.push_back(conn);
+}
+
+bool zx_device::PopTestCompatibilityConn(fs::FidlConnection* conn) {
+    fbl::AutoLock<fbl::Mutex> lock(&test_compatibility_conn_lock_);
+    if (test_compatibility_conn_.is_empty()) {
+        return false;
+    }
+    *conn = test_compatibility_conn_[0];
+    test_compatibility_conn_.erase(0);
+    return true;
+}
+
 // We must disable thread-safety analysis due to not being able to statically
 // guarantee the lock holding invariant.  Instead, we acquire the lock if
 // it's not already being held by the current thread.
diff --git a/zircon/system/core/devmgr/devhost/zx-device.h b/zircon/system/core/devmgr/devhost/zx-device.h
index 0a7e4c39..4b8449b 100644
--- a/zircon/system/core/devmgr/devhost/zx-device.h
+++ b/zircon/system/core/devmgr/devhost/zx-device.h
@@ -86,6 +86,9 @@
     void PushBindConn(const fs::FidlConnection& conn);
     bool PopBindConn(fs::FidlConnection* conn);
 
+    void PushTestCompatibilityConn(const fs::FidlConnection& conn);
+    bool PopTestCompatibilityConn(fs::FidlConnection* conn);
+
     // Check if this devhost has a device with the given ID, and if so returns a
     // reference to it.
     static fbl::RefPtr<zx_device> GetDeviceFromLocalId(uint64_t local_id);
@@ -197,6 +200,11 @@
     // The connection associated with a fuchsia.device.Controller/Bind.
     fbl::Mutex bind_conn_lock_;
     fbl::Vector<fs::FidlConnection> bind_conn_ TA_GUARDED(bind_conn_lock_);
+
+    // The connection associated with fuchsia.device.Controller/RunCompatibilityTests
+    fbl::Mutex test_compatibility_conn_lock_;
+    fbl::Vector<fs::FidlConnection> test_compatibility_conn_
+                                          TA_GUARDED(test_compatibility_conn_lock_);
 };
 
 // zx_device_t objects must be created or initialized by the driver manager's
@@ -222,6 +230,8 @@
 
 zx_status_t device_bind(const fbl::RefPtr<zx_device_t>& dev, const char* drv_libname);
 zx_status_t device_unbind(const fbl::RefPtr<zx_device_t>& dev);
+zx_status_t device_run_compatibility_tests(const fbl::RefPtr<zx_device_t>& dev,
+                                           int64_t hook_wait_time);
 zx_status_t device_open(const fbl::RefPtr<zx_device_t>& dev, fbl::RefPtr<zx_device_t>* out,
                         uint32_t flags);
 // Note that device_close() is intended to consume a reference (logically, the
diff --git a/zircon/system/dev/BUILD.gn b/zircon/system/dev/BUILD.gn
index 4dda5f8..9f3bf4b 100644
--- a/zircon/system/dev/BUILD.gn
+++ b/zircon/system/dev/BUILD.gn
@@ -39,6 +39,8 @@
     "tee",
 
     # test/ allows drivers to be used by IsolateDevmgr
+    "test/ddk-runcompatibilityhook:ddk-runcompatibilityhook-test",
+    "test/ddk-runcompatibilityhook:ddk-runcompatibilityhook-test-child",
     "test/fidl-llcpp-driver",
     "test/isolateddevmgr:isolateddevmgr-test",
     "test/mock-device",
diff --git a/zircon/system/dev/test/BUILD.gn b/zircon/system/dev/test/BUILD.gn
index 6ad443d..a3aefa9 100644
--- a/zircon/system/dev/test/BUILD.gn
+++ b/zircon/system/dev/test/BUILD.gn
@@ -6,6 +6,7 @@
   testonly = true
   deps = [
     "ddk-fidl-test",
+    "ddk-runcompatibilityhook",
     "ddk-test",
     "isolateddevmgr",
     "mock-device",
diff --git a/zircon/system/dev/test/ddk-runcompatibilityhook/BUILD.gn b/zircon/system/dev/test/ddk-runcompatibilityhook/BUILD.gn
new file mode 100644
index 0000000..ec4d3d3
--- /dev/null
+++ b/zircon/system/dev/test/ddk-runcompatibilityhook/BUILD.gn
@@ -0,0 +1,53 @@
+# Copyright 2019 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+driver("ddk-runcompatibilityhook-test") {
+  sources = [
+    "test-driver.cc",
+  ]
+  deps = [
+    "$zx/system/ulib/ddk",
+    "$zx/system/ulib/ddktl",
+    "$zx/system/ulib/fdio",
+    "$zx/system/ulib/zircon",
+  ]
+
+  # TODO(ZX-2863): This driver violates the allowed shlib deps policy.
+  # Tests fail when using #"$zx/system/ulib/fdio:static",
+  deprecated_inhibit_driver_shlib_allowlist = true
+}
+
+driver("ddk-runcompatibilityhook-test-child") {
+  sources = [
+    "test-driver-child.cc",
+  ]
+  deps = [
+    "$zx/system/ulib/ddk",
+    "$zx/system/ulib/ddktl",
+    "$zx/system/ulib/fdio",
+    "$zx/system/ulib/zircon",
+  ]
+
+  # TODO(ZX-2863): This driver violates the allowed shlib deps policy.
+  deprecated_inhibit_driver_shlib_allowlist = true
+}
+
+test("ddk-runcompatibilityhook") {
+  sources = [
+    "test.cc",
+  ]
+  deps = [
+    "$zx/system/fidl/fuchsia-device:c",
+    "$zx/system/fidl/fuchsia-device-manager:c",
+    "$zx/system/ulib/ddk",
+    "$zx/system/ulib/devmgr-integration-test",
+    "$zx/system/ulib/devmgr-launcher",
+    "$zx/system/ulib/driver-integration-test",
+    "$zx/system/ulib/fbl",
+    "$zx/system/ulib/fdio",
+    "$zx/system/ulib/zircon",
+    "$zx/system/ulib/zx",
+    "$zx/system/ulib/zxtest",
+  ]
+}
diff --git a/zircon/system/dev/test/ddk-runcompatibilityhook/test-driver-child.cc b/zircon/system/dev/test/ddk-runcompatibilityhook/test-driver-child.cc
new file mode 100644
index 0000000..7e47759
--- /dev/null
+++ b/zircon/system/dev/test/ddk-runcompatibilityhook/test-driver-child.cc
@@ -0,0 +1,74 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <ddk/binding.h>
+#include <ddk/debug.h>
+#include <ddk/device.h>
+#include <ddk/driver.h>
+#include <ddk/metadata.h>
+#include <ddk/platform-defs.h>
+#include <ddktl/device.h>
+#include <fbl/alloc_checker.h>
+
+#include "test-metadata.h"
+
+class TestCompatibilityHookDriverChild;
+using DeviceType = ddk::Device<TestCompatibilityHookDriverChild, ddk::Unbindable>;
+class TestCompatibilityHookDriverChild : public DeviceType {
+public:
+    TestCompatibilityHookDriverChild(zx_device_t* parent)
+        : DeviceType(parent) {}
+    static zx_status_t Create(void* ctx, zx_device_t* device);
+    zx_status_t Bind();
+    void DdkUnbind() {
+        if (test_metadata_.remove_in_unbind) {
+            DdkRemove();
+        }
+    }
+    void DdkRelease() {
+        delete this;
+    }
+    struct compatibility_test_metadata test_metadata_ = {};
+};
+
+zx_status_t TestCompatibilityHookDriverChild::Bind() {
+    size_t actual;
+    auto status = DdkGetMetadata(DEVICE_METADATA_PRIVATE, &test_metadata_, sizeof(test_metadata_),
+                                 &actual);
+    if (status != ZX_OK || actual != sizeof(test_metadata_)) {
+        zxlogf(ERROR, "test_compat_hook_child_get_metadata not succesful\n");
+        return ZX_ERR_INTERNAL;
+    }
+    if (test_metadata_.add_in_bind) {
+        return DdkAdd("compatibility-test-child");
+    }
+    return ZX_OK;
+}
+
+zx_status_t TestCompatibilityHookDriverChild::Create(void* ctx, zx_device_t* device) {
+    fbl::AllocChecker ac;
+    auto dev = fbl::make_unique_checked<TestCompatibilityHookDriverChild>(&ac, device);
+    if (!ac.check()) {
+        return ZX_ERR_NO_MEMORY;
+    }
+    auto status = dev->Bind();
+    if (status == ZX_OK) {
+        // devmgr is now in charge of the memory for dev
+        __UNUSED auto ptr = dev.release();
+    }
+    return status;
+}
+
+static zx_driver_ops_t test_compatibility_hook_child_driver_ops = []() -> zx_driver_ops_t {
+    zx_driver_ops_t ops;
+    ops.version = DRIVER_OPS_VERSION;
+    ops.bind = TestCompatibilityHookDriverChild::Create;
+    return ops;
+}();
+
+// clang-format off
+ZIRCON_DRIVER_BEGIN(TestCompatibilityHookChild, test_compatibility_hook_child_driver_ops, "zircon", "0.1", 1)
+    BI_MATCH_IF(EQ, BIND_PROTOCOL, ZX_PROTOCOL_TEST_CHILD),
+ZIRCON_DRIVER_END(TestCompatibilityHookChild)
+// clang-format on
diff --git a/zircon/system/dev/test/ddk-runcompatibilityhook/test-driver.cc b/zircon/system/dev/test/ddk-runcompatibilityhook/test-driver.cc
new file mode 100644
index 0000000..8ba270c
--- /dev/null
+++ b/zircon/system/dev/test/ddk-runcompatibilityhook/test-driver.cc
@@ -0,0 +1,84 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <ddk/binding.h>
+#include <ddk/debug.h>
+#include <ddk/device.h>
+#include <ddk/driver.h>
+#include <ddk/metadata.h>
+#include <ddk/platform-defs.h>
+#include <ddktl/device.h>
+#include <ddktl/protocol/empty-protocol.h>
+#include <fbl/alloc_checker.h>
+
+#include "test-metadata.h"
+
+class TestCompatibilityHookDriver;
+using DeviceType = ddk::Device<TestCompatibilityHookDriver, ddk::Unbindable>;
+class TestCompatibilityHookDriver : public DeviceType,
+                                    public ddk::EmptyProtocol<ZX_PROTOCOL_TEST_CHILD> {
+public:
+    TestCompatibilityHookDriver(zx_device_t* parent)
+        : DeviceType(parent) {}
+    zx_status_t Bind();
+    void DdkUnbind() {
+        DdkRemove();
+    }
+    void DdkRelease() {
+        delete this;
+    }
+private:
+    struct compatibility_test_metadata metadata_;
+};
+
+zx_status_t TestCompatibilityHookDriver::Bind() {
+    size_t size;
+    zx_status_t status = DdkGetMetadataSize(DEVICE_METADATA_TEST, &size);
+    if (status != ZX_OK) {
+        return status;
+    }
+
+    if (size != sizeof(struct compatibility_test_metadata)) {
+        printf("Did not get the metadata correctly. size is %lu\n", size);
+        return ZX_ERR_INTERNAL;
+    }
+
+    status = DdkGetMetadata(DEVICE_METADATA_TEST, &metadata_, size, &size);
+    if (status != ZX_OK) {
+        return status;
+    }
+
+    DdkAdd("compatibility-test", DEVICE_ADD_INVISIBLE);
+    status = DdkAddMetadata(DEVICE_METADATA_PRIVATE, &metadata_, size);
+    DdkMakeVisible();
+    return status;
+}
+
+zx_status_t test_compatibility_hook_bind(void* ctx, zx_device_t* device) {
+    fbl::AllocChecker ac;
+    auto dev = fbl::make_unique_checked<TestCompatibilityHookDriver>(&ac, device);
+    if (!ac.check()) {
+        return ZX_ERR_NO_MEMORY;
+    }
+    auto status = dev->Bind();
+    if (status == ZX_OK) {
+        // devmgr is now in charge of the memory for dev
+        __UNUSED auto ptr = dev.release();
+    }
+    return status;
+}
+
+static zx_driver_ops_t test_compatibility_hook_driver_ops = []() -> zx_driver_ops_t {
+    zx_driver_ops_t ops;
+    ops.version = DRIVER_OPS_VERSION;
+    ops.bind = test_compatibility_hook_bind;
+    return ops;
+}();
+
+// clang-format off
+ZIRCON_DRIVER_BEGIN(TestCompatibilityHook, test_compatibility_hook_driver_ops, "zircon", "0.1", 2)
+    BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_TEST),
+    BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_PID, PDEV_PID_COMPATIBILITY_TEST),
+ZIRCON_DRIVER_END(TestCompatibilityHook)
+// clang-format on
diff --git a/zircon/system/dev/test/ddk-runcompatibilityhook/test-metadata.h b/zircon/system/dev/test/ddk-runcompatibilityhook/test-metadata.h
new file mode 100644
index 0000000..6fa941f
--- /dev/null
+++ b/zircon/system/dev/test/ddk-runcompatibilityhook/test-metadata.h
@@ -0,0 +1,11 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#pragma once
+
+struct compatibility_test_metadata {
+    bool add_in_bind;
+    bool remove_in_unbind;
+    bool remove_twice_in_unbind;
+    bool remove_in_suspend;
+};
diff --git a/zircon/system/dev/test/ddk-runcompatibilityhook/test.cc b/zircon/system/dev/test/ddk-runcompatibilityhook/test.cc
new file mode 100644
index 0000000..a28b4c0
--- /dev/null
+++ b/zircon/system/dev/test/ddk-runcompatibilityhook/test.cc
@@ -0,0 +1,143 @@
+#include <ddk/platform-defs.h>
+#include <fuchsia/device/c/fidl.h>
+#include <fuchsia/device/manager/c/fidl.h>
+#include <lib/driver-integration-test/fixture.h>
+#include <lib/fdio/fd.h>
+#include <lib/fdio/fdio.h>
+#include <lib/fdio/namespace.h>
+#include <lib/fdio/spawn.h>
+#include <lib/fdio/unsafe.h>
+#include <lib/fdio/watcher.h>
+#include <zircon/processargs.h>
+#include <zircon/syscalls.h>
+#include <zxtest/zxtest.h>
+
+#include "test-metadata.h"
+
+using driver_integration_test::IsolatedDevmgr;
+
+
+TEST(DeviceControllerIntegrationTest, RunCompatibilityHookSuccess) {
+    IsolatedDevmgr devmgr;
+    IsolatedDevmgr::Args args;
+    args.load_drivers.push_back("/boot/driver/ddk-runcompatibilityhook-test.so");
+    args.load_drivers.push_back("/boot/driver/ddk-runcompatibilityhook-test-child.so");
+
+    board_test::DeviceEntry dev = {};
+    struct compatibility_test_metadata test_metadata = {
+        .add_in_bind = true,
+        .remove_in_unbind = true,
+        .remove_twice_in_unbind = false,
+        .remove_in_suspend = false,
+    };
+    dev.metadata = reinterpret_cast<const uint8_t *>(&test_metadata);
+    dev.metadata_size = sizeof(test_metadata);
+    dev.vid = PDEV_VID_TEST;
+    dev.pid = PDEV_PID_COMPATIBILITY_TEST;
+    dev.did = 0;
+    args.device_list.push_back(dev);
+
+    zx_status_t status = IsolatedDevmgr::Create(&args, &devmgr);
+    ASSERT_OK(status);
+    fbl::unique_fd parent_fd, child_fd;
+    devmgr_integration_test::RecursiveWaitForFile(devmgr.devfs_root(),
+            "sys/platform/11:0a:0/compatibility-test", &parent_fd);
+    ASSERT_GT(parent_fd.get(), 0);
+    devmgr_integration_test::RecursiveWaitForFile(devmgr.devfs_root(),
+            "sys/platform/11:0a:0/compatibility-test/compatibility-test-child", &child_fd);
+    ASSERT_GT(child_fd.get(), 0);
+
+    zx::channel parent_device_handle;
+    ASSERT_EQ(ZX_OK, fdio_get_service_handle(parent_fd.release(),
+               parent_device_handle.reset_and_get_address()));
+    ASSERT_TRUE((parent_device_handle.get() != ZX_HANDLE_INVALID), "");
+
+    uint32_t call_status;
+    status = fuchsia_device_ControllerRunCompatibilityTests(parent_device_handle.get(),
+                                                            zx::duration(zx::msec(2000)).get(),
+                                                            &call_status);
+    ASSERT_OK(status);
+    ASSERT_EQ(call_status, fuchsia_device_manager_CompatibilityTestStatus_OK);
+}
+
+TEST(DeviceControllerIntegrationTest, RunCompatibilityHookMissingAddInBind) {
+    IsolatedDevmgr devmgr;
+    IsolatedDevmgr::Args args;
+
+    args.load_drivers.push_back("/boot/driver/ddk-runcompatibilityhook-test.so");
+    args.load_drivers.push_back("/boot/driver/ddk-runcompatibilityhook-test-child.so");
+
+    board_test::DeviceEntry dev = {};
+    struct compatibility_test_metadata test_metadata = {
+        .add_in_bind = false,
+        .remove_in_unbind = true,
+        .remove_twice_in_unbind = false,
+        .remove_in_suspend = false,
+    };
+    dev.metadata = reinterpret_cast<const uint8_t *>(&test_metadata);
+    dev.metadata_size = sizeof(test_metadata);
+    dev.vid = PDEV_VID_TEST;
+    dev.pid = PDEV_PID_COMPATIBILITY_TEST;
+    dev.did = 0;
+    args.device_list.push_back(dev);
+
+    zx_status_t status = IsolatedDevmgr::Create(&args, &devmgr);
+    ASSERT_OK(status);
+    fbl::unique_fd parent_fd, child_fd;
+    devmgr_integration_test::RecursiveWaitForFile(devmgr.devfs_root(),
+            "sys/platform/11:0a:0/compatibility-test", &parent_fd);
+    ASSERT_GT(parent_fd.get(), 0);
+
+    zx::channel parent_device_handle;
+    ASSERT_EQ(ZX_OK, fdio_get_service_handle(parent_fd.release(),
+               parent_device_handle.reset_and_get_address()));
+    ASSERT_TRUE((parent_device_handle.get() != ZX_HANDLE_INVALID), "");
+
+    uint32_t call_status;
+    status = fuchsia_device_ControllerRunCompatibilityTests(parent_device_handle.get(),
+                                                            zx::duration(zx::msec(2000)).get(),
+                                                            &call_status);
+    ASSERT_OK(status);
+    ASSERT_EQ(call_status, fuchsia_device_manager_CompatibilityTestStatus_ERR_BIND_NO_DDKADD);
+}
+
+TEST(DeviceControllerIntegrationTest, RunCompatibilityHookMissingRemoveInUnbind) {
+    IsolatedDevmgr devmgr;
+    IsolatedDevmgr::Args args;
+
+    args.load_drivers.push_back("/boot/driver/ddk-runcompatibilityhook-test.so");
+    args.load_drivers.push_back("/boot/driver/ddk-runcompatibilityhook-test-child.so");
+
+    board_test::DeviceEntry dev = {};
+    struct compatibility_test_metadata test_metadata = {
+        .add_in_bind = true,
+        .remove_in_unbind = false,
+        .remove_twice_in_unbind = false,
+        .remove_in_suspend = false,
+    };
+    dev.metadata = reinterpret_cast<const uint8_t *>(&test_metadata);
+    dev.metadata_size = sizeof(test_metadata);
+    dev.vid = PDEV_VID_TEST;
+    dev.pid = PDEV_PID_COMPATIBILITY_TEST;
+    dev.did = 0;
+    args.device_list.push_back(dev);
+
+    zx_status_t status = IsolatedDevmgr::Create(&args, &devmgr);
+    ASSERT_OK(status);
+    fbl::unique_fd parent_fd, child_fd;
+    devmgr_integration_test::RecursiveWaitForFile(devmgr.devfs_root(),
+            "sys/platform/11:0a:0/compatibility-test", &parent_fd);
+    ASSERT_GT(parent_fd.get(), 0);
+
+    zx::channel parent_device_handle;
+    ASSERT_EQ(ZX_OK, fdio_get_service_handle(parent_fd.release(),
+               parent_device_handle.reset_and_get_address()));
+    ASSERT_TRUE((parent_device_handle.get() != ZX_HANDLE_INVALID), "");
+
+    uint32_t call_status;
+    status = fuchsia_device_ControllerRunCompatibilityTests(parent_device_handle.get(),
+                                                            zx::duration(zx::msec(2000)).get(),
+                                                            &call_status);
+    ASSERT_OK(status);
+    ASSERT_EQ(call_status, fuchsia_device_manager_CompatibilityTestStatus_ERR_UNBIND_TIMEOUT);
+}
diff --git a/zircon/system/dev/test/isolateddevmgr/test-driver.cc b/zircon/system/dev/test/isolateddevmgr/test-driver.cc
index d023c4a..f7b05ed 100644
--- a/zircon/system/dev/test/isolateddevmgr/test-driver.cc
+++ b/zircon/system/dev/test/isolateddevmgr/test-driver.cc
@@ -94,4 +94,4 @@
     BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_TEST_CHILD_1),
     BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_TEST_CHILD_2),
 ZIRCON_DRIVER_END(metadataTest)
-    // clang-format on
+// clang-format on
diff --git a/zircon/system/fidl/fuchsia-device-manager/coordinator.fidl b/zircon/system/fidl/fuchsia-device-manager/coordinator.fidl
index cf373cd..c48dcc0 100644
--- a/zircon/system/fidl/fuchsia-device-manager/coordinator.fidl
+++ b/zircon/system/fidl/fuchsia-device-manager/coordinator.fidl
@@ -64,6 +64,17 @@
     array<DeviceComponentPart>:DEVICE_COMPONENT_PARTS_MAX parts;
 };
 
+/// A enum of CompatibilityTestStatus
+enum CompatibilityTestStatus : uint32 {
+    OK = 1;
+    ERR_BIND_NO_DDKADD = 2;
+    ERR_BIND_TIMEOUT = 3;
+    ERR_UNBIND_NO_DDKREMOVE = 4;
+    ERR_UNBIND_TIMEOUT = 5;
+    ERR_SUSPEND_DDKREMOVE = 6;
+    ERR_INTERNAL = 7;
+};
+
 /// Protocol for controlling devices in a devhost process from the devcoordinator
 [Layout = "Simple"]
 protocol DeviceController {
@@ -86,6 +97,13 @@
 
     /// Ask devhost to suspend this device, using the target state indicated by |flags|.
     Suspend(uint32 flags) -> (zx.status status);
+
+    /// Inform devhost about the compatibility test status when compatibility tests
+    /// fail or complete successfully.
+    // TODO(ravoorir) : This should be an asynchronous call from devhost to
+    // devcoordinator as a reply to RunCompatibilityTests, when llcpp can support
+    // making an asynchronous fidl call.
+    CompleteCompatibilityTests(CompatibilityTestStatus status);
 };
 
 /// Protocol for controlling a devhost process from the devcoordinator
@@ -220,4 +238,11 @@
     /// See fuchsia.io.Directory for more information.
     DirectoryWatch(uint32 mask, uint32 options, handle<channel> watcher)
         -> (zx.status s);
+
+    /// Run Compatibility tests for the driver that binds to this device.
+    /// The hook_wait_time is the time that the driver expects to take for
+    /// each device hook in nanoseconds.
+    /// Returns whether the compatibility tests started, and does not convey
+    /// anything about the status of the test.
+    RunCompatibilityTests(int64 hook_wait_time) -> (zx.status status);
 };
diff --git a/zircon/system/fidl/fuchsia-device/controller.fidl b/zircon/system/fidl/fuchsia-device/controller.fidl
index d2ef6f7..c52fa31 100644
--- a/zircon/system/fidl/fuchsia-device/controller.fidl
+++ b/zircon/system/fidl/fuchsia-device/controller.fidl
@@ -62,4 +62,10 @@
     DebugSuspend() -> (zx.status status);
     /// Debug command: execute the device's resume hook
     DebugResume() -> (zx.status status);
+
+    /// RunCompatibilityTests: Runs compatibility tests for the driver that binds to this device.
+    /// The |hook_wait_time| is the time that the driver expects to take for each device hook in
+    /// nanoseconds.
+    /// Returns whether the driver passed the compatibility check.
+    RunCompatibilityTests(int64 hook_wait_time) -> (uint32 status);
 };
diff --git a/zircon/system/ulib/ddk/include/ddk/platform-defs.h b/zircon/system/ulib/ddk/include/ddk/platform-defs.h
index 32fe587..9b32569 100644
--- a/zircon/system/ulib/ddk/include/ddk/platform-defs.h
+++ b/zircon/system/ulib/ddk/include/ddk/platform-defs.h
@@ -195,6 +195,7 @@
 #define PDEV_PID_METADATA_TEST      7
 #define PDEV_PID_PCI_TEST           8
 #define PDEV_PID_DDKFIDL_TEST       9
+#define PDEV_PID_COMPATIBILITY_TEST 10
 
 #define PDEV_DID_TEST_PARENT        1
 #define PDEV_DID_TEST_CHILD_1       2
diff --git a/zircon/system/ulib/ddk/include/ddk/protodefs.h b/zircon/system/ulib/ddk/include/ddk/protodefs.h
index f4e467f..70c9427 100644
--- a/zircon/system/ulib/ddk/include/ddk/protodefs.h
+++ b/zircon/system/ulib/ddk/include/ddk/protodefs.h
@@ -87,6 +87,7 @@
 DDK_PROTOCOL_DEF(IHDA_DSP,       'piHD', "intel-hda-dsp", 0)
 DDK_PROTOCOL_DEF(AUDIO_CODEC,    'pAUC', "audio-codec", 0)
 DDK_PROTOCOL_DEF(TEST,           'pTST', "test", 0)
+DDK_PROTOCOL_DEF(TEST_CHILD,     'pTCC', "test-child", 0)
 DDK_PROTOCOL_DEF(TEST_PARENT,    'pTSP', "test-parent", PF_NOPUB)
 DDK_PROTOCOL_DEF(PBUS,           'pPBU', "platform-bus", 0)
 DDK_PROTOCOL_DEF(PDEV,           'pPDV', "platform-dev", 0)