[ddk] Start testing driver-ddk compliance

This change adds a way to test the ddk compliance of a driver. At the
moment, Only Bind and Unbind rules are tested but it will later be
expanded to test other hooks.

Test: runtests on qemu and vim2.

manual test with aml_sd_emmc driver.
fx set core.vim2 --with-base //garnet/packages/tests:zircon
--args=kernel_cmdline_args+=\[\"driver.\aml_sd_emmc.compatibility-tests-enable\"\]
Verified compatibility tests pass. lsblk; iochk on vim2 after
aml-sd-emmc-driver went through the tests.

runtests -t ddk-runcompatibilityhook-test with
https://fuchsia-review.googlesource.com/c/fuchsia/+/291488

Change-Id: Ifb0fcc8019eb2bd38d51eb702c00e3547ef482f5
diff --git a/zircon/docs/kernel_cmdline.md b/zircon/docs/kernel_cmdline.md
index ae3e058..2b4083b 100644
--- a/zircon/docs/kernel_cmdline.md
+++ b/zircon/docs/kernel_cmdline.md
@@ -73,6 +73,19 @@
 
 Turn on verbose logging.
 
+## driver.\<name>.compatibility-tests-enable
+
+If this option is set, devmgr will run compatibility tests for the driver.
+zircon\_driver\_info, and can be found as the first argument to the
+ZIRCON\_DRIVER\_BEGIN macro.
+
+## driver.\<name>.compatibility-tests-wait-time
+
+This timeout lets you configure the wait time in milliseconds for each of
+bind/unbind/suspend hooks to complete in compatibility tests.
+zircon\_driver\_info, and can be found as the first argument to the
+ZIRCON\_DRIVER\_BEGIN macro.
+
 ## driver.\<name>.disable
 
 Disables the driver with the given name. The driver name comes from the
diff --git a/zircon/system/core/devmgr/devcoordinator/coordinator.cc b/zircon/system/core/devmgr/devcoordinator/coordinator.cc
index a475a9c..e071f8a 100644
--- a/zircon/system/core/devmgr/devcoordinator/coordinator.cc
+++ b/zircon/system/core/devmgr/devcoordinator/coordinator.cc
@@ -15,6 +15,7 @@
 
 #include <ddk/driver.h>
 #include <driver-info/driver-info.h>
+#include <fbl/auto_call.h>
 #include <fbl/unique_ptr.h>
 #include <fuchsia/boot/c/fidl.h>
 #include <fuchsia/io/c/fidl.h>
@@ -127,8 +128,7 @@
     root_device_->flags = DEV_CTX_IMMORTAL | DEV_CTX_MUST_ISOLATE | DEV_CTX_MULTI_BIND;
 
     misc_device_ = fbl::MakeRefCounted<Device>(this, "misc", fbl::String(), "misc,", root_device_,
-                                               ZX_PROTOCOL_MISC_PARENT,
-                                               zx::channel());
+                                               ZX_PROTOCOL_MISC_PARENT, zx::channel());
     misc_device_->flags = DEV_CTX_IMMORTAL | DEV_CTX_MUST_ISOLATE | DEV_CTX_MULTI_BIND;
 
     sys_device_ = fbl::MakeRefCounted<Device>(this, "sys", sys_device_driver, "sys,", root_device_,
@@ -193,8 +193,8 @@
                                                    ZX_RIGHT_READ | ZX_RIGHT_EXECUTE | ZX_RIGHT_MAP,
                                                out_vmo);
         if (r != ZX_OK) {
-            log(ERROR, "devcoordinator: cannot duplicate cached dso for '%s' '%s'\n", drv->name.data(),
-                libname.data());
+            log(ERROR, "devcoordinator: cannot duplicate cached dso for '%s' '%s'\n",
+                drv->name.data(), libname.data());
         }
         return r;
     } else {
@@ -235,10 +235,9 @@
 
 void Coordinator::DumpDeviceProps(VmoWriter* vmo, const Device* dev) const {
     if (dev->host()) {
-        vmo->Printf("Name [%s]%s%s%s\n", dev->name().data(),
-                    dev->libname().empty() ? "" : " Driver [",
-                    dev->libname().empty() ? "" : dev->libname().data(),
-                    dev->libname().empty() ? "" : "]");
+        vmo->Printf(
+            "Name [%s]%s%s%s\n", dev->name().data(), dev->libname().empty() ? "" : " Driver [",
+            dev->libname().empty() ? "" : dev->libname().data(), dev->libname().empty() ? "" : "]");
         vmo->Printf("Flags   :%s%s%s%s%s%s\n", dev->flags & DEV_CTX_IMMORTAL ? " Immortal" : "",
                     dev->flags & DEV_CTX_MUST_ISOLATE ? " Isolate" : "",
                     dev->flags & DEV_CTX_MULTI_BIND ? " MultiBind" : "",
@@ -251,8 +250,8 @@
         char c = (char)((dev->protocol_id() >> 8) & 0xFF);
         char d = (char)(dev->protocol_id() & 0xFF);
         vmo->Printf("ProtoId : '%c%c%c%c' 0x%08x(%u)\n", isprint(a) ? a : '.', isprint(b) ? b : '.',
-                    isprint(c) ? c : '.', isprint(d) ? d : '.',
-                    dev->protocol_id(), dev->protocol_id());
+                    isprint(c) ? c : '.', isprint(d) ? d : '.', dev->protocol_id(),
+                    dev->protocol_id());
 
         const auto& props = dev->props();
         vmo->Printf("%zu Propert%s\n", props.size(), props.size() == 1 ? "y" : "ies");
@@ -341,7 +340,7 @@
         } else if (itr->composite() != nullptr) {
             strcpy(name_buf, "dev/");
             strncpy(name_buf + strlen("dev/"), itr->name().data(), fuchsia_io_MAX_FILENAME);
-            name_buf[sizeof(name_buf)-1] = 0;
+            name_buf[sizeof(name_buf) - 1] = 0;
             name = name_buf;
         } else {
             name = itr->name().data();
@@ -405,24 +404,23 @@
     constexpr size_t kMaxActions = 6;
     fdio_spawn_action_t actions[kMaxActions];
     size_t actions_count = 0;
-    actions[actions_count++] = fdio_spawn_action_t {
-        .action = FDIO_SPAWN_ACTION_SET_NAME,
-        .name = {.data = name}};
+    actions[actions_count++] =
+        fdio_spawn_action_t{.action = FDIO_SPAWN_ACTION_SET_NAME, .name = {.data = name}};
     // TODO: constrain to /svc/device
-    actions[actions_count++] = fdio_spawn_action_t {
+    actions[actions_count++] = fdio_spawn_action_t{
         .action = FDIO_SPAWN_ACTION_ADD_NS_ENTRY,
         .ns = {.prefix = "/svc", .handle = fs_clone("svc").release()},
     };
-    actions[actions_count++] = fdio_spawn_action_t {
+    actions[actions_count++] = fdio_spawn_action_t{
         .action = FDIO_SPAWN_ACTION_ADD_HANDLE,
         .h = {.id = PA_HND(PA_USER0, 0), .handle = hrpc},
     };
-    actions[actions_count++] = fdio_spawn_action_t {
+    actions[actions_count++] = fdio_spawn_action_t{
         .action = FDIO_SPAWN_ACTION_ADD_HANDLE,
         .h = {.id = PA_HND(PA_USER0, kIdHJobRoot), .handle = root_job_svc.release()},
     };
     if (resource.is_valid()) {
-        actions[actions_count++] = fdio_spawn_action_t {
+        actions[actions_count++] = fdio_spawn_action_t{
             .action = FDIO_SPAWN_ACTION_ADD_HANDLE,
             .h = {.id = PA_HND(PA_RESOURCE, 0), .handle = resource.release()},
         };
@@ -430,7 +428,7 @@
 
     uint32_t spawn_flags = FDIO_SPAWN_CLONE_ENVIRON;
     if (loader_connection.is_valid()) {
-        actions[actions_count++] = fdio_spawn_action_t {
+        actions[actions_count++] = fdio_spawn_action_t{
             .action = FDIO_SPAWN_ACTION_ADD_HANDLE,
             .h = {.id = PA_HND(PA_LDSVC_LOADER, 0), .handle = loader_connection.release()},
         };
@@ -482,9 +480,9 @@
     fbl::Vector<const char*> env;
     boot_args().Collect("driver.", &env);
     env.push_back(nullptr);
-    status = dc_launch_devhost(dh.get(), loader_service_, get_devhost_bin(config_.asan_drivers),
-                               name, env.get(), hrpc, root_resource(),
-                               zx::unowned_job(config_.devhost_job));
+    status =
+        dc_launch_devhost(dh.get(), loader_service_, get_devhost_bin(config_.asan_drivers), name,
+                          env.get(), hrpc, root_resource(), zx::unowned_job(config_.devhost_job));
     if (status != ZX_OK) {
         zx_handle_close(dh->hrpc());
         return status;
@@ -540,8 +538,8 @@
         return ZX_ERR_BAD_STATE;
     }
 
-    log(RPC_IN, "devcoordinator: rpc: add-device '%.*s' args='%.*s'\n", static_cast<int>(name.size()),
-        name.data(), static_cast<int>(args.size()), args.data());
+    log(RPC_IN, "devcoordinator: rpc: add-device '%.*s' args='%.*s'\n",
+        static_cast<int>(name.size()), name.data(), static_cast<int>(args.size()), args.data());
 
     fbl::Array<zx_device_prop_t> props(new zx_device_prop_t[props_count], props_count);
     if (!props) {
@@ -565,10 +563,9 @@
     }
 
     fbl::RefPtr<Device> dev;
-    zx_status_t status = Device::Create(this, parent, std::move(name_str),
-                                        std::move(driver_path_str), std::move(args_str),
-                                        protocol_id, std::move(props), std::move(rpc), invisible,
-                                        std::move(client_remote), &dev);
+    zx_status_t status = Device::Create(
+        this, parent, std::move(name_str), std::move(driver_path_str), std::move(args_str),
+        protocol_id, std::move(props), std::move(rpc), invisible, std::move(client_remote), &dev);
     if (status != ZX_OK) {
         return status;
     }
@@ -709,16 +706,29 @@
     // if we have a parent, disconnect and downref it
     fbl::RefPtr<Device> parent = dev->parent();
     if (parent != nullptr) {
+        Device* real_parent;
+        if (parent->flags & DEV_CTX_PROXY) {
+            real_parent = parent->parent().get();
+        } else {
+            real_parent = parent.get();
+        }
         dev->DetachFromParent();
         if (!(dev->flags & DEV_CTX_PROXY)) {
             if (parent->children().is_empty()) {
                 parent->flags &= (~DEV_CTX_BOUND);
+                if (real_parent->test_state() == Device::TestStateMachine::kTestUnbindSent) {
+                    real_parent->test_event().signal(0, TEST_REMOVE_DONE_SIGNAL);
+                    if (!(dev->flags & DEV_CTX_PROXY)) {
+                        // remove from list of all devices
+                        devices_.erase(*dev);
+                    }
+                    return ZX_OK;
+                }
 
                 // TODO: This code is to cause the bind process to
                 //      restart and get a new devhost to be launched
                 //      when a devhost dies.  It should probably be
                 //      more tied to devhost teardown than it is.
-
                 // IF we are the last child of our parent
                 // AND our parent is not itself dead
                 // AND our parent is a BUSDEV
@@ -753,10 +763,11 @@
     return ZX_OK;
 }
 
-zx_status_t Coordinator::AddCompositeDevice(
-        const fbl::RefPtr<Device>& dev, fbl::StringPiece name, const zx_device_prop_t* props_data,
-        size_t props_count, const fuchsia_device_manager_DeviceComponent* components,
-        size_t components_count, uint32_t coresident_device_index) {
+zx_status_t
+Coordinator::AddCompositeDevice(const fbl::RefPtr<Device>& dev, fbl::StringPiece name,
+                                const zx_device_prop_t* props_data, size_t props_count,
+                                const fuchsia_device_manager_DeviceComponent* components,
+                                size_t components_count, uint32_t coresident_device_index) {
     // Only the platform bus driver should be able to use this.  It is the
     // descendant of the sys device node.
     if (dev->parent() != sys_device_) {
@@ -764,9 +775,9 @@
     }
 
     std::unique_ptr<CompositeDevice> new_device;
-    zx_status_t status = CompositeDevice::Create(name, props_data, props_count, components,
-                                                 components_count, coresident_device_index,
-                                                 &new_device);
+    zx_status_t status =
+        CompositeDevice::Create(name, props_data, props_count, components, components_count,
+                                coresident_device_index, &new_device);
     if (status != ZX_OK) {
         return status;
     }
@@ -821,7 +832,8 @@
             zx::vmo nonexec_vmo;
             zx_status_t r = fdio_get_vmo_clone(fwfd, nonexec_vmo.reset_and_get_address());
             if (r == ZX_OK) {
-                r = zx_vmo_replace_as_executable(nonexec_vmo.release(), ZX_HANDLE_INVALID, vmo->reset_and_get_address());
+                r = zx_vmo_replace_as_executable(nonexec_vmo.release(), ZX_HANDLE_INVALID,
+                                                 vmo->reset_and_get_address());
             }
             close(fwfd);
             return r;
@@ -866,7 +878,7 @@
     // search components of composite devices
     if (test->composite()) {
         for (auto& component : test->composite()->bound_components()) {
-           auto dev = component.bound_device();
+            auto dev = component.bound_device();
             if (dev != nullptr) {
                 if (GetMetadataRecurse(dev, type, buffer, buflen, size) == ZX_OK) {
                     return ZX_OK;
@@ -1216,8 +1228,8 @@
 
     if (suspend_fallback() || suspend_debug()) {
         thrd_t t;
-        int ret = thrd_create_with_name(&t, suspend_timeout_thread, this,
-                                        "devcoord-suspend-timeout");
+        int ret =
+            thrd_create_with_name(&t, suspend_timeout_thread, this, "devcoord-suspend-timeout");
         if (ret != thrd_success) {
             log(ERROR, "devcoordinator: failed to create suspend timeout thread\n");
         } else {
@@ -1304,8 +1316,8 @@
     log(INFO, "devcoordinator: adding system driver '%s' '%s'\n", driver->name.data(),
         driver->libname.data());
     if (load_vmo(driver->libname.data(), &driver->dso_vmo)) {
-        log(ERROR, "devcoordinator: system driver '%s' '%s' could not cache DSO\n", driver->name.data(),
-            driver->libname.data());
+        log(ERROR, "devcoordinator: system driver '%s' '%s' could not cache DSO\n",
+            driver->name.data(), driver->libname.data());
     }
     if (version[0] == '*') {
         // de-prioritize drivers that are "fallback"
@@ -1324,8 +1336,6 @@
         return ZX_ERR_NEXT;
     }
 
-    log(SPEW, "devcoordinator: drv='%s' bindable to dev='%s'\n", drv->name.data(),
-        dev->name().data());
     zx_status_t status = attempt_bind(drv, dev);
     if (status != ZX_OK) {
         log(ERROR, "devcoordinator: failed to bind drv='%s' to dev='%s': %s\n", drv->name.data(),
@@ -1362,8 +1372,8 @@
     }
     printf("devcoordinator: driver '%s' added\n", drv->name.data());
     for (auto& dev : devices_) {
-        zx_status_t status = BindDriverToDevice(fbl::WrapRefPtr(&dev), drv, true /* autobind */,
-                                                attempt_bind);
+        zx_status_t status =
+            BindDriverToDevice(fbl::WrapRefPtr(&dev), drv, true /* autobind */, attempt_bind);
         if (status == ZX_ERR_NEXT) {
             continue;
         }
@@ -1533,32 +1543,33 @@
 
     const auto debug = [this](zx::channel request) {
         static constexpr fuchsia_device_manager_DebugDumper_ops_t kOps = {
-            .DumpTree = [](void* ctx, zx_handle_t vmo, fidl_txn_t* txn) {
-                VmoWriter writer{zx::vmo(vmo)};
-                static_cast<Coordinator*>(ctx)->DumpState(&writer);
-                return fuchsia_device_manager_DebugDumperDumpTree_reply(
+            .DumpTree =
+                [](void* ctx, zx_handle_t vmo, fidl_txn_t* txn) {
+                    VmoWriter writer{zx::vmo(vmo)};
+                    static_cast<Coordinator*>(ctx)->DumpState(&writer);
+                    return fuchsia_device_manager_DebugDumperDumpTree_reply(
                         txn, writer.status(), writer.written(), writer.available());
-            },
-            .DumpDrivers = [](void* ctx, zx_handle_t vmo, fidl_txn_t* txn) {
-                VmoWriter writer{zx::vmo(vmo)};
-                static_cast<Coordinator*>(ctx)->DumpDrivers(&writer);
-                return fuchsia_device_manager_DebugDumperDumpDrivers_reply(
+                },
+            .DumpDrivers =
+                [](void* ctx, zx_handle_t vmo, fidl_txn_t* txn) {
+                    VmoWriter writer{zx::vmo(vmo)};
+                    static_cast<Coordinator*>(ctx)->DumpDrivers(&writer);
+                    return fuchsia_device_manager_DebugDumperDumpDrivers_reply(
                         txn, writer.status(), writer.written(), writer.available());
-            },
-            .DumpBindingProperties = [](void* ctx, zx_handle_t vmo, fidl_txn_t* txn) {
-                VmoWriter writer{zx::vmo(vmo)};
-                static_cast<Coordinator*>(ctx)->DumpGlobalDeviceProps(&writer);
-                return fuchsia_device_manager_DebugDumperDumpBindingProperties_reply(
+                },
+            .DumpBindingProperties =
+                [](void* ctx, zx_handle_t vmo, fidl_txn_t* txn) {
+                    VmoWriter writer{zx::vmo(vmo)};
+                    static_cast<Coordinator*>(ctx)->DumpGlobalDeviceProps(&writer);
+                    return fuchsia_device_manager_DebugDumperDumpBindingProperties_reply(
                         txn, writer.status(), writer.written(), writer.available());
-            },
+                },
         };
 
-        auto status = fidl_bind(this->config_.dispatcher,
-                                request.release(),
-                                reinterpret_cast<fidl_dispatch_t*>(
-                                        fuchsia_device_manager_DebugDumper_dispatch),
-                                this,
-                                &kOps);
+        auto status = fidl_bind(
+            this->config_.dispatcher, request.release(),
+            reinterpret_cast<fidl_dispatch_t*>(fuchsia_device_manager_DebugDumper_dispatch), this,
+            &kOps);
         if (status != ZX_OK) {
             printf("Failed to bind to client channel: %d \n", status);
         }
diff --git a/zircon/system/core/devmgr/devcoordinator/coordinator.h b/zircon/system/core/devmgr/devcoordinator/coordinator.h
index 007d356..359a276 100644
--- a/zircon/system/core/devmgr/devcoordinator/coordinator.h
+++ b/zircon/system/core/devmgr/devcoordinator/coordinator.h
@@ -141,6 +141,7 @@
     void DriverAdded(Driver* drv, const char* version);
     void DriverAddedInit(Driver* drv, const char* version);
     zx_status_t LibnameToVmo(const fbl::String& libname, zx::vmo* out_vmo) const;
+    const Driver* LibnameToDriver(const fbl::String& libname) const;
 
     // Function that is invoked to request a driver try to bind to a device
     using AttemptBindFunc =
@@ -302,7 +303,6 @@
     void Suspend(SuspendContext ctx, std::function<void(zx_status_t)> callback);
 
     fbl::unique_ptr<Driver> ValidateDriver(fbl::unique_ptr<Driver> drv);
-    const Driver* LibnameToDriver(const fbl::String& libname) const;
 
     zx_status_t NewDevhost(const char* name, Devhost* parent, Devhost** out);
 
@@ -315,7 +315,6 @@
 
     zx_status_t GetMetadataRecurse(const fbl::RefPtr<Device>& dev, uint32_t type, void* buffer,
                                    size_t buflen, size_t* size);
-
     void InitOutgoingServices();
 };
 
diff --git a/zircon/system/core/devmgr/devcoordinator/device.cc b/zircon/system/core/devmgr/devcoordinator/device.cc
index e656d2a..47f1b20 100644
--- a/zircon/system/core/devmgr/devcoordinator/device.cc
+++ b/zircon/system/core/devmgr/devcoordinator/device.cc
@@ -5,6 +5,7 @@
 #include "device.h"
 
 #include <ddk/driver.h>
+#include <fbl/auto_call.h>
 #include <lib/fidl/coding.h>
 #include "../shared/fidl_txn.h"
 #include "../shared/log.h"
@@ -384,6 +385,37 @@
         if (resp->status != ZX_OK) {
             log(ERROR, "devcoordinator: rpc: bind-driver '%s' status %d\n", name_.data(),
                 resp->status);
+        } else {
+            Device* real_parent;
+            if (flags & DEV_CTX_PROXY) {
+                real_parent = this->parent().get();
+            } else {
+                real_parent = this;
+            }
+
+            for (auto& child : real_parent->children()) {
+                char bootarg[256] = {0};
+                const char* drivername =
+                     this->coordinator->LibnameToDriver(child.libname().data())->name.data();
+                snprintf(bootarg, sizeof(bootarg), "driver.%s.compatibility-tests-enable",
+                         drivername);
+
+                if (this->coordinator->boot_args().GetBool(bootarg, false)
+                    && (real_parent->test_state() == Device::TestStateMachine::kTestNotStarted)) {
+                        snprintf(bootarg, sizeof(bootarg),
+                                 "driver.%s.compatibility-tests-wait-time", drivername);
+                        const char* test_timeout = coordinator->boot_args().Get(bootarg);
+                        zx::duration test_time = (test_timeout != nullptr ?
+                                                  zx::msec(atoi(test_timeout)) :
+                                                  kDefaultTestTimeout);
+                        real_parent->set_test_time(test_time);
+                        real_parent->DriverCompatibiltyTest();
+                        break;
+                } else if (real_parent->test_state() == Device::TestStateMachine::kTestBindSent) {
+                    real_parent->test_event().signal(0, TEST_BIND_DONE_SIGNAL);
+                    break;
+                }
+            }
         }
         // TODO: try next driver, clear BOUND flag
     } else if (ordinal == fuchsia_device_manager_DeviceControllerSuspendOrdinal ||
@@ -450,6 +482,132 @@
     }
 }
 
+const char* Device::GetTestDriverName() {
+    for (auto& child: children()) {
+        return this->coordinator->LibnameToDriver(child.libname().data())->name.data();
+    }
+    return nullptr;
+}
+
+zx_status_t Device::DriverCompatibiltyTest() {
+    thrd_t t;
+    if (test_state() != TestStateMachine::kTestNotStarted) {
+         return ZX_ERR_ALREADY_EXISTS;
+    }
+    auto cb = [](void* arg) -> int {
+        auto dev = fbl::WrapRefPtr(reinterpret_cast<Device*>(arg));
+        return dev->RunCompatibilityTests();
+    };
+    int ret = thrd_create_with_name(&t, cb, this, "compatibility-tests-thread");
+    if (ret != thrd_success) {
+        log(ERROR,
+            "Driver Compatibility test failed for %s: "
+            "Thread creation failed\n", GetTestDriverName());
+        return ZX_ERR_NO_RESOURCES;
+    }
+    thrd_detach(t);
+    return ZX_OK;
+}
+
+int Device::RunCompatibilityTests() {
+    const char* test_driver_name = GetTestDriverName();
+    log(INFO, "%s: Running ddk compatibility test for driver %s \n", __func__, test_driver_name);
+    // 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;
+    }
+    zx_status_t status = zx::event::create(0, &test_event());
+    if (status != ZX_OK) {
+        log(ERROR,
+            "devcoordinator: Driver Compatibility test failed for %s: "
+            "Event creation failed : %d\n",
+            test_driver_name, status);
+        return ZX_ERR_NO_RESOURCES;
+    }
+
+    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;
+        itr++;
+        this->set_test_state(Device::TestStateMachine::kTestUnbindSent);
+        status = dh_send_unbind(&child);
+        if (status != ZX_OK) {
+            // TODO(ravoorir): How do we return to clean state here? Forcefully
+            // remove all the children?
+            log(ERROR,
+                "devcoordinator: Driver Compatibility test failed for %s: "
+                "Sending unbind to %s failed\n",
+                test_driver_name, child.name().data());
+            return ZX_ERR_INTERNAL;
+        }
+    }
+
+    zx_signals_t observed = 0;
+    // Now wait for the device to be removed.
+    status = test_event().wait_one(TEST_REMOVE_DONE_SIGNAL,
+                                   zx::deadline_after(test_time()), &observed);
+    if (status != ZX_OK) {
+        if (status == ZX_ERR_TIMED_OUT) {
+            // The Remove did not complete.
+            log(ERROR,
+                "devcoordinator: Driver Compatibility test failed for %s: "
+                "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);
+        } else {
+            log(ERROR,
+                "devcoordinator: Driver Compatibility test failed for %s: "
+                "Error waiting for device to be removed.\n",
+                test_driver_name);
+        }
+        return ZX_ERR_BAD_STATE;
+    }
+    this->set_test_state(Device::TestStateMachine::kTestBindSent);
+    this->coordinator->HandleNewDevice(fbl::WrapRefPtr(this));
+    observed = 0;
+    status = test_event().wait_one(TEST_BIND_DONE_SIGNAL,
+                                   zx::deadline_after(test_time()),
+                                   &observed);
+    if (status != ZX_OK) {
+         if (status == ZX_ERR_TIMED_OUT) {
+             // The Bind did not complete.
+             log(ERROR,
+                 "devcoordinator: Driver Compatibility test failed for %s: "
+                 "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);
+         } else {
+             log(ERROR,
+                    "devcoordinator: Driver Compatibility test failed for %s: "
+                    "Error waiting for driver to be bound: %d\n",
+                    test_driver_name, status);
+         }
+         return ZX_ERR_BAD_STATE;
+    }
+    this->set_test_state(Device::TestStateMachine::kTestBindDone);
+    if (this->children().is_empty()) {
+       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;
+    }
+    // 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;
+}
+
 // Handlers for the messages from devices
 static zx_status_t fidl_AddDevice(void* ctx, zx_handle_t raw_rpc, const uint64_t* props_data,
                                   size_t props_count, const char* name_data, size_t name_size,
diff --git a/zircon/system/core/devmgr/devcoordinator/device.h b/zircon/system/core/devmgr/devcoordinator/device.h
index e4447fe..c6ab921 100644
--- a/zircon/system/core/devmgr/devcoordinator/device.h
+++ b/zircon/system/core/devmgr/devcoordinator/device.h
@@ -6,11 +6,15 @@
 
 #include <ddk/device.h>
 #include <fbl/array.h>
+#include <fbl/auto_lock.h>
+#include <fbl/mutex.h>
 #include <fbl/ref_counted.h>
 #include <fbl/string.h>
+#include <fuchsia/device/manager/c/fidl.h>
 #include <lib/async/cpp/task.h>
 #include <lib/async/cpp/wait.h>
 #include <lib/zx/channel.h>
+#include <lib/zx/event.h>
 #include <variant>
 
 #include "composite-device.h"
@@ -53,6 +57,14 @@
 // return to this state once made visible.
 #define DEV_CTX_INVISIBLE     0x80
 
+// Signals used on the test event
+#define TEST_BIND_DONE_SIGNAL ZX_USER_SIGNAL_0
+#define TEST_SUSPEND_DONE_SIGNAL ZX_USER_SIGNAL_1
+#define TEST_RESUME_DONE_SIGNAL ZX_USER_SIGNAL_2
+#define TEST_REMOVE_DONE_SIGNAL ZX_USER_SIGNAL_3
+
+constexpr zx::duration kDefaultTestTimeout = zx::sec(5);
+
 // clang-format on
 
 struct Device : public fbl::RefCounted<Device>, public AsyncLoopRefCountedRpcHandler<Device> {
@@ -306,6 +318,8 @@
     // Device.
     void CompleteSuspend(zx_status_t status);
 
+    zx_status_t DriverCompatibiltyTest();
+
     zx::channel take_client_remote() { return std::move(client_remote_); }
 
     const fbl::String& name() const { return name_; }
@@ -330,8 +344,40 @@
     };
 
     State state() const { return state_; }
+
+    enum class TestStateMachine {
+        kTestNotStarted = 1,
+        kTestUnbindSent,
+        kTestBindSent,
+        kTestBindDone,
+        kTestSuspendSent,
+        kTestSuspendDone,
+        kTestResumeSent,
+        kTestResumeDone,
+        kTestDone,
+    };
+
+    TestStateMachine test_state() {
+        fbl::AutoLock<fbl::Mutex> lock(&test_state_lock_);
+        return test_state_;
+    }
+
+    void set_test_state(TestStateMachine new_state) {
+        fbl::AutoLock<fbl::Mutex> lock(&test_state_lock_);
+        test_state_ = new_state;
+    }
+    void set_test_time(zx::duration& test_time) {
+        test_time_ = test_time;
+    }
+    zx::duration& test_time() {
+        return test_time_;
+    }
+    const char* GetTestDriverName();
+    zx::event& test_event() { return test_event_; }
+
 private:
     zx_status_t HandleRead();
+    int RunCompatibilityTests();
 
     const fbl::String name_;
     const fbl::String libname_;
@@ -394,6 +440,10 @@
     // For attaching as an open connection to the proxy device,
     // or once the device becomes visible.
     zx::channel client_remote_;
+    fbl::Mutex test_state_lock_;
+    TestStateMachine test_state_ __TA_GUARDED(test_state_lock_) = TestStateMachine::kTestNotStarted;
+    zx::event test_event_;
+    zx::duration test_time_;
 };
 
 } // namespace devmgr