[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();