Moved stuff to device

Change-Id: Ic24dc64b8f85f914b9c3886877084fafa23de3dc
diff --git a/zircon/system/core/devmgr/devcoordinator/device.cpp b/zircon/system/core/devmgr/devcoordinator/device.cpp
index ac830d0..0e9bcc1 100644
--- a/zircon/system/core/devmgr/devcoordinator/device.cpp
+++ b/zircon/system/core/devmgr/devcoordinator/device.cpp
@@ -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"
@@ -396,18 +397,16 @@
 
             for (auto& child : real_parent->children()) {
                 char bootarg[256];
-                snprintf(bootarg, sizeof(bootarg), "driver.%s.run-compatibility-tests",
-                         this->coordinator->LibnameToDriver(child.libname().data())->name.data());
+                const char* drivername = this->coordinator->LibnameToDriver(child.libname().data())->name.data();
+                snprintf(bootarg, sizeof(bootarg), "driver.%s.run-compatibility-tests", drivername);
 
                 //FOR TESTING PURPOSES
                 if (!strcmp(bootarg, "driver.aml_sd_emmc.run-compatibility-tests")
                 //if (this->coordinator->boot_args().GetBool(bootarg, false)
                     && (real_parent->test_state == Device::TestStateMachine::kTestNotStarted)) {
-                        auto newchild = fbl::WrapRefPtr(static_cast<Device*>(&child));
-                        this->coordinator->DriverCompatibiltyTest(newchild);
+                        real_parent->DriverCompatibiltyTest(drivername);
                         break;
-                } else if (real_parent->test_state == Device::TestStateMachine::kTestBindSent &&
-                           !strcmp(this->coordinator->test_context().devname, child.name().data())) {
+                } else if (real_parent->test_state == Device::TestStateMachine::kTestBindSent) {
                     zx_object_signal(real_parent->test_event, 0, TEST_BIND_DONE_SIGNAL);
                     break;
                 }
@@ -478,6 +477,127 @@
     }
 }
 
+zx_status_t Device::DriverCompatibiltyTest(const char* drivername) {
+    thrd_t t;
+    strcpy(test_drivername, drivername);
+    auto cb = [](void* arg) -> int {
+        return reinterpret_cast<Device*>(arg)->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", test_drivername);
+        return ZX_ERR_NO_RESOURCES;
+    }
+    thrd_detach(t);
+    return ZX_OK;
+}
+
+int Device::RunCompatibilityTests() {
+    log(INFO, "%s: Running ddk compatibility test for driver %s \n", __func__, test_drivername);
+
+    // Device should be bound for test to work
+    if (!(flags & DEV_CTX_BOUND)) {
+        log(ERROR,
+            "devcoordinator: Driver Compatibility test failed for %s: "
+            "Parent Device not bound\n",
+            test_drivername);
+        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_drivername, status);
+        return -1;
+    }
+
+    auto cleanup = fbl::MakeAutoCall([this]() {
+        zx_handle_close(this->test_event);
+        test_event = ZX_HANDLE_INVALID;
+        test_state = Device::TestStateMachine::kTestDone;
+    });
+
+    // Issue unbind on all its children.
+    for (auto& child : this->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",
+                test_drivername, child.name().data());
+            return -1;
+        }
+    }
+
+    this->test_state = Device::TestStateMachine::kTestUnbindSent;
+    uint32_t observed = 0;
+    // Now wait for the device to be removed.
+    status = zx_object_wait_one(this->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",
+                test_drivername, status);
+        } else {
+            log(ERROR,
+                "devcoordinator: Driver Compatibility test failed for %s: "
+                "Error waiting for device to be removed.\n",
+                test_drivername);
+        }
+        return -1;
+    }
+    if (observed & TEST_REMOVE_DONE_SIGNAL) {
+        this->test_state = Device::TestStateMachine::kTestRemoveCalled;
+        observed = 0;
+        this->coordinator->HandleNewDevice(fbl::WrapRefPtr(this));
+        this->test_state = Device::TestStateMachine::kTestBindSent;
+        status = zx_object_wait_one(this->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",
+                    test_drivername, status);
+            } else {
+                log(ERROR,
+                    "devcoordinator: Driver Compatibility test failed for %s: "
+                    "Error waiting for driver to be bound: %d\n",
+                    test_drivername, status);
+            }
+            return -1;
+        }
+        if (observed & TEST_BIND_DONE_SIGNAL) {
+            this->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_drivername);
+                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_drivername);
+    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 6ba7ce7..a952e89 100644
--- a/zircon/system/core/devmgr/devcoordinator/device.h
+++ b/zircon/system/core/devmgr/devcoordinator/device.h
@@ -312,6 +312,8 @@
     // Device.
     void CompleteSuspend(zx_status_t status);
 
+    zx_status_t DriverCompatibiltyTest(const char* drivername);
+
     zx::channel take_client_remote() { return std::move(client_remote_); }
 
     const fbl::String& name() const { return name_; }
@@ -351,9 +353,11 @@
     };
 
     TestStateMachine test_state = TestStateMachine::kTestNotStarted;
+    char test_drivername[fuchsia_device_manager_DEVICE_NAME_MAX];
     zx_handle_t test_event = ZX_HANDLE_INVALID;
 private:
     zx_status_t HandleRead();
+    int RunCompatibilityTests();
 
     const fbl::String name_;
     const fbl::String libname_;