[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