[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: Manually verified with aml-sd-emmc driver. Will add a test driver
that in-turn tests the functionality of these tests.

Change-Id: Ifb0fcc8019eb2bd38d51eb702c00e3547ef482f5
diff --git a/zircon/docs/kernel_cmdline.md b/zircon/docs/kernel_cmdline.md
index b8a6830..e6c70af 100644
--- a/zircon/docs/kernel_cmdline.md
+++ b/zircon/docs/kernel_cmdline.md
@@ -73,6 +73,12 @@
 
 Turn on verbose logging.
 
+## driver.\<name>.run-compatibility-tests
+
+if this option is set, devmgr will run compatibility tests for the device
+that is published in DdkAdd("name") by the driver under test. If the compatibility
+tests fail, the driver is unbound. The name comes from the parameter to DdkAdd.
+
 ## 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.cpp b/zircon/system/core/devmgr/devcoordinator/coordinator.cpp
index 5dae1a8..52af492 100644
--- a/zircon/system/core/devmgr/devcoordinator/coordinator.cpp
+++ b/zircon/system/core/devmgr/devcoordinator/coordinator.cpp
@@ -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>
@@ -118,8 +119,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_,
@@ -184,8 +184,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 {
@@ -226,10 +226,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" : "",
@@ -242,8 +241,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");
@@ -332,7 +331,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();
@@ -396,24 +395,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()},
         };
@@ -421,7 +419,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()},
         };
@@ -473,9 +471,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;
@@ -531,8 +529,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) {
@@ -556,10 +554,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;
     }
@@ -715,6 +712,14 @@
                 // AND our parent is a BUSDEV
                 // AND our parent's devhost is not dying
                 // THEN we will want to rebind our parent
+                if (parent->test_state == Device::TestStateMachine::kTestUnbindSent) {
+                    zx_object_signal(parent->test_event, 0, TEST_REMOVE_DONE_SIGNAL);
+                    if (!(dev->flags & DEV_CTX_PROXY)) {
+                        // remove from list of all devices
+                        devices_.erase(*dev);
+                    }
+                    return ZX_OK;
+                }
                 if (!(parent->flags & DEV_CTX_DEAD) && (parent->flags & DEV_CTX_MUST_ISOLATE) &&
                     ((parent->host() == nullptr) ||
                      !(parent->host()->flags() & Devhost::Flags::kDying))) {
@@ -744,10 +749,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_) {
@@ -755,9 +761,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;
     }
@@ -812,7 +818,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;
@@ -857,7 +864,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;
@@ -1199,8 +1206,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 {
@@ -1287,8 +1294,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"
@@ -1298,6 +1305,141 @@
     }
 }
 
+zx_status_t Coordinator::DriverCompatibiltyTest(const fbl::RefPtr<Device>& dev) {
+    thrd_t t;
+    test_context_.dev = dev;
+    auto cb = [](void* arg) -> int {
+        return reinterpret_cast<Coordinator*>(arg)->RunCompatibilityTests();
+    };
+    int ret = thrd_create_with_name(&t, cb, this, "compatibility-tests-thread");
+    if (ret != thrd_success) {
+        log(ERROR,
+            "devcoordinator: Driver Compatibility test failed for %s: "
+            "Thread creation failed\n",
+            dev->name().data());
+        return ZX_ERR_NO_RESOURCES;
+    }
+    thrd_detach(t);
+    return ZX_OK;
+}
+
+int Coordinator::RunCompatibilityTests() {
+    const fbl::RefPtr<Device>& dev = test_context_.dev;
+    log(INFO, "%s: Driver compatibility test for driver %s \n", __func__, dev->name().data());
+    char devname[256];
+    strncpy(devname, dev->name().data(), 255);
+
+    // Get Real Parent. Lets track the test with real parent.
+    // We have a reference for dev. So the parent cannot be deleted without the
+    // dev being deleted.
+    fbl::RefPtr<Device> real_parent;
+    if (dev->parent()->flags & DEV_CTX_PROXY) {
+        real_parent = dev->parent()->parent();
+    } else {
+        real_parent = dev->parent();
+    }
+
+    // Device should be bound for test to work
+    if (!(dev->parent()->flags & DEV_CTX_BOUND)) {
+        log(ERROR,
+            "devcoordinator: Driver Compatibility test failed for %s: "
+            "Parent Device not bound\n",
+            devname);
+        return ZX_ERR_INTERNAL;
+    }
+    zx_status_t status = zx_event_create(0, &real_parent->test_event);
+    if (status != ZX_OK) {
+        log(ERROR,
+            "devcoordinator: Driver Compatibility test failed for %s: "
+            "Event creation failed : %d\n",
+            devname, status);
+        return -1;
+    }
+
+    auto cleanup = fbl::MakeAutoCall([&real_parent]() {
+        zx_handle_close(real_parent->test_event);
+        real_parent->test_event = ZX_HANDLE_INVALID;
+        real_parent->test_state = Device::TestStateMachine::kTestDone;
+    });
+    // Issue unbind on this child all its siblings.
+    for (auto& child : real_parent->children()) {
+        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",
+                devname, child.name().data());
+            return -1;
+        }
+    }
+
+    real_parent->test_state = Device::TestStateMachine::kTestUnbindSent;
+    uint32_t observed = 0;
+    // Now wait for the device to be removed.
+    status = zx_object_wait_one(real_parent->test_event, TEST_REMOVE_DONE_SIGNAL,
+                                zx_deadline_after(ZX_SEC(20)), &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",
+                devname, status);
+        } else {
+            log(ERROR,
+                "devcoordinator: Driver Compatibility test failed for %s: "
+                "Error waiting for device to be removed.\n",
+                devname);
+        }
+        return -1;
+    }
+    if (observed & TEST_REMOVE_DONE_SIGNAL) {
+        real_parent->test_state = Device::TestStateMachine::kTestRemoveCalled;
+        log(ERROR, "devcoordinator: Ok. STAGE1 COMPLETE: Device Removed.\n");
+        observed = 0;
+        HandleNewDevice(real_parent);
+        real_parent->test_state = Device::TestStateMachine::kTestBindSent;
+        status = zx_object_wait_one(real_parent->test_event, TEST_BIND_DONE_SIGNAL,
+                                    zx_deadline_after(ZX_SEC(20)), &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",
+                    devname, status);
+            } else {
+                log(ERROR,
+                    "devcoordinator: Driver Compatibility test failed for %s: "
+                    "Error waiting for driver to be bound: %d\n",
+                    devname, status);
+            }
+            return -1;
+        }
+        if (observed & TEST_BIND_DONE_SIGNAL) {
+            real_parent->test_state = Device::TestStateMachine::kTestBindDone;
+            if (real_parent->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",
+                    devname);
+                return -1;
+            }
+        }
+    }
+
+    // TODO(ravoorir): Test Suspend and Resume hooks
+    log(ERROR, "%s: Driver Compatibility test %s for %s\n", __func__,
+        real_parent->test_state == Device::TestStateMachine::kTestBindDone ? "Succeeded" : "Failed",
+        devname);
+    return ZX_OK;
+}
+
 zx_status_t Coordinator::BindDriverToDevice(const fbl::RefPtr<Device>& dev, const Driver* drv,
                                             bool autobind, const AttemptBindFunc& attempt_bind) {
     if (!dev->is_bindable()) {
@@ -1307,8 +1449,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(),
@@ -1323,7 +1463,7 @@
     zx_status_t topo_status = GetTopologicalPath(dev, path, sizeof(path));
     if (topo_status == ZX_OK && strstr(path, "misc/ramctl") != nullptr) {
         printf("[%ld ms] (ramctl) BindDriver: %s to %s\n",
-            zx::clock::get_monotonic().get() / ZX_MSEC(1), drv->name.data(), path);
+               zx::clock::get_monotonic().get() / ZX_MSEC(1), drv->name.data(), path);
     }
     return status;
 }
@@ -1344,7 +1484,7 @@
         if (status == ZX_OK) {
             // TODO(FLK-299): Remove this once the root cause is found.
             printf("[%ld ms] (misc) BindDriver: %s\n",
-                zx::clock::get_monotonic().get() / ZX_MSEC(1), drv->name.data());
+                   zx::clock::get_monotonic().get() / ZX_MSEC(1), drv->name.data());
         }
         return status;
     }
@@ -1357,8 +1497,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;
         }
@@ -1509,48 +1649,47 @@
             },
         };
 
-        const auto status = fidl_bind(this->config_.dispatcher,
-                                      request.release(),
-                                      reinterpret_cast<fidl_dispatch_t*>(
-                                              fuchsia_device_manager_Administrator_dispatch),
-                                      this,
-                                      &kOps);
-           if (status != ZX_OK) {
-              printf("Failed to bind to client channel: %d \n", status);
-           }
-           return status;
-       };
+        const auto status = fidl_bind(
+            this->config_.dispatcher, request.release(),
+            reinterpret_cast<fidl_dispatch_t*>(fuchsia_device_manager_Administrator_dispatch), this,
+            &kOps);
+        if (status != ZX_OK) {
+            printf("Failed to bind to client channel: %d \n", status);
+        }
+        return status;
+    };
     public_dir->AddEntry(fuchsia_device_manager_Administrator_Name,
                          fbl::MakeRefCounted<fs::Service>(admin));
 
     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 be2a5d1e..96abd26 100644
--- a/zircon/system/core/devmgr/devcoordinator/coordinator.h
+++ b/zircon/system/core/devmgr/devcoordinator/coordinator.h
@@ -111,6 +111,10 @@
     bool suspend_debug;
 };
 
+struct CompatibilityTestArgs {
+   fbl::RefPtr<Device> dev;
+};
+
 class Coordinator {
 public:
     Coordinator(const Coordinator&) = delete;
@@ -233,6 +237,7 @@
 
     // This method is public only for the test suite.
     zx_status_t BindDriver(Driver* drv, const AttemptBindFunc& attempt_bind);
+    zx_status_t DriverCompatibiltyTest(const fbl::RefPtr<Device>& dev);
 private:
     CoordinatorConfig config_;
     bool running_ = false;
@@ -270,6 +275,7 @@
     fbl::RefPtr<Device> test_device_;
 
     SuspendContext suspend_context_;
+    CompatibilityTestArgs test_context_;
 
     fbl::DoublyLinkedList<fbl::unique_ptr<Metadata>, Metadata::Node> published_metadata_;
 
@@ -299,6 +305,7 @@
 
     zx_status_t GetMetadataRecurse(const fbl::RefPtr<Device>& dev, uint32_t type, void* buffer,
                                    size_t buflen, size_t* size);
+    int RunCompatibilityTests();
 
     void InitOutgoingServices();
 };
diff --git a/zircon/system/core/devmgr/devcoordinator/device.cpp b/zircon/system/core/devmgr/devcoordinator/device.cpp
index c406e0a..e51e469 100644
--- a/zircon/system/core/devmgr/devcoordinator/device.cpp
+++ b/zircon/system/core/devmgr/devcoordinator/device.cpp
@@ -382,6 +382,30 @@
         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;
+            }
+
+            ZX_DEBUG_ASSERT((real_parent->children().is_empty() == false));
+            for (auto& child : real_parent->children()) {
+                char bootarg[256];
+                snprintf(bootarg, sizeof(bootarg), "driver.%s.run-compatibility-tests",
+                         child.name().data());
+                if (this->coordinator->boot_args().GetBool(bootarg, false)) {
+                    if (real_parent->test_state == Device::TestStateMachine::kTestNotStarted) {
+                        auto newchild = fbl::WrapRefPtr(static_cast<Device*>(&child));
+                        this->coordinator->DriverCompatibiltyTest(newchild);
+                        break;
+                    } else if (real_parent->test_state == Device::TestStateMachine::kTestBindSent) {
+                        zx_object_signal(real_parent->test_event, 0, TEST_BIND_DONE_SIGNAL);
+                        break;
+                    }
+                }
+            }
         }
         // TODO: try next driver, clear BOUND flag
     } else if (ordinal == fuchsia_device_manager_DeviceControllerSuspendOrdinal ||
diff --git a/zircon/system/core/devmgr/devcoordinator/device.h b/zircon/system/core/devmgr/devcoordinator/device.h
index e4447fe..6ba7ce7 100644
--- a/zircon/system/core/devmgr/devcoordinator/device.h
+++ b/zircon/system/core/devmgr/devcoordinator/device.h
@@ -53,6 +53,12 @@
 // 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_4
+
 // clang-format on
 
 struct Device : public fbl::RefCounted<Device>, public AsyncLoopRefCountedRpcHandler<Device> {
@@ -330,6 +336,22 @@
     };
 
     State state() const { return state_; }
+
+    enum class TestStateMachine {
+        kTestNotStarted = 1,
+        kTestUnbindSent,
+        kTestRemoveCalled,
+        kTestBindSent,
+        kTestBindDone,
+        kTestSuspendSent,
+        kTestSupsendDone,
+        kTestResumeSent,
+        kTestResumeDone,
+        kTestDone,
+    };
+
+    TestStateMachine test_state = TestStateMachine::kTestNotStarted;
+    zx_handle_t test_event = ZX_HANDLE_INVALID;
 private:
     zx_status_t HandleRead();