blob: fd5986e5a2ac0db12e3dcaa012652d7f8490cf45 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "coordinator.h"
#include <fuchsia/device/manager/c/fidl.h>
#include <fuchsia/driver/test/c/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/fdio/directory.h>
#include <lib/fidl/coding.h>
#include <lib/fidl/cpp/message.h>
#include <lib/fidl/cpp/message_builder.h>
#include <string.h>
#include <threads.h>
#include <zircon/fidl.h>
#include <vector>
#include <ddk/binding.h>
#include <ddk/driver.h>
#include <fbl/algorithm.h>
#include <fbl/vector.h>
#include <zxtest/zxtest.h>
#include "devfs.h"
#include "devhost.h"
#include "driver-test-reporter.h"
#include "fdio.h"
namespace {
constexpr char kSystemDriverPath[] = "/boot/driver/platform-bus.so";
constexpr char kDriverPath[] = "/boot/driver/test/mock-device.so";
constexpr char kLogMessage[] = "log message text";
constexpr char kLogTestCaseName[] = "log test case";
class DummyFsProvider : public devmgr::FsProvider {
~DummyFsProvider() {}
zx::channel CloneFs(const char* path) override { return zx::channel(); }
};
void CreateBootArgs(const char* config, size_t size, devmgr::BootArgs* boot_args) {
zx::vmo vmo;
zx_status_t status = zx::vmo::create(size, 0, &vmo);
ASSERT_OK(status);
status = vmo.write(config, 0, size);
ASSERT_OK(status);
status = devmgr::BootArgs::Create(std::move(vmo), size, boot_args);
ASSERT_OK(status);
}
devmgr::CoordinatorConfig DefaultConfig(async_dispatcher_t* dispatcher,
devmgr::BootArgs* boot_args) {
devmgr::CoordinatorConfig config{};
const char config1[] = "key1=old-value\0key2=value2\0key1=new-value";
if (boot_args != nullptr) {
CreateBootArgs(config1, sizeof(config1), boot_args);
}
config.dispatcher = dispatcher;
config.require_system = false;
config.asan_drivers = false;
config.boot_args = boot_args;
config.fs_provider = new DummyFsProvider();
config.suspend_fallback = true;
zx::event::create(0, &config.fshost_event);
return config;
}
TEST(CoordinatorTestCase, InitializeCoreDevices) {
devmgr::Coordinator coordinator(DefaultConfig(nullptr, nullptr));
zx_status_t status = coordinator.InitializeCoreDevices(kSystemDriverPath);
ASSERT_OK(status);
}
TEST(CoordinatorTestCase, DumpState) {
devmgr::Coordinator coordinator(DefaultConfig(nullptr, nullptr));
zx_status_t status = coordinator.InitializeCoreDevices(kSystemDriverPath);
ASSERT_OK(status);
constexpr int32_t kBufSize = 256;
char buf[kBufSize + 1] = {0};
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(kBufSize, 0, &vmo));
devmgr::VmoWriter writer(std::move(vmo));
coordinator.DumpState(&writer);
ASSERT_EQ(writer.written(), writer.available());
ASSERT_LT(writer.written(), kBufSize);
ASSERT_GT(writer.written(), 0);
ASSERT_OK(writer.vmo().read(buf, 0, writer.written()));
ASSERT_NE(nullptr, strstr(buf, "[root]"));
}
TEST(CoordinatorTestCase, LoadDriver) {
bool found_driver = false;
auto callback = [&found_driver](devmgr::Driver* drv, const char* version) {
delete drv;
found_driver = true;
};
devmgr::load_driver(kDriverPath, callback);
ASSERT_TRUE(found_driver);
}
TEST(CoordinatorTestCase, BindDrivers) {
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
devmgr::Coordinator coordinator(DefaultConfig(loop.dispatcher(), nullptr));
zx_status_t status = coordinator.InitializeCoreDevices(kSystemDriverPath);
ASSERT_OK(status);
coordinator.set_running(true);
devmgr::Driver* driver;
auto callback = [&coordinator, &driver](devmgr::Driver* drv, const char* version) {
driver = drv;
return coordinator.DriverAdded(drv, version);
};
devmgr::load_driver(kDriverPath, callback);
loop.RunUntilIdle();
ASSERT_EQ(1, coordinator.drivers().size_slow());
ASSERT_EQ(driver, &coordinator.drivers().front());
}
// Test binding drivers against the root/test/misc devices
TEST(CoordinatorTestCase, BindDriversForBuiltins) {
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
devmgr::Coordinator coordinator(DefaultConfig(loop.dispatcher(), nullptr));
zx_status_t status = coordinator.InitializeCoreDevices(kSystemDriverPath);
ASSERT_OK(status);
// AttemptBind function that asserts it has only been called once
class CallOnce {
public:
explicit CallOnce(size_t line) : line_number_(line) {}
CallOnce(const CallOnce&) = delete;
CallOnce& operator=(const CallOnce&) = delete;
CallOnce(CallOnce&& other) { *this = std::move(other); }
CallOnce& operator=(CallOnce&& other) {
if (this != &other) {
line_number_ = other.line_number_;
call_count_ = other.call_count_;
// Ensure the dtor for the other one doesn't run
other.call_count_ = 1;
}
return *this;
}
~CallOnce() { EXPECT_EQ(1, call_count_, "Mismatch from line %zu\n", line_number_); }
zx_status_t operator()(const devmgr::Driver* drv, const fbl::RefPtr<devmgr::Device>& dev) {
++call_count_;
return ZX_OK;
}
private:
size_t line_number_;
size_t call_count_ = 0;
};
auto make_fake_driver = [](auto&& instructions) -> std::unique_ptr<devmgr::Driver> {
size_t instruction_count = fbl::count_of(instructions);
auto binding = std::make_unique<zx_bind_inst_t[]>(instruction_count);
memcpy(binding.get(), instructions, instruction_count * sizeof(instructions[0]));
auto drv = std::make_unique<devmgr::Driver>();
drv->binding.reset(binding.release());
drv->binding_size = static_cast<uint32_t>(instruction_count * sizeof(instructions[0]));
return drv;
};
{
zx_bind_inst_t test_drv_bind[] = {
BI_MATCH_IF(EQ, BIND_PROTOCOL, ZX_PROTOCOL_TEST_PARENT),
};
auto test_drv = make_fake_driver(test_drv_bind);
ASSERT_OK(coordinator.BindDriver(test_drv.get(), CallOnce{__LINE__}));
}
{
zx_bind_inst_t misc_drv_bind[] = {
BI_MATCH_IF(EQ, BIND_PROTOCOL, ZX_PROTOCOL_MISC_PARENT),
};
auto misc_drv = make_fake_driver(misc_drv_bind);
ASSERT_OK(coordinator.BindDriver(misc_drv.get(), CallOnce{__LINE__}));
}
{
zx_bind_inst_t root_drv_bind[] = {
BI_MATCH_IF(EQ, BIND_PROTOCOL, ZX_PROTOCOL_ROOT),
};
auto root_drv = make_fake_driver(root_drv_bind);
ASSERT_OK(coordinator.BindDriver(root_drv.get(), CallOnce{__LINE__}));
}
{
zx_bind_inst_t test_drv_bind[] = {
BI_MATCH_IF(EQ, BIND_PROTOCOL, ZX_PROTOCOL_TEST_PARENT),
BI_MATCH_IF(EQ, BIND_PROTOCOL, ZX_PROTOCOL_I2C),
};
auto test_drv = make_fake_driver(test_drv_bind);
ASSERT_OK(coordinator.BindDriver(test_drv.get(), CallOnce{__LINE__}));
}
{
zx_bind_inst_t misc_drv_bind[] = {
BI_MATCH_IF(EQ, BIND_PROTOCOL, ZX_PROTOCOL_MISC_PARENT),
BI_MATCH_IF(EQ, BIND_PROTOCOL, ZX_PROTOCOL_I2C),
};
auto misc_drv = make_fake_driver(misc_drv_bind);
ASSERT_OK(coordinator.BindDriver(misc_drv.get(), CallOnce{__LINE__}));
}
{
zx_bind_inst_t root_drv_bind[] = {
BI_MATCH_IF(EQ, BIND_PROTOCOL, ZX_PROTOCOL_ROOT),
BI_MATCH_IF(EQ, BIND_PROTOCOL, ZX_PROTOCOL_I2C),
};
auto root_drv = make_fake_driver(root_drv_bind);
ASSERT_OK(coordinator.BindDriver(root_drv.get(), CallOnce{__LINE__}));
}
}
void InitializeCoordinator(devmgr::Coordinator* coordinator) {
zx_status_t status = coordinator->InitializeCoreDevices(kSystemDriverPath);
ASSERT_OK(status);
// Load the component driver
devmgr::load_driver(devmgr::kComponentDriverPath,
fit::bind_member(coordinator, &devmgr::Coordinator::DriverAddedInit));
// Add the driver we're using as platform bus
devmgr::load_driver(kSystemDriverPath,
fit::bind_member(coordinator, &devmgr::Coordinator::DriverAddedInit));
// Initialize devfs.
devmgr::devfs_init(coordinator->root_device(), coordinator->dispatcher());
status = devmgr::devfs_publish(coordinator->root_device(), coordinator->test_device());
status = devmgr::devfs_publish(coordinator->root_device(), coordinator->sys_device());
ASSERT_OK(status);
coordinator->set_running(true);
}
// Reads a BindDriver request from remote, checks that it is for the expected
// driver, and then sends a ZX_OK response.
void CheckBindDriverReceived(const zx::channel& remote, const char* expected_driver) {
// Read the BindDriver request.
FIDL_ALIGNDECL uint8_t bytes[ZX_CHANNEL_MAX_MSG_BYTES];
zx_handle_t handles[ZX_CHANNEL_MAX_MSG_HANDLES];
uint32_t actual_bytes;
uint32_t actual_handles;
zx_status_t status = remote.read(0, bytes, handles, sizeof(bytes), fbl::count_of(handles),
&actual_bytes, &actual_handles);
ASSERT_OK(status);
ASSERT_LT(0, actual_bytes);
ASSERT_EQ(1, actual_handles);
status = zx_handle_close(handles[0]);
ASSERT_OK(status);
// Validate the BindDriver request.
auto hdr = reinterpret_cast<fidl_message_header_t*>(bytes);
ASSERT_EQ(fuchsia_device_manager_DeviceControllerBindDriverOrdinal, hdr->ordinal);
status = fidl_decode(&fuchsia_device_manager_DeviceControllerBindDriverRequestTable, bytes,
actual_bytes, handles, actual_handles, nullptr);
ASSERT_OK(status);
auto req = reinterpret_cast<fuchsia_device_manager_DeviceControllerBindDriverRequest*>(bytes);
ASSERT_EQ(req->driver_path.size, strlen(expected_driver));
ASSERT_BYTES_EQ(reinterpret_cast<const uint8_t*>(expected_driver),
reinterpret_cast<const uint8_t*>(req->driver_path.data), req->driver_path.size,
"");
// Write the BindDriver response.
memset(bytes, 0, sizeof(bytes));
auto resp = reinterpret_cast<fuchsia_device_manager_DeviceControllerBindDriverResponse*>(bytes);
resp->hdr.ordinal = fuchsia_device_manager_DeviceControllerBindDriverOrdinal;
resp->status = ZX_OK;
status = fidl_encode(&fuchsia_device_manager_DeviceControllerBindDriverResponseTable, bytes,
sizeof(*resp), handles, fbl::count_of(handles), &actual_handles, nullptr);
ASSERT_OK(status);
ASSERT_EQ(0, actual_handles);
status = remote.write(0, bytes, sizeof(*resp), nullptr, 0);
ASSERT_OK(status);
}
TEST(CoordinatorTestCase, BindDevices) {
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
devmgr::Coordinator coordinator(DefaultConfig(loop.dispatcher(), nullptr));
ASSERT_NO_FATAL_FAILURES(InitializeCoordinator(&coordinator));
// Add the device.
zx::channel local, remote;
zx_status_t status = zx::channel::create(0, &local, &remote);
ASSERT_OK(status);
fbl::RefPtr<devmgr::Device> device;
status = coordinator.AddDevice(coordinator.test_device(), std::move(local),
nullptr /* props_data */, 0 /* props_count */, "mock-device",
ZX_PROTOCOL_TEST, nullptr /* driver_path */, nullptr /* args */,
false /* invisible */, zx::channel() /* client_remote */, &device);
ASSERT_OK(status);
ASSERT_EQ(1, coordinator.devices().size_slow());
// Add the driver.
devmgr::load_driver(kDriverPath,
fit::bind_member(&coordinator, &devmgr::Coordinator::DriverAdded));
loop.RunUntilIdle();
ASSERT_FALSE(coordinator.drivers().is_empty());
// Bind the device to a fake devhost.
fbl::RefPtr<devmgr::Device> dev = fbl::RefPtr(&coordinator.devices().front());
devmgr::Devhost host;
host.AddRef(); // refcount starts at zero, so bump it up to keep us from being cleaned up
dev->set_host(&host);
status = coordinator.BindDevice(dev, kDriverPath, true /* new device */);
ASSERT_OK(status);
// Check the BindDriver request.
ASSERT_NO_FATAL_FAILURES(CheckBindDriverReceived(remote, kDriverPath));
loop.RunUntilIdle();
// Reset the fake devhost connection.
dev->set_host(nullptr);
remote.reset();
loop.RunUntilIdle();
}
// Reads a BindDriver request from remote, checks that it is for the expected
// driver, and then sends a ZX_OK response.
void BindDriverTestOutput(const zx::channel& remote, zx::channel test_output) {
// Read the BindDriver request.
FIDL_ALIGNDECL uint8_t bytes[ZX_CHANNEL_MAX_MSG_BYTES];
zx_handle_t handles[ZX_CHANNEL_MAX_MSG_HANDLES];
uint32_t actual_bytes;
uint32_t actual_handles;
zx_status_t status = remote.read(0, bytes, handles, sizeof(bytes), fbl::count_of(handles),
&actual_bytes, &actual_handles);
ASSERT_OK(status);
ASSERT_LT(0, actual_bytes);
ASSERT_EQ(1, actual_handles);
status = zx_handle_close(handles[0]);
ASSERT_OK(status);
// Validate the BindDriver request.
auto hdr = reinterpret_cast<fidl_message_header_t*>(bytes);
ASSERT_EQ(fuchsia_device_manager_DeviceControllerBindDriverOrdinal, hdr->ordinal);
status = fidl_decode(&fuchsia_device_manager_DeviceControllerBindDriverRequestTable, bytes,
actual_bytes, handles, actual_handles, nullptr);
ASSERT_OK(status);
// Write the BindDriver response.
memset(bytes, 0, sizeof(bytes));
auto resp = reinterpret_cast<fuchsia_device_manager_DeviceControllerBindDriverResponse*>(bytes);
resp->hdr.ordinal = fuchsia_device_manager_DeviceControllerBindDriverOrdinal;
resp->status = ZX_OK;
resp->test_output = test_output.release();
status = fidl_encode(&fuchsia_device_manager_DeviceControllerBindDriverResponseTable, bytes,
sizeof(*resp), handles, fbl::count_of(handles), &actual_handles, nullptr);
ASSERT_OK(status);
ASSERT_EQ(1, actual_handles);
status = remote.write(0, bytes, sizeof(*resp), handles, actual_handles);
ASSERT_OK(status);
}
void WriteTestLog(const zx::channel& output) {
uint32_t len =
sizeof(fuchsia_driver_test_LoggerLogMessageRequest) + FIDL_ALIGN(strlen(kLogMessage));
FIDL_ALIGNDECL uint8_t bytes[len];
fidl::Builder builder(bytes, len);
auto* req = builder.New<fuchsia_driver_test_LoggerLogMessageRequest>();
req->hdr.ordinal = fuchsia_driver_test_LoggerLogMessageOrdinal;
req->hdr.txid = FIDL_TXID_NO_RESPONSE;
auto* data = builder.NewArray<char>(static_cast<uint32_t>(strlen(kLogMessage)));
req->msg.data = data;
req->msg.size = strlen(kLogMessage);
memcpy(data, kLogMessage, strlen(kLogMessage));
fidl::Message msg(builder.Finalize(), fidl::HandlePart());
const char* err = nullptr;
zx_status_t status = msg.Encode(&fuchsia_driver_test_LoggerLogMessageRequestTable, &err);
ASSERT_OK(status);
status = msg.Write(output.get(), 0);
ASSERT_OK(status);
}
void WriteTestCase(const zx::channel& output) {
uint32_t len =
sizeof(fuchsia_driver_test_LoggerLogTestCaseRequest) + FIDL_ALIGN(strlen(kLogTestCaseName));
FIDL_ALIGNDECL uint8_t bytes[len];
fidl::Builder builder(bytes, len);
auto* req = builder.New<fuchsia_driver_test_LoggerLogTestCaseRequest>();
req->hdr.ordinal = fuchsia_driver_test_LoggerLogTestCaseOrdinal;
req->hdr.txid = FIDL_TXID_NO_RESPONSE;
auto* data = builder.NewArray<char>(static_cast<uint32_t>(strlen(kLogTestCaseName)));
req->name.data = data;
req->name.size = strlen(kLogTestCaseName);
memcpy(data, kLogTestCaseName, strlen(kLogTestCaseName));
req->result.passed = 1;
req->result.failed = 2;
req->result.skipped = 3;
fidl::Message msg(builder.Finalize(), fidl::HandlePart());
const char* err = nullptr;
zx_status_t status = msg.Encode(&fuchsia_driver_test_LoggerLogTestCaseRequestTable, &err);
ASSERT_OK(status);
status = msg.Write(output.get(), 0);
ASSERT_OK(status);
}
class TestDriverTestReporter : public devmgr::DriverTestReporter {
public:
explicit TestDriverTestReporter(const fbl::String& driver_name)
: devmgr::DriverTestReporter(driver_name) {}
void LogMessage(const char* msg, size_t size) override {
if (size != strlen(kLogMessage)) {
return;
}
if (strncmp(msg, kLogMessage, size)) {
return;
}
log_message_called = true;
}
void LogTestCase(const char* name, size_t name_size,
const fuchsia_driver_test_TestCaseResult* result) override {
if (name_size != strlen(kLogTestCaseName)) {
return;
}
if (strncmp(name, kLogTestCaseName, name_size)) {
return;
}
if (result->passed != 1 || result->failed != 2 || result->skipped != 3) {
return;
}
log_test_case_called = true;
}
void TestStart() override { start_called = true; }
void TestFinished() override { finished_called = true; }
bool log_message_called = false;
bool log_test_case_called = false;
bool start_called = false;
bool finished_called = false;
};
TEST(CoordinatorTestCase, TestOutput) {
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
devmgr::Coordinator coordinator(DefaultConfig(loop.dispatcher(), nullptr));
ASSERT_NO_FATAL_FAILURES(InitializeCoordinator(&coordinator));
// Add the device.
zx::channel local, remote;
zx_status_t status = zx::channel::create(0, &local, &remote);
ASSERT_OK(status);
fbl::RefPtr<devmgr::Device> device;
status = coordinator.AddDevice(coordinator.test_device(), std::move(local),
nullptr /* props_data */, 0 /* props_count */, "mock-device",
ZX_PROTOCOL_TEST, nullptr /* driver_path */, nullptr /* args */,
false /* invisible */, zx::channel() /* client_remote */, &device);
ASSERT_OK(status);
ASSERT_EQ(1, coordinator.devices().size_slow());
fbl::String driver_name;
auto test_reporter_ = std::make_unique<TestDriverTestReporter>(driver_name);
auto* test_reporter = test_reporter_.get();
device->test_reporter = std::move(test_reporter_);
// Add the driver.
devmgr::load_driver(kDriverPath,
fit::bind_member(&coordinator, &devmgr::Coordinator::DriverAdded));
loop.RunUntilIdle();
ASSERT_FALSE(coordinator.drivers().is_empty());
// Bind the device to a fake devhost.
fbl::RefPtr<devmgr::Device> dev = fbl::RefPtr(&coordinator.devices().front());
devmgr::Devhost host;
host.AddRef(); // refcount starts at zero, so bump it up to keep us from being cleaned up
dev->set_host(&host);
status = coordinator.BindDevice(dev, kDriverPath, true /* new device */);
ASSERT_OK(status);
// Check the BindDriver request.
zx::channel test_device, test_coordinator;
zx::channel::create(0, &test_device, &test_coordinator);
ASSERT_NO_FATAL_FAILURES(BindDriverTestOutput(remote, std::move(test_coordinator)));
loop.RunUntilIdle();
ASSERT_NO_FATAL_FAILURES(WriteTestLog(test_device));
ASSERT_NO_FATAL_FAILURES(WriteTestCase(test_device));
loop.RunUntilIdle();
// The test logging handlers should not be called until the test is finished and the channel is
// closed.
EXPECT_FALSE(test_reporter->start_called);
EXPECT_FALSE(test_reporter->log_message_called);
EXPECT_FALSE(test_reporter->log_test_case_called);
EXPECT_FALSE(test_reporter->finished_called);
test_device.reset();
loop.RunUntilIdle();
EXPECT_TRUE(test_reporter->start_called);
EXPECT_TRUE(test_reporter->log_message_called);
EXPECT_TRUE(test_reporter->log_test_case_called);
EXPECT_TRUE(test_reporter->finished_called);
// Reset the fake devhost connection.
dev->set_host(nullptr);
remote.reset();
loop.RunUntilIdle();
}
// Reads a CreateDevice from remote, checks expectations, and sends a ZX_OK
// response.
void CheckCreateDeviceReceived(const zx::channel& remote, const char* expected_driver,
zx::channel* device_remote) {
// Read the CreateDevice request.
FIDL_ALIGNDECL uint8_t bytes[ZX_CHANNEL_MAX_MSG_BYTES];
zx_handle_t handles[ZX_CHANNEL_MAX_MSG_HANDLES];
uint32_t actual_bytes;
uint32_t actual_handles;
zx_status_t status = remote.read(0, bytes, handles, sizeof(bytes), fbl::count_of(handles),
&actual_bytes, &actual_handles);
ASSERT_OK(status);
ASSERT_LT(0, actual_bytes);
ASSERT_EQ(3, actual_handles);
*device_remote = zx::channel(handles[0]);
status = zx_handle_close(handles[1]);
ASSERT_OK(status);
// Validate the CreateDevice request.
auto hdr = reinterpret_cast<fidl_message_header_t*>(bytes);
ASSERT_EQ(fuchsia_device_manager_DevhostControllerCreateDeviceOrdinal, hdr->ordinal);
status = fidl_decode(&fuchsia_device_manager_DevhostControllerCreateDeviceRequestTable, bytes,
actual_bytes, handles, actual_handles, nullptr);
ASSERT_OK(status);
auto req = reinterpret_cast<fuchsia_device_manager_DevhostControllerCreateDeviceRequest*>(bytes);
ASSERT_EQ(req->driver_path.size, strlen(expected_driver));
ASSERT_BYTES_EQ(reinterpret_cast<const uint8_t*>(expected_driver),
reinterpret_cast<const uint8_t*>(req->driver_path.data), req->driver_path.size,
"");
}
// Reads a Suspend request from remote and checks that it is for the expected
// flags, without sending a response. |SendSuspendReply| can be used to send the desired response.
void CheckSuspendReceived(const zx::channel& remote, uint32_t expected_flags) {
// Read the Suspend request.
FIDL_ALIGNDECL uint8_t bytes[ZX_CHANNEL_MAX_MSG_BYTES];
zx_handle_t handles[ZX_CHANNEL_MAX_MSG_HANDLES];
uint32_t actual_bytes;
uint32_t actual_handles;
zx_status_t status = remote.read(0, bytes, handles, sizeof(bytes), fbl::count_of(handles),
&actual_bytes, &actual_handles);
ASSERT_OK(status);
ASSERT_LT(0, actual_bytes);
ASSERT_EQ(0, actual_handles);
// Validate the Suspend request.
auto hdr = reinterpret_cast<fidl_message_header_t*>(bytes);
ASSERT_EQ(fuchsia_device_manager_DeviceControllerSuspendOrdinal, hdr->ordinal);
status = fidl_decode(&fuchsia_device_manager_DeviceControllerSuspendRequestTable, bytes,
actual_bytes, handles, actual_handles, nullptr);
ASSERT_OK(status);
auto req = reinterpret_cast<fuchsia_device_manager_DeviceControllerSuspendRequest*>(bytes);
ASSERT_EQ(req->flags, expected_flags);
}
// Sends a response with the given return_status. This can be used to reply to a
// request received by |CheckSuspendReceived|.
void SendSuspendReply(const zx::channel& remote, zx_status_t return_status) {
FIDL_ALIGNDECL uint8_t bytes[ZX_CHANNEL_MAX_MSG_BYTES];
zx_handle_t handles[ZX_CHANNEL_MAX_MSG_HANDLES];
uint32_t actual_handles;
// Write the Suspend response.
memset(bytes, 0, sizeof(bytes));
auto resp = reinterpret_cast<fuchsia_device_manager_DeviceControllerSuspendResponse*>(bytes);
resp->hdr.ordinal = fuchsia_device_manager_DeviceControllerSuspendOrdinal;
resp->status = return_status;
zx_status_t status =
fidl_encode(&fuchsia_device_manager_DeviceControllerSuspendResponseTable, bytes,
sizeof(*resp), handles, fbl::count_of(handles), &actual_handles, nullptr);
ASSERT_OK(status);
ASSERT_EQ(0, actual_handles);
status = remote.write(0, bytes, sizeof(*resp), nullptr, 0);
ASSERT_OK(status);
}
// Reads a Suspend request from remote, checks that it is for the expected
// flags, and then sends the given response.
void CheckSuspendReceived(const zx::channel& remote, uint32_t expected_flags,
zx_status_t return_status) {
CheckSuspendReceived(remote, expected_flags);
SendSuspendReply(remote, return_status);
}
// Reads a Resume request from remote and checks that it is for the expected
// target state, without sending a response. |SendResumeReply| can be used to send the desired
// response.
void CheckResumeReceived(const zx::channel& remote, SystemPowerState target_state) {
// Read the Resume request.
FIDL_ALIGNDECL uint8_t bytes[ZX_CHANNEL_MAX_MSG_BYTES];
zx_handle_t handles[ZX_CHANNEL_MAX_MSG_HANDLES];
uint32_t actual_bytes;
uint32_t actual_handles;
zx_status_t status = remote.read(0, bytes, handles, sizeof(bytes), fbl::count_of(handles),
&actual_bytes, &actual_handles);
ASSERT_OK(status);
ASSERT_LT(0, actual_bytes);
ASSERT_EQ(0, actual_handles);
// Validate the Resume request.
auto hdr = reinterpret_cast<fidl_message_header_t*>(bytes);
ASSERT_EQ(fuchsia_device_manager_DeviceControllerResumeOrdinal, hdr->ordinal);
status = fidl_decode(&fuchsia_device_manager_DeviceControllerResumeRequestTable, bytes,
actual_bytes, handles, actual_handles, nullptr);
ASSERT_OK(status);
auto req = reinterpret_cast<fuchsia_device_manager_DeviceControllerResumeRequest*>(bytes);
ASSERT_EQ(static_cast<SystemPowerState>(req->target_system_state), target_state);
}
// Sends a response with the given return_status. This can be used to reply to a
// request received by |CheckResumeReceived|.
void SendResumeReply(const zx::channel& remote, zx_status_t return_status) {
FIDL_ALIGNDECL uint8_t bytes[ZX_CHANNEL_MAX_MSG_BYTES];
zx_handle_t handles[ZX_CHANNEL_MAX_MSG_HANDLES];
uint32_t actual_handles;
// Write the Resume response.
memset(bytes, 0, sizeof(bytes));
auto resp = reinterpret_cast<fuchsia_device_manager_DeviceControllerResumeResponse*>(bytes);
resp->hdr.ordinal = fuchsia_device_manager_DeviceControllerResumeOrdinal;
resp->status = return_status;
zx_status_t status =
fidl_encode(&fuchsia_device_manager_DeviceControllerResumeResponseTable, bytes, sizeof(*resp),
handles, fbl::count_of(handles), &actual_handles, nullptr);
ASSERT_OK(status);
ASSERT_EQ(0, actual_handles);
status = remote.write(0, bytes, sizeof(*resp), nullptr, 0);
ASSERT_OK(status);
}
// Reads a Resume request from remote, checks that it is for the expected
// target state, and then sends the given response.
void CheckResumeReceived(const zx::channel& remote, SystemPowerState target_state,
zx_status_t return_status) {
CheckResumeReceived(remote, target_state);
SendResumeReply(remote, return_status);
}
// Reads a CreateCompositeDevice from remote, checks expectations, and sends
// a ZX_OK response.
void CheckCreateCompositeDeviceReceived(const zx::channel& remote, const char* expected_name,
size_t expected_components_count,
zx::channel* composite_remote) {
// Read the CreateCompositeDevice request.
FIDL_ALIGNDECL uint8_t bytes[ZX_CHANNEL_MAX_MSG_BYTES];
zx_handle_t handles[ZX_CHANNEL_MAX_MSG_HANDLES];
uint32_t actual_bytes;
uint32_t actual_handles;
zx_status_t status = remote.read(0, bytes, handles, sizeof(bytes), fbl::count_of(handles),
&actual_bytes, &actual_handles);
ASSERT_OK(status);
ASSERT_LT(0, actual_bytes);
ASSERT_EQ(1, actual_handles);
composite_remote->reset(handles[0]);
// Validate the CreateCompositeDevice request.
auto hdr = reinterpret_cast<fidl_message_header_t*>(bytes);
ASSERT_EQ(fuchsia_device_manager_DevhostControllerCreateCompositeDeviceOrdinal, hdr->ordinal);
status = fidl_decode(&fuchsia_device_manager_DevhostControllerCreateCompositeDeviceRequestTable,
bytes, actual_bytes, handles, actual_handles, nullptr);
ASSERT_OK(status);
auto req =
reinterpret_cast<fuchsia_device_manager_DevhostControllerCreateCompositeDeviceRequest*>(
bytes);
ASSERT_EQ(req->name.size, strlen(expected_name));
ASSERT_BYTES_EQ(reinterpret_cast<const uint8_t*>(expected_name),
reinterpret_cast<const uint8_t*>(req->name.data), req->name.size, "");
ASSERT_EQ(expected_components_count, req->components.count);
// Write the CreateCompositeDevice response.
memset(bytes, 0, sizeof(bytes));
auto resp =
reinterpret_cast<fuchsia_device_manager_DevhostControllerCreateCompositeDeviceResponse*>(
bytes);
resp->hdr.ordinal = fuchsia_device_manager_DevhostControllerCreateCompositeDeviceOrdinal;
resp->status = ZX_OK;
status =
fidl_encode(&fuchsia_device_manager_DevhostControllerCreateCompositeDeviceResponseTable,
bytes, sizeof(*resp), handles, fbl::count_of(handles), &actual_handles, nullptr);
ASSERT_OK(status);
ASSERT_EQ(0, actual_handles);
status = remote.write(0, bytes, sizeof(*resp), nullptr, 0);
ASSERT_OK(status);
}
// Helper for BindComposite for issuing an AddComposite for a composite with the
// given components. It's assumed that these components are children of
// the platform_bus and have the given protocol_id
void BindCompositeDefineComposite(const fbl::RefPtr<devmgr::Device>& platform_bus,
const uint32_t* protocol_ids, size_t component_count,
const zx_device_prop_t* props, size_t props_count,
const char* name, zx_status_t expected_status = ZX_OK) {
std::vector<llcpp::fuchsia::device::manager::DeviceComponent> components = {};
for (size_t i = 0; i < component_count; ++i) {
// Define a union type to avoid violating the strict aliasing rule.
zx_bind_inst_t always = BI_MATCH();
zx_bind_inst_t protocol = BI_MATCH_IF(EQ, BIND_PROTOCOL, protocol_ids[i]);
llcpp::fuchsia::device::manager::DeviceComponent component; // = &components[i];
component.parts_count = 2;
component.parts[0].match_program_count = 1;
component.parts[0].match_program[0] = ::llcpp::fuchsia::device::manager::BindInstruction{
.op = always.op,
.arg = always.arg,
};
component.parts[1].match_program_count = 1;
component.parts[1].match_program[0] = ::llcpp::fuchsia::device::manager::BindInstruction{
.op = protocol.op,
.arg = protocol.arg,
};
components.push_back(component);
}
auto prop_view = ::fidl::VectorView<uint64_t>(
reinterpret_cast<uint64_t*>(const_cast<zx_device_prop_t*>(props)), props_count);
devmgr::Coordinator* coordinator = platform_bus->coordinator;
ASSERT_EQ(
coordinator->AddCompositeDevice(platform_bus, name, prop_view, ::fidl::VectorView(components),
0 /* coresident index */),
expected_status);
}
struct DeviceState {
// The representation in the coordinator of the device
fbl::RefPtr<devmgr::Device> device;
// The remote end of the channel that the coordinator is talking to
zx::channel remote;
};
class MultipleDeviceTestCase : public zxtest::Test {
public:
~MultipleDeviceTestCase() override = default;
async::Loop* coordinator_loop() { return &coordinator_loop_; }
bool coordinator_loop_thread_running() { return coordinator_loop_thread_running_; }
void set_coordinator_loop_thread_running(bool value) { coordinator_loop_thread_running_ = value; }
devmgr::Coordinator* coordinator() { return &coordinator_; }
devmgr::Devhost* devhost() { return &devhost_; }
const zx::channel& devhost_remote() { return devhost_remote_; }
const fbl::RefPtr<devmgr::Device>& platform_bus() const { return platform_bus_.device; }
const zx::channel& platform_bus_remote() const { return platform_bus_.remote; }
DeviceState* device(size_t index) const { return &devices_[index]; }
void AddDevice(const fbl::RefPtr<devmgr::Device>& parent, const char* name, uint32_t protocol_id,
fbl::String driver, size_t* device_index);
void RemoveDevice(size_t device_index);
bool DeviceHasPendingMessages(size_t device_index);
bool DeviceHasPendingMessages(const zx::channel& remote);
void DoSuspend(uint32_t flags);
void DoSuspend(uint32_t flags, fit::function<void(uint32_t)> suspend_cb);
void DoResume(SystemPowerState target_state);
void DoResume(SystemPowerState target_state, fit::function<void(SystemPowerState)> resume_cb);
void CheckUnbindReceived(const zx::channel& remote);
void SendUnbindReply(const zx::channel& remote);
void CheckUnbindReceivedAndReply(const zx::channel& remote);
void CheckRemoveReceived(const zx::channel& remote);
void SendRemoveReply(const zx::channel& remote);
void CheckRemoveReceivedAndReply(const zx::channel& remote);
protected:
void SetUp() override {
ASSERT_NO_FATAL_FAILURES(InitializeCoordinator(&coordinator_));
// refcount starts at zero, so bump it up to keep us from being cleaned up
devhost_.AddRef();
{
zx::channel local;
zx_status_t status = zx::channel::create(0, &local, &devhost_remote_);
ASSERT_OK(status);
devhost_.set_hrpc(local.release());
}
// Set up the sys device proxy, inside of the devhost
ASSERT_OK(coordinator_.PrepareProxy(coordinator_.sys_device(), &devhost_));
coordinator_loop_.RunUntilIdle();
ASSERT_NO_FATAL_FAILURES(
CheckCreateDeviceReceived(devhost_remote_, kSystemDriverPath, &sys_proxy_remote_));
coordinator_loop_.RunUntilIdle();
// Create a child of the sys_device (an equivalent of the platform bus)
{
zx::channel local;
zx_status_t status = zx::channel::create(0, &local, &platform_bus_.remote);
ASSERT_OK(status);
status = coordinator_.AddDevice(
coordinator_.sys_device()->proxy(), std::move(local), nullptr /* props_data */,
0 /* props_count */, "platform-bus", 0, nullptr /* driver_path */, nullptr /* args */,
false /* invisible */, zx::channel() /* client_remote */, &platform_bus_.device);
ASSERT_OK(status);
coordinator_loop_.RunUntilIdle();
}
}
void TearDown() override {
if (!coordinator_loop_thread_running_) {
coordinator_loop_.RunUntilIdle();
}
// Remove the devices in the opposite order that we added them
while (!devices_.is_empty()) {
devices_.pop_back();
if (!coordinator_loop_thread_running_) {
coordinator_loop_.RunUntilIdle();
}
}
platform_bus_.device.reset();
if (!coordinator_loop_thread_running_) {
coordinator_loop_.RunUntilIdle();
}
devhost_.devices().clear();
}
// The fake devhost that the platform bus is put into
devmgr::Devhost devhost_;
// The remote end of the channel that the coordinator uses to talk to the
// devhost
zx::channel devhost_remote_;
// The remote end of the channel that the coordinator uses to talk to the
// sys device proxy
zx::channel sys_proxy_remote_;
// The device object representing the platform bus driver (child of the
// sys proxy)
DeviceState platform_bus_;
// These should be listed after devhost/sys_proxy as it needs to be
// destroyed before them.
async::Loop coordinator_loop_{&kAsyncLoopConfigNoAttachToCurrentThread};
bool coordinator_loop_thread_running_ = false;
devmgr::BootArgs boot_args_;
devmgr::Coordinator coordinator_{DefaultConfig(coordinator_loop_.dispatcher(), &boot_args_)};
// A list of all devices that were added during this test, and their
// channels. These exist to keep them alive until the test is over.
fbl::Vector<DeviceState> devices_;
};
void MultipleDeviceTestCase::AddDevice(const fbl::RefPtr<devmgr::Device>& parent, const char* name,
uint32_t protocol_id, fbl::String driver, size_t* index) {
DeviceState state;
zx::channel local;
zx_status_t status = zx::channel::create(0, &local, &state.remote);
ASSERT_OK(status);
status = coordinator_.AddDevice(
parent, std::move(local), nullptr /* props_data */, 0 /* props_count */, name, protocol_id,
driver.data() /* driver_path */, nullptr /* args */, false /* invisible */,
zx::channel() /* client_remote */, &state.device);
state.device->flags |= DEV_CTX_ALLOW_MULTI_COMPOSITE;
ASSERT_OK(status);
coordinator_loop_.RunUntilIdle();
devices_.push_back(std::move(state));
*index = devices_.size() - 1;
}
void MultipleDeviceTestCase::RemoveDevice(size_t device_index) {
auto& state = devices_[device_index];
ASSERT_OK(coordinator_.RemoveDevice(state.device, false));
state.device.reset();
state.remote.reset();
coordinator_loop_.RunUntilIdle();
}
bool MultipleDeviceTestCase::DeviceHasPendingMessages(const zx::channel& remote) {
return remote.wait_one(ZX_CHANNEL_READABLE, zx::time(0), nullptr) == ZX_OK;
}
bool MultipleDeviceTestCase::DeviceHasPendingMessages(size_t device_index) {
return DeviceHasPendingMessages(devices_[device_index].remote);
}
void MultipleDeviceTestCase::DoSuspend(uint32_t flags,
fit::function<void(uint32_t flags)> suspend_cb) {
const bool vfs_exit_expected = (flags != DEVICE_SUSPEND_FLAG_SUSPEND_RAM);
if (vfs_exit_expected) {
zx::unowned_event event(coordinator()->fshost_event());
auto thrd_func = [](void* ctx) -> int {
zx::unowned_event event(*static_cast<zx::unowned_event*>(ctx));
if (event->wait_one(FSHOST_SIGNAL_EXIT, zx::time::infinite(), nullptr) != ZX_OK) {
return false;
}
if (event->signal(0, FSHOST_SIGNAL_EXIT_DONE) != ZX_OK) {
return false;
}
return true;
};
thrd_t fshost_thrd;
ASSERT_EQ(thrd_create(&fshost_thrd, thrd_func, &event), thrd_success);
suspend_cb(flags);
if (!coordinator_loop_thread_running()) {
coordinator_loop()->RunUntilIdle();
}
int thread_status;
ASSERT_EQ(thrd_join(fshost_thrd, &thread_status), thrd_success);
ASSERT_TRUE(thread_status);
// Make sure that vfs_exit() happened.
ASSERT_OK(
coordinator()->fshost_event().wait_one(FSHOST_SIGNAL_EXIT_DONE, zx::time(0), nullptr));
} else {
suspend_cb(flags);
if (!coordinator_loop_thread_running()) {
coordinator_loop()->RunUntilIdle();
}
// Make sure that vfs_exit() didn't happen.
ASSERT_EQ(coordinator()->fshost_event().wait_one(FSHOST_SIGNAL_EXIT | FSHOST_SIGNAL_EXIT_DONE,
zx::time(0), nullptr),
ZX_ERR_TIMED_OUT);
}
}
void MultipleDeviceTestCase::DoSuspend(uint32_t flags) {
DoSuspend(flags, [this](uint32_t flags) { coordinator()->Suspend(flags); });
}
void MultipleDeviceTestCase::DoResume(
SystemPowerState target_state, fit::function<void(SystemPowerState target_state)> resume_cb) {
resume_cb(target_state);
if (!coordinator_loop_thread_running()) {
coordinator_loop()->RunUntilIdle();
}
}
void MultipleDeviceTestCase::DoResume(SystemPowerState target_state) {
DoResume(target_state,
[this](SystemPowerState target_state) { coordinator()->Resume(target_state); });
}
TEST_F(MultipleDeviceTestCase, RemoveDeadDevice) {
size_t index;
ASSERT_NO_FATAL_FAILURES(AddDevice(platform_bus(), "device", 0 /* protocol id */, "", &index));
auto& state = devices_[index];
ASSERT_OK(coordinator_.RemoveDevice(state.device, false));
ASSERT_FALSE(state.device->is_bindable());
ASSERT_NOT_OK(coordinator_.RemoveDevice(state.device, false), "device should already be dead");
}
// Reads the request from |remote| and verifies whether it matches the expected Unbind request.
// |SendUnbindReply| can be used to send the desired response.
void MultipleDeviceTestCase::CheckUnbindReceived(const zx::channel& remote) {
// Read the unbind request.
FIDL_ALIGNDECL uint8_t bytes[ZX_CHANNEL_MAX_MSG_BYTES];
zx_handle_t handles[ZX_CHANNEL_MAX_MSG_HANDLES];
uint32_t actual_bytes;
uint32_t actual_handles;
zx_status_t status = remote.read(0, bytes, handles, sizeof(bytes), fbl::count_of(handles),
&actual_bytes, &actual_handles);
ASSERT_OK(status);
ASSERT_LT(0, actual_bytes);
ASSERT_EQ(0, actual_handles);
// Validate the unbind request.
auto hdr = reinterpret_cast<fidl_message_header_t*>(bytes);
ASSERT_EQ(fuchsia_device_manager_DeviceControllerUnbindOrdinal, hdr->ordinal);
status = fidl_decode(&fuchsia_device_manager_DeviceControllerUnbindRequestTable, bytes,
actual_bytes, handles, actual_handles, nullptr);
ASSERT_OK(status);
}
// Sends a response with the given return_status. This can be used to reply to a
// request received by |CheckUnbindReceived|.
void MultipleDeviceTestCase::SendUnbindReply(const zx::channel& remote) {
FIDL_ALIGNDECL uint8_t bytes[ZX_CHANNEL_MAX_MSG_BYTES];
zx_handle_t handles[ZX_CHANNEL_MAX_MSG_HANDLES];
uint32_t actual_handles;
// Write the UnbindDone message.
memset(bytes, 0, sizeof(bytes));
auto req = reinterpret_cast<fuchsia_device_manager_CoordinatorUnbindDoneRequest*>(bytes);
req->hdr.ordinal = fuchsia_device_manager_CoordinatorUnbindDoneOrdinal;
req->hdr.txid = 1;
zx_status_t status =
fidl_encode(&fuchsia_device_manager_CoordinatorUnbindDoneRequestTable, bytes, sizeof(*req),
handles, fbl::count_of(handles), &actual_handles, nullptr);
ASSERT_OK(status);
ASSERT_EQ(0, actual_handles);
status = remote.write(0, bytes, sizeof(*req), nullptr, 0);
ASSERT_OK(status);
coordinator_loop()->RunUntilIdle();
// Verify the UnbindDone response.
uint32_t actual_bytes;
status = remote.read(0, bytes, handles, sizeof(bytes), fbl::count_of(handles), &actual_bytes,
&actual_handles);
ASSERT_OK(status);
ASSERT_LT(0, actual_bytes);
ASSERT_EQ(0, actual_handles);
fidl::EncodedMessage<::llcpp::fuchsia::device::manager::Coordinator::UnbindDoneResponse> encoded(
fidl::BytePart(bytes, actual_bytes, actual_bytes));
auto decode_result = fidl::Decode(std::move(encoded));
ASSERT_OK(decode_result.status);
const ::llcpp::fuchsia::device::manager::Coordinator::UnbindDoneResponse& msg =
*decode_result.message.message();
ASSERT_FALSE(msg.result.is_err());
}
void MultipleDeviceTestCase::CheckUnbindReceivedAndReply(const zx::channel& remote) {
CheckUnbindReceived(remote);
SendUnbindReply(remote);
}
// Reads the request from |remote| and verifies whether it matches the expected
// CompleteRemoval request.
// |SendRemoveReply| can be used to send the desired response.
void MultipleDeviceTestCase::CheckRemoveReceived(const zx::channel& remote) {
// Read the remove request.
FIDL_ALIGNDECL uint8_t bytes[ZX_CHANNEL_MAX_MSG_BYTES];
zx_handle_t handles[ZX_CHANNEL_MAX_MSG_HANDLES];
uint32_t actual_bytes;
uint32_t actual_handles;
zx_status_t status = remote.read(0, bytes, handles, sizeof(bytes), fbl::count_of(handles),
&actual_bytes, &actual_handles);
ASSERT_OK(status);
ASSERT_LT(0, actual_bytes);
ASSERT_EQ(0, actual_handles);
// Validate the remove request.
auto hdr = reinterpret_cast<fidl_message_header_t*>(bytes);
ASSERT_EQ(fuchsia_device_manager_DeviceControllerCompleteRemovalOrdinal, hdr->ordinal);
status = fidl_decode(&fuchsia_device_manager_DeviceControllerCompleteRemovalRequestTable, bytes,
actual_bytes, handles, actual_handles, nullptr);
ASSERT_OK(status);
}
// Sends a response with the given return_status. This can be used to reply to a
// request received by |CheckRemoveReceived|.
void MultipleDeviceTestCase::SendRemoveReply(const zx::channel& remote) {
FIDL_ALIGNDECL uint8_t bytes[ZX_CHANNEL_MAX_MSG_BYTES];
zx_handle_t handles[ZX_CHANNEL_MAX_MSG_HANDLES];
uint32_t actual_handles;
// Write the RemoveDone message.
memset(bytes, 0, sizeof(bytes));
auto req = reinterpret_cast<fuchsia_device_manager_CoordinatorRemoveDoneRequest*>(bytes);
req->hdr.ordinal = fuchsia_device_manager_CoordinatorRemoveDoneOrdinal;
req->hdr.txid = 1;
zx_status_t status =
fidl_encode(&fuchsia_device_manager_CoordinatorRemoveDoneRequestTable, bytes, sizeof(*req),
handles, fbl::count_of(handles), &actual_handles, nullptr);
ASSERT_OK(status);
ASSERT_EQ(0, actual_handles);
status = remote.write(0, bytes, sizeof(*req), nullptr, 0);
ASSERT_OK(status);
coordinator_loop()->RunUntilIdle();
// Verify the RemoveDone response.
uint32_t actual_bytes;
status = remote.read(0, bytes, handles, sizeof(bytes), fbl::count_of(handles), &actual_bytes,
&actual_handles);
ASSERT_OK(status);
ASSERT_LT(0, actual_bytes);
ASSERT_EQ(0, actual_handles);
fidl::EncodedMessage<::llcpp::fuchsia::device::manager::Coordinator::RemoveDoneResponse> encoded(
fidl::BytePart(bytes, actual_bytes, actual_bytes));
auto decode_result = fidl::Decode(std::move(encoded));
ASSERT_OK(decode_result.status);
const ::llcpp::fuchsia::device::manager::Coordinator::RemoveDoneResponse& msg =
*decode_result.message.message();
ASSERT_FALSE(msg.result.is_err());
}
void MultipleDeviceTestCase::CheckRemoveReceivedAndReply(const zx::channel& remote) {
CheckRemoveReceived(remote);
SendRemoveReply(remote);
}
class UnbindTestCase : public MultipleDeviceTestCase {
public:
// The expected action to receive. This is required as device_remove does not call unbind
// on the initial device.
enum class Action {
kNone,
kRemove,
kUnbind,
};
struct DeviceDesc {
// Index into the device desc array below. UINT32_MAX = platform_bus()
const size_t parent_desc_index;
const char* const name;
Action want_action = Action::kNone;
// If non-null, will be run after receiving the Remove / Unbind request,
// but before replying.
std::function<void()> unbind_op = nullptr;
// index for use with device()
size_t index = 0;
bool removed = false;
bool unbound = false;
};
// |target_device_index| is the index of the device in the |devices| array to
// schedule removal of.
// If |unbind_children_only| is true, it will skip removal of the target device.
void UnbindTest(DeviceDesc devices[], size_t num_devices, size_t target_device_index,
bool unbind_children_only = false, bool unbind_target_device = false);
};
TEST_F(UnbindTestCase, UnbindLeaf) {
DeviceDesc devices[] = {
{UINT32_MAX, "root_child1"}, {UINT32_MAX, "root_child2"},
{0, "root_child1_1"}, {0, "root_child1_2"},
{2, "root_child1_1_1"}, {1, "root_child2_1", Action::kRemove},
};
// Only remove root_child2_1.
size_t index_to_remove = 5;
ASSERT_NO_FATAL_FAILURES(UnbindTest(devices, fbl::count_of(devices), index_to_remove));
}
TEST_F(UnbindTestCase, UnbindMultipleChildren) {
DeviceDesc devices[] = {
{UINT32_MAX, "root_child1", Action::kRemove}, {UINT32_MAX, "root_child2"},
{0, "root_child1_1", Action::kUnbind}, {0, "root_child1_2", Action::kUnbind},
{2, "root_child1_1_1", Action::kUnbind}, {1, "root_child2_1"},
};
// Remove root_child1 and all its children.
size_t index_to_remove = 0;
ASSERT_NO_FATAL_FAILURES(UnbindTest(devices, fbl::count_of(devices), index_to_remove));
}
// This tests the removal of a child device in unbind. e.g.
//
// void MyDevice::Unbind() {
// child->DdkRemove();
// DdkRemove();
// }
TEST_F(UnbindTestCase, UnbindWithRemoveOp) {
// Remove root_child1 and all its children.
size_t index_to_remove = 0;
DeviceDesc devices[] = {
{UINT32_MAX, "root_child1", Action::kRemove},
{0, "root_child1_1", Action::kUnbind},
{1, "root_child1_1_1", Action::kRemove},
{2, "root_child1_1_1_1", Action::kUnbind},
};
// We will schedule child device 1_1_1's removal in device 1_1's unbind hook.
auto unbind_op = [&] {
ASSERT_NO_FATAL_FAILURES(
coordinator_.ScheduleDevhostRequestedRemove(device(devices[2].index)->device));
};
devices[1].unbind_op = unbind_op;
ASSERT_NO_FATAL_FAILURES(UnbindTest(devices, fbl::count_of(devices), index_to_remove));
}
TEST_F(UnbindTestCase, UnbindChildrenOnly) {
DeviceDesc devices[] = {
{UINT32_MAX, "root_child1"}, // Unbinding children of this device.
{UINT32_MAX, "root_child2"},
{0, "root_child1_1", Action::kUnbind},
{0, "root_child1_2", Action::kUnbind},
{2, "root_child1_1_1", Action::kUnbind},
{1, "root_child2_1"},
};
// Remove the children of root_child1.
size_t target_device_index = 0;
ASSERT_NO_FATAL_FAILURES(UnbindTest(devices, fbl::count_of(devices), target_device_index,
true /* unbind_children_only */));
}
TEST_F(UnbindTestCase, UnbindSelf) {
DeviceDesc devices[] = {
{UINT32_MAX, "root_child1", Action::kUnbind}, // Require unbinding of the target device.
{UINT32_MAX, "root_child2"},
{0, "root_child1_1", Action::kUnbind},
{0, "root_child1_2", Action::kUnbind},
{2, "root_child1_1_1", Action::kUnbind},
{1, "root_child2_1"},
};
// Unbind root_child1.
size_t index_to_remove = 0;
ASSERT_NO_FATAL_FAILURES(UnbindTest(devices, fbl::count_of(devices), index_to_remove,
false /* unbind_children_only */,
true /* unbind_target_device */));
}
void UnbindTestCase::UnbindTest(DeviceDesc devices[], size_t num_devices,
size_t target_device_index, bool unbind_children_only,
bool unbind_target_device) {
size_t num_to_remove = 0;
size_t num_to_unbind = 0;
for (size_t i = 0; i < num_devices; i++) {
auto& desc = devices[i];
fbl::RefPtr<devmgr::Device> parent;
if (desc.parent_desc_index == UINT32_MAX) {
parent = platform_bus();
} else {
size_t index = devices[desc.parent_desc_index].index;
parent = device(index)->device;
}
ASSERT_NO_FATAL_FAILURES(AddDevice(parent, desc.name, 0 /* protocol id */, "", &desc.index));
if (desc.want_action == Action::kUnbind) {
num_to_unbind++;
num_to_remove++;
} else if (desc.want_action == Action::kRemove) {
num_to_remove++;
}
}
auto& desc = devices[target_device_index];
if (unbind_children_only) {
// Skip removal of the target device.
ASSERT_NO_FATAL_FAILURES(
coordinator_.ScheduleDevhostRequestedUnbindChildren(device(desc.index)->device));
} else {
ASSERT_NO_FATAL_FAILURES(coordinator_.ScheduleDevhostRequestedRemove(device(desc.index)->device,
unbind_target_device));
}
coordinator_loop()->RunUntilIdle();
while (num_to_unbind > 0) {
bool made_progress = false;
// Currently devices are unbound from the ancestor first.
// Always check from leaf device upwards, so we ensure no child
// is unbound before its parent.
// To avoid overflow, check the counter before it is decremented.
for (size_t i = num_devices; i-- > 0;) {
auto& desc = devices[i];
if (desc.unbound) {
continue;
}
if (!DeviceHasPendingMessages(desc.index)) {
continue;
}
ASSERT_NE(desc.want_action, Action::kNone);
if (desc.want_action == Action::kUnbind) {
ASSERT_NO_FATAL_FAILURES(CheckUnbindReceived(device(desc.index)->remote));
if (desc.unbind_op) {
desc.unbind_op();
}
ASSERT_NO_FATAL_FAILURES(SendUnbindReply(device(desc.index)->remote));
desc.unbound = true;
}
// Check if the parent is expected to have been unbound already.
if (desc.parent_desc_index != UINT32_MAX) {
auto parent_desc = devices[desc.parent_desc_index];
if (parent_desc.want_action == Action::kUnbind) {
ASSERT_TRUE(parent_desc.unbound);
}
}
--num_to_unbind;
made_progress = true;
}
// Make sure we're not stuck waiting
ASSERT_TRUE(made_progress);
coordinator_loop()->RunUntilIdle();
}
// Now check that we receive the removals in the expected order, leaf first.
while (num_to_remove > 0) {
bool made_progress = false;
for (size_t i = 0; i < num_devices; ++i) {
auto& desc = devices[i];
if (desc.removed) {
continue;
}
if (!DeviceHasPendingMessages(desc.index)) {
continue;
}
ASSERT_NE(desc.want_action, Action::kNone);
ASSERT_NO_FATAL_FAILURES(CheckRemoveReceivedAndReply(device(desc.index)->remote));
// Check that all our children have already been removed.
for (size_t j = 0; j < num_devices; ++j) {
auto& other_desc = devices[j];
if (other_desc.parent_desc_index == i) {
ASSERT_TRUE(other_desc.removed);
}
}
desc.removed = true;
--num_to_remove;
made_progress = true;
}
// Make sure we're not stuck waiting
ASSERT_TRUE(made_progress);
coordinator_loop()->RunUntilIdle();
}
for (size_t i = 0; i < num_devices; i++) {
auto& desc = devices[i];
ASSERT_NULL(device(desc.index)->device->GetActiveUnbind());
ASSERT_NULL(device(desc.index)->device->GetActiveRemove());
}
}
TEST_F(UnbindTestCase, UnbindSysDevice) {
// Since the sys device is immortal, only its children will be unbound.
ASSERT_NO_FATAL_FAILURES(coordinator_.ScheduleRemove(coordinator_.sys_device()));
coordinator_loop()->RunUntilIdle();
ASSERT_FALSE(DeviceHasPendingMessages(sys_proxy_remote_));
ASSERT_NO_FATAL_FAILURES(CheckUnbindReceivedAndReply(platform_bus_remote()));
coordinator_loop()->RunUntilIdle();
ASSERT_FALSE(DeviceHasPendingMessages(sys_proxy_remote_));
ASSERT_NO_FATAL_FAILURES(CheckRemoveReceivedAndReply(platform_bus_remote()));
coordinator_loop()->RunUntilIdle();
ASSERT_NO_FATAL_FAILURES(CheckRemoveReceivedAndReply(sys_proxy_remote_));
coordinator_loop()->RunUntilIdle();
ASSERT_NULL(coordinator_.sys_device()->GetActiveUnbind());
ASSERT_NULL(coordinator_.sys_device()->GetActiveRemove());
}
TEST_F(UnbindTestCase, UnbindWhileRemovingProxy) {
// The unbind task should complete immediately.
// The remove task is blocked on the platform bus remove task completing.
ASSERT_NO_FATAL_FAILURES(coordinator_.ScheduleRemove(coordinator_.sys_device()->proxy()));
// Since the sys device is immortal, only its children will be unbound.
ASSERT_NO_FATAL_FAILURES(coordinator_.ScheduleRemove(coordinator_.sys_device()));
coordinator_loop()->RunUntilIdle();
ASSERT_FALSE(DeviceHasPendingMessages(sys_proxy_remote_));
ASSERT_NO_FATAL_FAILURES(CheckUnbindReceivedAndReply(platform_bus_remote()));
coordinator_loop()->RunUntilIdle();
ASSERT_FALSE(DeviceHasPendingMessages(sys_proxy_remote_));
ASSERT_NO_FATAL_FAILURES(CheckRemoveReceivedAndReply(platform_bus_remote()));
coordinator_loop()->RunUntilIdle();
ASSERT_NO_FATAL_FAILURES(CheckRemoveReceivedAndReply(sys_proxy_remote_));
coordinator_loop()->RunUntilIdle();
ASSERT_NULL(coordinator_.sys_device()->GetActiveUnbind());
ASSERT_NULL(coordinator_.sys_device()->GetActiveRemove());
}
TEST_F(UnbindTestCase, NumRemovals) {
size_t child_index;
ASSERT_NO_FATAL_FAILURES(
AddDevice(platform_bus(), "child", 0 /* protocol id */, "", &child_index));
auto* child_device = device(child_index);
ASSERT_NO_FATAL_FAILURES(coordinator_.ScheduleRemove(child_device->device));
coordinator_loop()->RunUntilIdle();
ASSERT_NO_FATAL_FAILURES(CheckRemoveReceivedAndReply(child_device->remote));
coordinator_loop()->RunUntilIdle();
// Make sure the coordinator device does not detect the devhost's channel closing,
// otherwise it will try to remove an already dead device and we will get a log error.
child_device->remote.reset();
coordinator_loop()->RunUntilIdle();
ASSERT_EQ(child_device->device->num_removal_attempts(), 1);
}
TEST_F(UnbindTestCase, AddDuringParentUnbind) {
size_t parent_index;
ASSERT_NO_FATAL_FAILURES(
AddDevice(platform_bus(), "parent", 0 /* protocol id */, "", &parent_index));
auto* parent_device = device(parent_index);
ASSERT_NO_FATAL_FAILURES(coordinator_.ScheduleRemove(parent_device->device));
coordinator_loop()->RunUntilIdle();
// Don't reply to the request until we add the device.
ASSERT_NO_FATAL_FAILURES(CheckRemoveReceived(parent_device->remote));
// Adding a child device to an unbinding parent should fail.
fbl::RefPtr<devmgr::Device> child;
zx::channel local, remote;
zx_status_t status = zx::channel::create(0, &local, &remote);
ASSERT_OK(status);
status = coordinator_.AddDevice(parent_device->device, std::move(local), nullptr /* props_data */,
0 /* props_count */, "child", 0 /* protocol_id */,
nullptr /* driver_path */, nullptr /* args */,
false /* invisible */, zx::channel() /* client_remote */, &child);
ASSERT_NOT_OK(status);
coordinator_loop()->RunUntilIdle();
// Complete the original parent unbind.
ASSERT_NO_FATAL_FAILURES(SendRemoveReply(parent_device->remote));
coordinator_loop()->RunUntilIdle();
}
TEST_F(UnbindTestCase, TwoConcurrentRemovals) {
size_t parent_index;
ASSERT_NO_FATAL_FAILURES(
AddDevice(platform_bus(), "parent", 0 /* protocol id */, "", &parent_index));
auto* parent_device = device(parent_index);
size_t child_index;
ASSERT_NO_FATAL_FAILURES(
AddDevice(parent_device->device, "child", 0 /* protocol id */, "", &child_index));
auto* child_device = device(child_index);
// Schedule concurrent removals.
ASSERT_NO_FATAL_FAILURES(coordinator_.ScheduleRemove(parent_device->device));
ASSERT_NO_FATAL_FAILURES(coordinator_.ScheduleRemove(child_device->device));
coordinator_loop()->RunUntilIdle();
ASSERT_NO_FATAL_FAILURES(CheckRemoveReceivedAndReply(child_device->remote));
coordinator_loop()->RunUntilIdle();
ASSERT_NO_FATAL_FAILURES(CheckRemoveReceivedAndReply(parent_device->remote));
coordinator_loop()->RunUntilIdle();
}
TEST_F(UnbindTestCase, ManyConcurrentRemovals) {
size_t num_devices = 100;
size_t idx_map[num_devices];
for (size_t i = 0; i < num_devices; i++) {
auto parent = i == 0 ? platform_bus() : device(idx_map[i - 1])->device;
ASSERT_NO_FATAL_FAILURES(AddDevice(parent, "child", 0 /* protocol id */, "", &idx_map[i]));
}
for (size_t i = 0; i < num_devices; i++) {
ASSERT_NO_FATAL_FAILURES(coordinator_.ScheduleRemove(device(idx_map[i])->device));
}
coordinator_loop()->RunUntilIdle();
for (size_t i = 0; i < num_devices; i++) {
ASSERT_NO_FATAL_FAILURES(
CheckRemoveReceivedAndReply(device(idx_map[num_devices - i - 1])->remote));
coordinator_loop()->RunUntilIdle();
}
}
TEST_F(UnbindTestCase, ForcedRemovalDuringUnbind) {
size_t parent_index;
ASSERT_NO_FATAL_FAILURES(
AddDevice(platform_bus(), "parent", 0 /* protocol id */, "", &parent_index));
auto* parent_device = device(parent_index);
size_t child_index;
ASSERT_NO_FATAL_FAILURES(
AddDevice(parent_device->device, "child", 0 /* protocol id */, "", &child_index));
auto* child_device = device(child_index);
ASSERT_NO_FATAL_FAILURES(coordinator_.ScheduleRemove(parent_device->device));
coordinator_loop()->RunUntilIdle();
// Don't reply to the unbind request.
ASSERT_NO_FATAL_FAILURES(CheckUnbindReceived(child_device->remote));
// Close the parent device's channel to trigger a forced removal of the parent and child.
parent_device->remote = zx::channel();
coordinator_loop()->RunUntilIdle();
// Check that both devices are dead and have no pending unbind or remove tasks.
ASSERT_EQ(devmgr::Device::State::kDead, parent_device->device->state());
ASSERT_NULL(parent_device->device->GetActiveUnbind());
ASSERT_NULL(parent_device->device->GetActiveRemove());
ASSERT_EQ(devmgr::Device::State::kDead, child_device->device->state());
ASSERT_NULL(child_device->device->GetActiveUnbind());
ASSERT_NULL(parent_device->device->GetActiveRemove());
}
TEST_F(UnbindTestCase, ForcedRemovalDuringRemove) {
size_t parent_index;
ASSERT_NO_FATAL_FAILURES(
AddDevice(platform_bus(), "parent", 0 /* protocol id */, "", &parent_index));
auto* parent_device = device(parent_index);
size_t child_index;
ASSERT_NO_FATAL_FAILURES(
AddDevice(parent_device->device, "child", 0 /* protocol id */, "", &child_index));
auto* child_device = device(child_index);
ASSERT_NO_FATAL_FAILURES(coordinator_.ScheduleRemove(parent_device->device));
coordinator_loop()->RunUntilIdle();
ASSERT_NO_FATAL_FAILURES(CheckUnbindReceivedAndReply(child_device->remote));
coordinator_loop()->RunUntilIdle();
// Don't reply to the remove request.
ASSERT_NO_FATAL_FAILURES(CheckRemoveReceived(child_device->remote));
// Close the parent device's channel to trigger a forced removal of the parent and child.
parent_device->remote = zx::channel();
coordinator_loop()->RunUntilIdle();
// Check that both devices are dead and have no pending unbind or remove tasks.
ASSERT_EQ(devmgr::Device::State::kDead, parent_device->device->state());
ASSERT_NULL(parent_device->device->GetActiveUnbind());
ASSERT_NULL(parent_device->device->GetActiveRemove());
ASSERT_EQ(devmgr::Device::State::kDead, child_device->device->state());
ASSERT_NULL(child_device->device->GetActiveUnbind());
ASSERT_NULL(child_device->device->GetActiveRemove());
}
TEST_F(UnbindTestCase, RemoveParentWhileRemovingChild) {
size_t parent_index;
ASSERT_NO_FATAL_FAILURES(
AddDevice(platform_bus(), "parent", 0 /* protocol id */, "", &parent_index));
auto* parent_device = device(parent_index);
size_t child_index;
ASSERT_NO_FATAL_FAILURES(
AddDevice(parent_device->device, "child", 0 /* protocol id */, "", &child_index));
auto* child_device = device(child_index);
// Add a grandchild so that the child's remove task does not begin running after the
// child's unbind task completes.
size_t grandchild_index;
ASSERT_NO_FATAL_FAILURES(
AddDevice(child_device->device, "grandchild", 0 /* protocol id */, "", &grandchild_index));
auto* grandchild_device = device(grandchild_index);
// Start removing the child. Since we are not requesting an unbind
// the unbind task will complete immediately. The remove task will be waiting
// on the grandchild's remove to complete.
ASSERT_NO_FATAL_FAILURES(coordinator_.ScheduleRemove(child_device->device));
coordinator_loop()->RunUntilIdle();
// Start removing the parent.
ASSERT_NO_FATAL_FAILURES(coordinator_.ScheduleRemove(parent_device->device));
coordinator_loop()->RunUntilIdle();
ASSERT_NO_FATAL_FAILURES(CheckUnbindReceivedAndReply(grandchild_device->remote));
coordinator_loop()->RunUntilIdle();
ASSERT_NO_FATAL_FAILURES(CheckRemoveReceivedAndReply(grandchild_device->remote));
coordinator_loop()->RunUntilIdle();
ASSERT_NO_FATAL_FAILURES(CheckRemoveReceivedAndReply(child_device->remote));
coordinator_loop()->RunUntilIdle();
ASSERT_NO_FATAL_FAILURES(CheckRemoveReceivedAndReply(parent_device->remote));
coordinator_loop()->RunUntilIdle();
}
TEST_F(UnbindTestCase, RemoveParentAndChildSimultaneously) {
size_t parent_index;
ASSERT_NO_FATAL_FAILURES(
AddDevice(platform_bus(), "parent", 0 /* protocol id */, "", &parent_index));
auto* parent_device = device(parent_index);
size_t child_index;
ASSERT_NO_FATAL_FAILURES(
AddDevice(parent_device->device, "child", 0 /* protocol id */, "", &child_index));
auto* child_device = device(child_index);
ASSERT_NO_FATAL_FAILURES(
coordinator_.ScheduleDevhostRequestedRemove(parent_device->device, false /* do_unbind */));
coordinator_loop()->RunUntilIdle();
// At the same time, have the child try to remove itself.
ASSERT_NO_FATAL_FAILURES(
coordinator_.ScheduleDevhostRequestedRemove(child_device->device, false /* do_unbind */));
coordinator_loop()->RunUntilIdle();
// The child device will not reply, as it already called device_remove previously.
ASSERT_NO_FATAL_FAILURES(CheckUnbindReceived(child_device->remote));
coordinator_loop()->RunUntilIdle();
ASSERT_NO_FATAL_FAILURES(CheckRemoveReceivedAndReply(child_device->remote));
coordinator_loop()->RunUntilIdle();
ASSERT_NO_FATAL_FAILURES(CheckRemoveReceivedAndReply(parent_device->remote));
coordinator_loop()->RunUntilIdle();
}
class SuspendTestCase : public MultipleDeviceTestCase {
public:
void SuspendTest(uint32_t flags);
void StateTest(zx_status_t suspend_status, devmgr::Device::State want_device_state);
};
TEST_F(SuspendTestCase, Poweroff) {
ASSERT_NO_FATAL_FAILURES(SuspendTest(DEVICE_SUSPEND_FLAG_POWEROFF));
}
TEST_F(SuspendTestCase, Reboot) {
ASSERT_NO_FATAL_FAILURES(SuspendTest(DEVICE_SUSPEND_FLAG_REBOOT));
}
TEST_F(SuspendTestCase, RebootWithFlags) {
ASSERT_NO_FATAL_FAILURES(SuspendTest(DEVICE_SUSPEND_FLAG_REBOOT_BOOTLOADER));
}
TEST_F(SuspendTestCase, Mexec) { ASSERT_NO_FATAL_FAILURES(SuspendTest(DEVICE_SUSPEND_FLAG_MEXEC)); }
TEST_F(SuspendTestCase, SuspendToRam) {
ASSERT_NO_FATAL_FAILURES(SuspendTest(DEVICE_SUSPEND_FLAG_SUSPEND_RAM));
}
// Verify the suspend order is correct
void SuspendTestCase::SuspendTest(uint32_t flags) {
struct DeviceDesc {
// Index into the device desc array below. UINT32_MAX = platform_bus()
const size_t parent_desc_index;
const char* const name;
// index for use with device()
size_t index = 0;
bool suspended = false;
};
DeviceDesc devices[] = {
{UINT32_MAX, "root_child1"}, {UINT32_MAX, "root_child2"}, {0, "root_child1_1"},
{0, "root_child1_2"}, {2, "root_child1_1_1"}, {1, "root_child2_1"},
};
for (auto& desc : devices) {
fbl::RefPtr<devmgr::Device> parent;
if (desc.parent_desc_index == UINT32_MAX) {
parent = platform_bus();
} else {
size_t index = devices[desc.parent_desc_index].index;
parent = device(index)->device;
}
ASSERT_NO_FATAL_FAILURES(AddDevice(parent, desc.name, 0 /* protocol id */, "", &desc.index));
}
ASSERT_NO_FATAL_FAILURES(DoSuspend(flags));
size_t num_to_suspend = fbl::count_of(devices);
while (num_to_suspend > 0) {
// Check that platform bus is not suspended yet.
ASSERT_FALSE(DeviceHasPendingMessages(platform_bus_remote()));
bool made_progress = false;
// Since the table of devices above is topologically sorted (i.e.
// any child is below its parent), this loop should always be able
// to catch a parent receiving a suspend message before its child.
for (size_t i = 0; i < fbl::count_of(devices); ++i) {
auto& desc = devices[i];
if (desc.suspended) {
continue;
}
if (!DeviceHasPendingMessages(desc.index)) {
continue;
}
ASSERT_NO_FATAL_FAILURES(CheckSuspendReceived(device(desc.index)->remote, flags, ZX_OK));
// Make sure all descendants of this device are already suspended.
// We just need to check immediate children since this will
// recursively enforce that property.
for (auto& other_desc : devices) {
if (other_desc.parent_desc_index == i) {
ASSERT_TRUE(other_desc.suspended);
}
}
desc.suspended = true;
--num_to_suspend;
made_progress = true;
}
// Make sure we're not stuck waiting
ASSERT_TRUE(made_progress);
coordinator_loop()->RunUntilIdle();
}
ASSERT_NO_FATAL_FAILURES(CheckSuspendReceived(platform_bus_remote(), flags, ZX_OK));
}
TEST_F(SuspendTestCase, SuspendSuccess) {
ASSERT_NO_FATAL_FAILURES(StateTest(ZX_OK, devmgr::Device::State::kSuspended));
}
TEST_F(SuspendTestCase, SuspendFail) {
ASSERT_NO_FATAL_FAILURES(StateTest(ZX_ERR_BAD_STATE, devmgr::Device::State::kActive));
}
// Verify the device transitions in and out of the suspending state.
void SuspendTestCase::StateTest(zx_status_t suspend_status,
devmgr::Device::State want_device_state) {
size_t index;
ASSERT_NO_FATAL_FAILURES(AddDevice(platform_bus(), "device", 0 /* protocol id */, "", &index));
const uint32_t flags = DEVICE_SUSPEND_FLAG_POWEROFF;
ASSERT_NO_FATAL_FAILURES(DoSuspend(flags));
// Check for the suspend message without replying.
ASSERT_NO_FATAL_FAILURES(CheckSuspendReceived(device(index)->remote, flags));
ASSERT_EQ(device(index)->device->state(), devmgr::Device::State::kSuspending);
ASSERT_NO_FATAL_FAILURES(SendSuspendReply(device(index)->remote, suspend_status));
coordinator_loop()->RunUntilIdle();
ASSERT_EQ(device(index)->device->state(), want_device_state);
}
class ResumeTestCase : public MultipleDeviceTestCase {
public:
void ResumeTest(SystemPowerState target_state);
void StateTest(zx_status_t resume_status, devmgr::Device::State want_device_state);
};
// Verify the resume order is correct
void ResumeTestCase::ResumeTest(SystemPowerState target_state) {
struct DeviceDesc {
// Index into the device desc array below. UINT32_MAX = platform_bus()
const size_t parent_desc_index;
const char* const name;
// index for use with device()
size_t index = 0;
};
DeviceDesc devices[] = {
{UINT32_MAX, "root_child1"}, {UINT32_MAX, "root_child2"}, {0, "root_child1_1"},
{0, "root_child1_2"}, {2, "root_child1_1_1"}, {1, "root_child2_1"},
};
for (auto& desc : devices) {
fbl::RefPtr<devmgr::Device> parent;
if (desc.parent_desc_index == UINT32_MAX) {
parent = platform_bus();
} else {
size_t index = devices[desc.parent_desc_index].index;
parent = device(index)->device;
}
ASSERT_NO_FATAL_FAILURES(AddDevice(parent, desc.name, 0 /* protocol id */, "", &desc.index));
}
ASSERT_NO_FATAL_FAILURES(DoResume(target_state));
// size_t num_to_resume = fbl::count_of(devices);
// while (num_to_resume > 0) {
// Check that platform bus received the resume first
ASSERT_TRUE(DeviceHasPendingMessages(platform_bus_remote()));
coordinator_loop()->RunUntilIdle();
ASSERT_NO_FATAL_FAILURES(CheckResumeReceived(platform_bus_remote(), target_state, ZX_OK));
//}
}
TEST_F(ResumeTestCase, FullyOnCheckOrder) {
ASSERT_NO_FATAL_FAILURES(ResumeTest(SystemPowerState::SYSTEM_POWER_STATE_FULLY_ON));
}
class CompositeTestCase : public MultipleDeviceTestCase {
public:
~CompositeTestCase() override = default;
void CheckCompositeCreation(const char* composite_name, const size_t* device_indexes,
size_t device_indexes_count, size_t* component_indexes_out,
zx::channel* composite_remote_out);
protected:
void SetUp() override {
MultipleDeviceTestCase::SetUp();
ASSERT_NOT_NULL(coordinator_.component_driver());
}
};
TEST_F(MultipleDeviceTestCase, UnbindThenSuspend) {
size_t parent_index;
ASSERT_NO_FATAL_FAILURES(
AddDevice(platform_bus(), "parent-device", 0 /* protocol id */, "", &parent_index));
size_t child_index;
ASSERT_NO_FATAL_FAILURES(AddDevice(device(parent_index)->device, "child-device",
0 /* protocol id */, "", &child_index));
ASSERT_NO_FATAL_FAILURES(coordinator_.ScheduleRemove(device(parent_index)->device));
coordinator_loop()->RunUntilIdle();
// The child should be unbound first.
ASSERT_NO_FATAL_FAILURES(CheckUnbindReceived(device(child_index)->remote));
const uint32_t flags = DEVICE_SUSPEND_FLAG_POWEROFF;
ASSERT_NO_FATAL_FAILURES(DoSuspend(flags));
ASSERT_NO_FATAL_FAILURES(SendUnbindReply(device(child_index)->remote));
coordinator_loop()->RunUntilIdle();
ASSERT_NO_FATAL_FAILURES(CheckRemoveReceivedAndReply(device(child_index)->remote));
coordinator_loop()->RunUntilIdle();
ASSERT_NO_FATAL_FAILURES(CheckRemoveReceivedAndReply(device(parent_index)->remote));
coordinator_loop()->RunUntilIdle();
// The suspend task should complete but not send a suspend message.
ASSERT_FALSE(DeviceHasPendingMessages(device(parent_index)->remote));
ASSERT_NO_FATAL_FAILURES(CheckSuspendReceived(platform_bus_remote(), flags, ZX_OK));
}
TEST_F(MultipleDeviceTestCase, SuspendThenUnbind) {
size_t parent_index;
ASSERT_NO_FATAL_FAILURES(
AddDevice(platform_bus(), "parent-device", 0 /* protocol id */, "", &parent_index));
size_t child_index;
ASSERT_NO_FATAL_FAILURES(AddDevice(device(parent_index)->device, "child-device",
0 /* protocol id */, "", &child_index));
const uint32_t flags = DEVICE_SUSPEND_FLAG_POWEROFF;
ASSERT_NO_FATAL_FAILURES(DoSuspend(flags));
// Don't reply to the suspend yet.
ASSERT_NO_FATAL_FAILURES(CheckSuspendReceived(device(child_index)->remote, flags));
ASSERT_NO_FATAL_FAILURES(coordinator_.ScheduleRemove(device(parent_index)->device));
coordinator_loop()->RunUntilIdle();
// Check that the child device has not yet started unbinding.
ASSERT_FALSE(DeviceHasPendingMessages(device(child_index)->remote));
ASSERT_NO_FATAL_FAILURES(SendSuspendReply(device(child_index)->remote, ZX_OK));
coordinator_loop()->RunUntilIdle();
// The parent should have started suspending. Don't reply yet.
ASSERT_NO_FATAL_FAILURES(CheckSuspendReceived(device(parent_index)->remote, flags));
// Finish unbinding the child.
ASSERT_NO_FATAL_FAILURES(CheckUnbindReceivedAndReply(device(child_index)->remote));
coordinator_loop()->RunUntilIdle();
ASSERT_NO_FATAL_FAILURES(CheckRemoveReceivedAndReply(device(child_index)->remote));
coordinator_loop()->RunUntilIdle();
// Finish suspending the parent.
ASSERT_NO_FATAL_FAILURES(SendSuspendReply(device(parent_index)->remote, ZX_OK));
coordinator_loop()->RunUntilIdle();
ASSERT_NO_FATAL_FAILURES(CheckSuspendReceived(platform_bus_remote(), flags, ZX_OK));
// The parent should now be removed.
ASSERT_NO_FATAL_FAILURES(CheckRemoveReceivedAndReply(device(parent_index)->remote));
coordinator_loop()->RunUntilIdle();
}
void CompositeTestCase::CheckCompositeCreation(const char* composite_name,
const size_t* device_indexes,
size_t device_indexes_count,
size_t* component_indexes_out,
zx::channel* composite_remote_out) {
for (size_t i = 0; i < device_indexes_count; ++i) {
auto device_state = device(device_indexes[i]);
// Check that the components got bound
fbl::String driver = coordinator()->component_driver()->libname;
ASSERT_NO_FATAL_FAILURES(CheckBindDriverReceived(device_state->remote, driver.data()));
coordinator_loop()->RunUntilIdle();
// Synthesize the AddDevice request the component driver would send
char name[32];
snprintf(name, sizeof(name), "%s-comp-device-%zu", composite_name, i);
ASSERT_NO_FATAL_FAILURES(
AddDevice(device_state->device, name, 0, driver, &component_indexes_out[i]));
}
// Make sure the composite comes up
ASSERT_NO_FATAL_FAILURES(CheckCreateCompositeDeviceReceived(
devhost_remote(), composite_name, device_indexes_count, composite_remote_out));
}
class CompositeAddOrderTestCase : public CompositeTestCase {
public:
enum class AddLocation {
// Add the composite before any components
BEFORE,
// Add the composite after some components
MIDDLE,
// Add the composite after all components
AFTER,
};
void ExecuteTest(AddLocation add);
};
class CompositeAddOrderSharedComponentTestCase : public CompositeAddOrderTestCase {
public:
enum class DevNum {
DEV1 = 1,
DEV2,
};
void ExecuteSharedComponentTest(AddLocation dev1Add, AddLocation dev2Add);
};
void CompositeAddOrderSharedComponentTestCase::ExecuteSharedComponentTest(AddLocation dev1_add,
AddLocation dev2_add) {
size_t device_indexes[3];
uint32_t protocol_id[] = {
ZX_PROTOCOL_GPIO,
ZX_PROTOCOL_I2C,
ZX_PROTOCOL_ETHERNET,
};
static_assert(fbl::count_of(protocol_id) == fbl::count_of(device_indexes));
const char* kCompositeDev1Name = "composite-dev1";
const char* kCompositeDev2Name = "composite-dev2";
auto do_add = [&](const char* devname) {
ASSERT_NO_FATAL_FAILURES(BindCompositeDefineComposite(
platform_bus(), protocol_id, fbl::count_of(protocol_id), nullptr /* props */, 0, devname));
};
if (dev1_add == AddLocation::BEFORE) {
ASSERT_NO_FATAL_FAILURES(do_add(kCompositeDev1Name));
}
if (dev2_add == AddLocation::BEFORE) {
ASSERT_NO_FATAL_FAILURES(do_add(kCompositeDev2Name));
}
// Add the devices to construct the composite out of.
for (size_t i = 0; i < fbl::count_of(device_indexes); ++i) {
char name[32];
snprintf(name, sizeof(name), "device-%zu", i);
ASSERT_NO_FATAL_FAILURES(
AddDevice(platform_bus(), name, protocol_id[i], "", &device_indexes[i]));
if (i == 0 && dev1_add == AddLocation::MIDDLE) {
ASSERT_NO_FATAL_FAILURES(do_add(kCompositeDev1Name));
}
if (i == 0 && dev2_add == AddLocation::MIDDLE) {
ASSERT_NO_FATAL_FAILURES(do_add(kCompositeDev2Name));
}
}
if (dev1_add == AddLocation::AFTER) {
ASSERT_NO_FATAL_FAILURES(do_add(kCompositeDev1Name));
}
zx::channel composite_remote1;
zx::channel composite_remote2;
size_t component_device1_indexes[fbl::count_of(device_indexes)];
size_t component_device2_indexes[fbl::count_of(device_indexes)];
ASSERT_NO_FATAL_FAILURES(CheckCompositeCreation(kCompositeDev1Name, device_indexes,
fbl::count_of(device_indexes),
component_device1_indexes, &composite_remote1));
if (dev2_add == AddLocation::AFTER) {
ASSERT_NO_FATAL_FAILURES(do_add(kCompositeDev2Name));
}
ASSERT_NO_FATAL_FAILURES(CheckCompositeCreation(kCompositeDev2Name, device_indexes,
fbl::count_of(device_indexes),
component_device2_indexes, &composite_remote2));
}
void CompositeAddOrderTestCase::ExecuteTest(AddLocation add) {
size_t device_indexes[3];
uint32_t protocol_id[] = {
ZX_PROTOCOL_GPIO,
ZX_PROTOCOL_I2C,
ZX_PROTOCOL_ETHERNET,
};
static_assert(fbl::count_of(protocol_id) == fbl::count_of(device_indexes));
const char* kCompositeDevName = "composite-dev";
auto do_add = [&]() {
ASSERT_NO_FATAL_FAILURES(
BindCompositeDefineComposite(platform_bus(), protocol_id, fbl::count_of(protocol_id),
nullptr /* props */, 0, kCompositeDevName));
};
if (add == AddLocation::BEFORE) {
ASSERT_NO_FATAL_FAILURES(do_add());
}
// Add the devices to construct the composite out of.
for (size_t i = 0; i < fbl::count_of(device_indexes); ++i) {
char name[32];
snprintf(name, sizeof(name), "device-%zu", i);
ASSERT_NO_FATAL_FAILURES(
AddDevice(platform_bus(), name, protocol_id[i], "", &device_indexes[i]));
if (i == 0 && add == AddLocation::MIDDLE) {
ASSERT_NO_FATAL_FAILURES(do_add());
}
}
if (add == AddLocation::AFTER) {
ASSERT_NO_FATAL_FAILURES(do_add());
}
zx::channel composite_remote;
size_t component_device_indexes[fbl::count_of(device_indexes)];
ASSERT_NO_FATAL_FAILURES(CheckCompositeCreation(kCompositeDevName, device_indexes,
fbl::count_of(device_indexes),
component_device_indexes, &composite_remote));
}
TEST_F(CompositeAddOrderTestCase, DefineBeforeDevices) {
ASSERT_NO_FATAL_FAILURES(ExecuteTest(AddLocation::BEFORE));
}
TEST_F(CompositeAddOrderTestCase, DefineAfterDevices) {
ASSERT_NO_FATAL_FAILURES(ExecuteTest(AddLocation::AFTER));
}
TEST_F(CompositeAddOrderTestCase, DefineInbetweenDevices) {
ASSERT_NO_FATAL_FAILURES(ExecuteTest(AddLocation::MIDDLE));
}
TEST_F(CompositeAddOrderSharedComponentTestCase, DefineDevice1BeforeDevice2Before) {
ASSERT_NO_FATAL_FAILURES(ExecuteSharedComponentTest(AddLocation::BEFORE, AddLocation::BEFORE));
}
TEST_F(CompositeAddOrderSharedComponentTestCase, DefineDevice1BeforeDevice2After) {
ASSERT_NO_FATAL_FAILURES(ExecuteSharedComponentTest(AddLocation::BEFORE, AddLocation::AFTER));
}
TEST_F(CompositeAddOrderSharedComponentTestCase, DefineDevice1MiddleDevice2Before) {
ASSERT_NO_FATAL_FAILURES(ExecuteSharedComponentTest(AddLocation::BEFORE, AddLocation::MIDDLE));
}
TEST_F(CompositeAddOrderSharedComponentTestCase, DefineDevice1MiddleDevice2After) {
ASSERT_NO_FATAL_FAILURES(ExecuteSharedComponentTest(AddLocation::MIDDLE, AddLocation::AFTER));
}
TEST_F(CompositeAddOrderSharedComponentTestCase, DefineDevice1AfterDevice2After) {
ASSERT_NO_FATAL_FAILURES(ExecuteSharedComponentTest(AddLocation::AFTER, AddLocation::AFTER));
}
TEST_F(CompositeTestCase, CantAddFromNonPlatformBus) {
size_t index;
ASSERT_NO_FATAL_FAILURES(AddDevice(platform_bus(), "test-device", 0, "", &index));
auto device_state = device(index);
uint32_t protocol_id[] = {ZX_PROTOCOL_I2C, ZX_PROTOCOL_GPIO};
ASSERT_NO_FATAL_FAILURES(
BindCompositeDefineComposite(device_state->device, protocol_id, fbl::count_of(protocol_id),
nullptr /* props */, 0, "composite-dev", ZX_ERR_ACCESS_DENIED));
}
TEST_F(CompositeTestCase, AddMultipleSharedComponentCompositeDevices) {
size_t device_indexes[2];
zx_status_t status = ZX_OK;
uint32_t protocol_id[] = {
ZX_PROTOCOL_GPIO,
ZX_PROTOCOL_I2C,
};
static_assert(fbl::count_of(protocol_id) == fbl::count_of(device_indexes));
for (size_t i = 0; i < fbl::count_of(device_indexes); ++i) {
char name[32];
snprintf(name, sizeof(name), "device-%zu", i);
ASSERT_NO_FATAL_FAILURES(
AddDevice(platform_bus(), name, protocol_id[i], "", &device_indexes[i]));
}
for (size_t i = 1; i <= 5; i++) {
char composite_dev_name[32];
snprintf(composite_dev_name, sizeof(composite_dev_name), "composite-dev-%zu", i);
ASSERT_NO_FATAL_FAILURES(
BindCompositeDefineComposite(platform_bus(), protocol_id, fbl::count_of(protocol_id),
nullptr /* props */, 0, composite_dev_name));
}
zx::channel composite_remote[5];
size_t component_device_indexes[5][fbl::count_of(device_indexes)];
for (size_t i = 1; i <= 5; i++) {
char composite_dev_name[32];
snprintf(composite_dev_name, sizeof(composite_dev_name), "composite-dev-%zu", i);
ASSERT_NO_FATAL_FAILURES(
CheckCompositeCreation(composite_dev_name, device_indexes, fbl::count_of(device_indexes),
component_device_indexes[i - 1], &composite_remote[i - 1]));
}
auto device1 = device(device_indexes[1])->device;
size_t count = 0;
for (auto& child : device1->children()) {
count++;
char name[32];
snprintf(name, sizeof(name), "composite-dev-%zu-comp-device-1", count);
if (strcmp(child.name().data(), name)) {
status = ZX_ERR_INTERNAL;
}
}
ASSERT_OK(status);
ASSERT_EQ(count, 5);
}
TEST_F(CompositeTestCase, SharedComponentUnbinds) {
size_t device_indexes[2];
uint32_t protocol_id[] = {
ZX_PROTOCOL_GPIO,
ZX_PROTOCOL_I2C,
};
static_assert(fbl::count_of(protocol_id) == fbl::count_of(device_indexes));
const char* kCompositeDev1Name = "composite-dev-1";
const char* kCompositeDev2Name = "composite-dev-2";
ASSERT_NO_FATAL_FAILURES(
BindCompositeDefineComposite(platform_bus(), protocol_id, fbl::count_of(protocol_id),
nullptr /* props */, 0, kCompositeDev1Name));
ASSERT_NO_FATAL_FAILURES(
BindCompositeDefineComposite(platform_bus(), protocol_id, fbl::count_of(protocol_id),
nullptr /* props */, 0, kCompositeDev2Name));
// Add the devices to construct the composite out of.
for (size_t i = 0; i < fbl::count_of(device_indexes); ++i) {
char name[32];
snprintf(name, sizeof(name), "device-%zu", i);
ASSERT_NO_FATAL_FAILURES(
AddDevice(platform_bus(), name, protocol_id[i], "", &device_indexes[i]));
}
zx::channel composite1_remote;
zx::channel composite2_remote;
size_t component_device1_indexes[fbl::count_of(device_indexes)];
size_t component_device2_indexes[fbl::count_of(device_indexes)];
ASSERT_NO_FATAL_FAILURES(CheckCompositeCreation(kCompositeDev1Name, device_indexes,
fbl::count_of(device_indexes),
component_device1_indexes, &composite1_remote));
ASSERT_NO_FATAL_FAILURES(CheckCompositeCreation(kCompositeDev2Name, device_indexes,
fbl::count_of(device_indexes),
component_device2_indexes, &composite2_remote));
coordinator_loop()->RunUntilIdle();
{
auto device1 = device(device_indexes[1])->device;
fbl::RefPtr<devmgr::Device> comp_device1;
fbl::RefPtr<devmgr::Device> comp_device2;
for (auto& comp : device1->components()) {
auto comp_device = comp.composite()->device();
if (!strcmp(comp_device->name().data(), kCompositeDev1Name)) {
comp_device1 = comp_device;
continue;
}
if (!strcmp(comp_device->name().data(), kCompositeDev2Name)) {
comp_device2 = comp_device;
continue;
}
}
ASSERT_NOT_NULL(comp_device1);
ASSERT_NOT_NULL(comp_device2);
}
// Remove device 0 and its children (component and composite devices).
ASSERT_NO_FATAL_FAILURES(coordinator_.ScheduleRemove(device(device_indexes[0])->device));
coordinator_loop()->RunUntilIdle();
zx::channel& device_remote = device(device_indexes[0])->remote;
zx::channel& component1_remote = device(component_device1_indexes[0])->remote;
zx::channel& component2_remote = device(component_device2_indexes[0])->remote;
// Check the components have received their unbind requests.
ASSERT_NO_FATAL_FAILURES(CheckUnbindReceived(component1_remote));
ASSERT_NO_FATAL_FAILURES(CheckUnbindReceived(component2_remote));
// The device and composites should not have received any requests yet.
ASSERT_FALSE(DeviceHasPendingMessages(device_remote));
ASSERT_FALSE(DeviceHasPendingMessages(composite1_remote));
ASSERT_FALSE(DeviceHasPendingMessages(composite2_remote));
ASSERT_NO_FATAL_FAILURES(SendUnbindReply(component1_remote));
ASSERT_NO_FATAL_FAILURES(SendUnbindReply(component2_remote));
coordinator_loop()->RunUntilIdle();
// The composites should start unbinding since the components finished unbinding.
ASSERT_NO_FATAL_FAILURES(CheckUnbindReceivedAndReply(composite1_remote));
ASSERT_NO_FATAL_FAILURES(CheckUnbindReceivedAndReply(composite2_remote));
coordinator_loop()->RunUntilIdle();
// We are still waiting for the composites to be removed.
ASSERT_FALSE(DeviceHasPendingMessages(device_remote));
ASSERT_FALSE(DeviceHasPendingMessages(component1_remote));
ASSERT_FALSE(DeviceHasPendingMessages(component2_remote));
// Finish removing the composites.
ASSERT_NO_FATAL_FAILURES(CheckRemoveReceivedAndReply(composite1_remote));
ASSERT_NO_FATAL_FAILURES(CheckRemoveReceivedAndReply(composite2_remote));
coordinator_loop()->RunUntilIdle();
ASSERT_FALSE(DeviceHasPendingMessages(device_remote));
// Finish removing the components.
ASSERT_NO_FATAL_FAILURES(CheckRemoveReceivedAndReply(component1_remote));
ASSERT_NO_FATAL_FAILURES(CheckRemoveReceivedAndReply(component2_remote));
coordinator_loop()->RunUntilIdle();
ASSERT_NO_FATAL_FAILURES(CheckRemoveReceivedAndReply(device_remote));
// Add the device back and verify the composite gets created again
ASSERT_NO_FATAL_FAILURES(
AddDevice(platform_bus(), "device-0", protocol_id[0], "", &device_indexes[0]));
{
auto device_state = device(device_indexes[0]);
// Wait for the components to get bound
fbl::String driver = coordinator()->component_driver()->libname;
ASSERT_NO_FATAL_FAILURES(CheckBindDriverReceived(device_state->remote, driver.data()));
coordinator_loop()->RunUntilIdle();
// Synthesize the AddDevice request the component driver would send
ASSERT_NO_FATAL_FAILURES(AddDevice(device_state->device, "composite-dev1-comp-device-0", 0,
driver, &component_device1_indexes[0]));
}
{
auto device_state = device(device_indexes[0]);
// Wait for the components to get bound
fbl::String driver = coordinator()->component_driver()->libname;
ASSERT_NO_FATAL_FAILURES(CheckBindDriverReceived(device_state->remote, driver.data()));
coordinator_loop()->RunUntilIdle();
// Synthesize the AddDevice request the component driver would send
ASSERT_NO_FATAL_FAILURES(AddDevice(device_state->device, "composite-dev2-comp-device-0", 0,
driver, &component_device2_indexes[0]));
}
ASSERT_NO_FATAL_FAILURES(CheckCreateCompositeDeviceReceived(
devhost_remote(), kCompositeDev1Name, fbl::count_of(device_indexes), &composite1_remote));
ASSERT_NO_FATAL_FAILURES(CheckCreateCompositeDeviceReceived(
devhost_remote(), kCompositeDev2Name, fbl::count_of(device_indexes), &composite2_remote));
}
TEST_F(CompositeTestCase, ComponentUnbinds) {
size_t device_indexes[2];
uint32_t protocol_id[] = {
ZX_PROTOCOL_GPIO,
ZX_PROTOCOL_I2C,
};
static_assert(fbl::count_of(protocol_id) == fbl::count_of(device_indexes));
const char* kCompositeDevName = "composite-dev";
ASSERT_NO_FATAL_FAILURES(BindCompositeDefineComposite(platform_bus(), protocol_id,
fbl::count_of(protocol_id),
nullptr /* props */, 0, kCompositeDevName));
// Add the devices to construct the composite out of.
for (size_t i = 0; i < fbl::count_of(device_indexes); ++i) {
char name[32];
snprintf(name, sizeof(name), "device-%zu", i);
ASSERT_NO_FATAL_FAILURES(
AddDevice(platform_bus(), name, protocol_id[i], "", &device_indexes[i]));
}
zx::channel composite_remote;
size_t component_device_indexes[fbl::count_of(device_indexes)];
ASSERT_NO_FATAL_FAILURES(CheckCompositeCreation(kCompositeDevName, device_indexes,
fbl::count_of(device_indexes),
component_device_indexes, &composite_remote));
coordinator_loop()->RunUntilIdle();
{
auto device1 = device(device_indexes[1])->device;
fbl::RefPtr<devmgr::Device> comp_device;
for (auto& comp : device1->components()) {
comp_device = comp.composite()->device();
if (!strcmp(comp_device->name().data(), kCompositeDevName)) {
break;
}
}
ASSERT_NOT_NULL(comp_device);
}
// Remove device 0 and its children (component and composite devices).
ASSERT_NO_FATAL_FAILURES(coordinator_.ScheduleRemove(device(device_indexes[0])->device));
coordinator_loop()->RunUntilIdle();
zx::channel& device_remote = device(device_indexes[0])->remote;
zx::channel& component_remote = device(component_device_indexes[0])->remote;
// The device and composite should not have received an unbind request yet.
ASSERT_FALSE(DeviceHasPendingMessages(device_remote));
ASSERT_FALSE(DeviceHasPendingMessages(composite_remote));
// Check the component and composite are unbound.
ASSERT_NO_FATAL_FAILURES(CheckUnbindReceivedAndReply(component_remote));
coordinator_loop()->RunUntilIdle();
ASSERT_FALSE(DeviceHasPendingMessages(device_remote));
ASSERT_FALSE(DeviceHasPendingMessages(component_remote));
ASSERT_NO_FATAL_FAILURES(CheckUnbindReceivedAndReply(composite_remote));
coordinator_loop()->RunUntilIdle();
// Still waiting for the composite to be removed.
ASSERT_FALSE(DeviceHasPendingMessages(device_remote));
ASSERT_FALSE(DeviceHasPendingMessages(component_remote));
// Finish removing the composite.
ASSERT_NO_FATAL_FAILURES(CheckRemoveReceivedAndReply(composite_remote));
coordinator_loop()->RunUntilIdle();
ASSERT_FALSE(DeviceHasPendingMessages(device_remote));
// Finish removing the component.
ASSERT_NO_FATAL_FAILURES(CheckRemoveReceivedAndReply(component_remote));
coordinator_loop()->RunUntilIdle();
ASSERT_NO_FATAL_FAILURES(CheckRemoveReceivedAndReply(device_remote));
coordinator_loop()->RunUntilIdle();
// Add the device back and verify the composite gets created again
ASSERT_NO_FATAL_FAILURES(
AddDevice(platform_bus(), "device-0", protocol_id[0], "", &device_indexes[0]));
{
auto device_state = device(device_indexes[0]);
// Wait for the components to get bound
fbl::String driver = coordinator()->component_driver()->libname;
ASSERT_NO_FATAL_FAILURES(CheckBindDriverReceived(device_state->remote, driver.data()));
coordinator_loop()->RunUntilIdle();
// Synthesize the AddDevice request the component driver would send
ASSERT_NO_FATAL_FAILURES(AddDevice(device_state->device, "component-device-0", 0, driver,
&component_device_indexes[0]));
}
ASSERT_NO_FATAL_FAILURES(CheckCreateCompositeDeviceReceived(
devhost_remote(), kCompositeDevName, fbl::count_of(device_indexes), &composite_remote));
}
TEST_F(CompositeTestCase, SuspendOrder) {
size_t device_indexes[2];
uint32_t protocol_id[] = {
ZX_PROTOCOL_GPIO,
ZX_PROTOCOL_I2C,
};
static_assert(fbl::count_of(protocol_id) == fbl::count_of(device_indexes));
const char* kCompositeDevName = "composite-dev";
ASSERT_NO_FATAL_FAILURES(BindCompositeDefineComposite(platform_bus(), protocol_id,
fbl::count_of(protocol_id),
nullptr /* props */, 0, kCompositeDevName));
// Add the devices to construct the composite out of.
for (size_t i = 0; i < fbl::count_of(device_indexes); ++i) {
char name[32];
snprintf(name, sizeof(name), "device-%zu", i);
ASSERT_NO_FATAL_FAILURES(
AddDevice(platform_bus(), name, protocol_id[i], "", &device_indexes[i]));
}
zx::channel composite_remote;
size_t component_device_indexes[fbl::count_of(device_indexes)];
ASSERT_NO_FATAL_FAILURES(CheckCompositeCreation(kCompositeDevName, device_indexes,
fbl::count_of(device_indexes),
component_device_indexes, &composite_remote));
const uint32_t suspend_flags = DEVICE_SUSPEND_FLAG_POWEROFF;
ASSERT_NO_FATAL_FAILURES(DoSuspend(suspend_flags));
// Make sure none of the components have received their suspend requests
ASSERT_FALSE(DeviceHasPendingMessages(platform_bus_remote()));
for (auto idx : device_indexes) {
ASSERT_FALSE(DeviceHasPendingMessages(idx));
}
for (auto idx : component_device_indexes) {
ASSERT_FALSE(DeviceHasPendingMessages(idx));
}
// The composite should have been the first to get one
ASSERT_NO_FATAL_FAILURES(CheckSuspendReceived(composite_remote, suspend_flags, ZX_OK));
coordinator_loop()->RunUntilIdle();
// Next, all of the internal component devices should have them, but none of the devices
// themselves
ASSERT_FALSE(DeviceHasPendingMessages(platform_bus_remote()));
for (auto idx : device_indexes) {
ASSERT_FALSE(DeviceHasPendingMessages(idx));
}
for (auto idx : component_device_indexes) {
ASSERT_NO_FATAL_FAILURES(CheckSuspendReceived(device(idx)->remote, suspend_flags, ZX_OK));
}
coordinator_loop()->RunUntilIdle();
// Next, the devices should get them
ASSERT_FALSE(DeviceHasPendingMessages(platform_bus_remote()));
for (auto idx : device_indexes) {
ASSERT_NO_FATAL_FAILURES(CheckSuspendReceived(device(idx)->remote, suspend_flags, ZX_OK));
}
coordinator_loop()->RunUntilIdle();
// Finally, the platform bus driver, which is the parent of all of the devices
ASSERT_NO_FATAL_FAILURES(CheckSuspendReceived(platform_bus_remote(), suspend_flags, ZX_OK));
coordinator_loop()->RunUntilIdle();
}
// Make sure we receive devfs notifications when composite devices appear
TEST_F(CompositeTestCase, DevfsNotifications) {
zx::channel watcher;
{
zx::channel remote;
ASSERT_OK(zx::channel::create(0, &watcher, &remote));
ASSERT_OK(devfs_watch(coordinator()->root_device()->self, std::move(remote),
fuchsia_io_WATCH_MASK_ADDED));
}
size_t device_indexes[2];
uint32_t protocol_id[] = {
ZX_PROTOCOL_GPIO,
ZX_PROTOCOL_I2C,
};
static_assert(fbl::count_of(protocol_id) == fbl::count_of(device_indexes));
const char* kCompositeDevName = "composite-dev";
ASSERT_NO_FATAL_FAILURES(BindCompositeDefineComposite(platform_bus(), protocol_id,
fbl::count_of(protocol_id),
nullptr /* props */, 0, kCompositeDevName));
// Add the devices to construct the composite out of.
for (size_t i = 0; i < fbl::count_of(device_indexes); ++i) {
char name[32];
snprintf(name, sizeof(name), "device-%zu", i);
ASSERT_NO_FATAL_FAILURES(
AddDevice(platform_bus(), name, protocol_id[i], "", &device_indexes[i]));
}
zx::channel composite_remote;
size_t component_device_indexes[fbl::count_of(device_indexes)];
ASSERT_NO_FATAL_FAILURES(CheckCompositeCreation(kCompositeDevName, device_indexes,
fbl::count_of(device_indexes),
component_device_indexes, &composite_remote));
uint8_t msg[fuchsia_io_MAX_FILENAME + 2];
uint32_t msg_len = 0;
ASSERT_OK(watcher.read(0, msg, nullptr, sizeof(msg), 0, &msg_len, nullptr));
ASSERT_EQ(msg_len, 2 + strlen(kCompositeDevName));
ASSERT_EQ(msg[0], fuchsia_io_WATCH_EVENT_ADDED);
ASSERT_EQ(msg[1], strlen(kCompositeDevName));
ASSERT_BYTES_EQ(reinterpret_cast<const uint8_t*>(kCompositeDevName), msg + 2, msg[1]);
}
// Make sure the path returned by GetTopologicalPath is accurate
TEST_F(CompositeTestCase, Topology) {
size_t device_indexes[2];
uint32_t protocol_id[] = {
ZX_PROTOCOL_GPIO,
ZX_PROTOCOL_I2C,
};
static_assert(fbl::count_of(protocol_id) == fbl::count_of(device_indexes));
const char* kCompositeDevName = "composite-dev";
ASSERT_NO_FATAL_FAILURES(BindCompositeDefineComposite(platform_bus(), protocol_id,
fbl::count_of(protocol_id),
nullptr /* props */, 0, kCompositeDevName));
// Add the devices to construct the composite out of.
for (size_t i = 0; i < fbl::count_of(device_indexes); ++i) {
char name[32];
snprintf(name, sizeof(name), "device-%zu", i);
ASSERT_NO_FATAL_FAILURES(
AddDevice(platform_bus(), name, protocol_id[i], "", &device_indexes[i]));
}
zx::channel composite_remote;
size_t component_device_indexes[fbl::count_of(device_indexes)];
ASSERT_NO_FATAL_FAILURES(CheckCompositeCreation(kCompositeDevName, device_indexes,
fbl::count_of(device_indexes),
component_device_indexes, &composite_remote));
devmgr::Devnode* dn = coordinator()->root_device()->self;
fbl::RefPtr<devmgr::Device> composite_dev;
ASSERT_OK(devmgr::devfs_walk(dn, "composite-dev", &composite_dev));
char path_buf[PATH_MAX];
ASSERT_OK(coordinator()->GetTopologicalPath(composite_dev, path_buf, sizeof(path_buf)));
ASSERT_STR_EQ(path_buf, "/dev/composite-dev");
}
// Disable the test as it is flaking fxb/34842
TEST_F(MultipleDeviceTestCase, SuspendFidlMexec) {
ASSERT_OK(coordinator_loop()->StartThread("DevCoordLoop"));
set_coordinator_loop_thread_running(true);
async::Loop devhost_loop{&kAsyncLoopConfigNoAttachToCurrentThread};
ASSERT_OK(devhost_loop.StartThread("DevHostLoop"));
async::Wait suspend_task_pbus(
platform_bus_remote().get(), ZX_CHANNEL_READABLE, 0,
[this](async_dispatcher_t*, async::Wait*, zx_status_t, const zx_packet_signal_t*) {
CheckSuspendReceived(platform_bus_remote(), DEVICE_SUSPEND_FLAG_MEXEC, ZX_OK);
});
ASSERT_OK(suspend_task_pbus.Begin(devhost_loop.dispatcher()));
async::Wait suspend_task_sys(
sys_proxy_remote_.get(), ZX_CHANNEL_READABLE, 0,
[this](async_dispatcher_t*, async::Wait*, zx_status_t, const zx_packet_signal_t*) {
CheckSuspendReceived(sys_proxy_remote_, DEVICE_SUSPEND_FLAG_MEXEC, ZX_OK);
});
ASSERT_OK(suspend_task_sys.Begin(devhost_loop.dispatcher()));
zx::channel services, services_remote;
ASSERT_OK(zx::channel::create(0, &services, &services_remote));
ASSERT_OK(coordinator()->BindOutgoingServices(std::move(services_remote)));
zx::channel channel, channel_remote;
ASSERT_OK(zx::channel::create(0, &channel, &channel_remote));
const char* service = "svc/" fuchsia_device_manager_Administrator_Name;
ASSERT_OK(fdio_service_connect_at(services.get(), service, channel_remote.release()));
bool callback_executed = false;
DoSuspend(DEVICE_SUSPEND_FLAG_MEXEC, [&](uint32_t flags) {
zx_status_t call_status = ZX_OK;
ASSERT_OK(fuchsia_device_manager_AdministratorSuspend(channel.get(), flags, &call_status));
ASSERT_OK(call_status);
callback_executed = true;
});
ASSERT_TRUE(callback_executed);
ASSERT_FALSE(suspend_task_pbus.is_pending());
ASSERT_FALSE(suspend_task_sys.is_pending());
}
TEST_F(MultipleDeviceTestCase, SuspendFidlMexecFail) {
ASSERT_OK(coordinator_loop()->StartThread("DevCoordLoop"));
set_coordinator_loop_thread_running(true);
async::Loop devhost_loop{&kAsyncLoopConfigNoAttachToCurrentThread};
ASSERT_OK(devhost_loop.StartThread("DevHostLoop"));
async::Wait suspend_task_pbus(
platform_bus_remote().get(), ZX_CHANNEL_READABLE, 0,
[this](async_dispatcher_t*, async::Wait*, zx_status_t, const zx_packet_signal_t*) {
CheckSuspendReceived(platform_bus_remote(), DEVICE_SUSPEND_FLAG_MEXEC);
});
ASSERT_OK(suspend_task_pbus.Begin(devhost_loop.dispatcher()));
async::Wait suspend_task_sys(
sys_proxy_remote_.get(), ZX_CHANNEL_READABLE, 0,
[this](async_dispatcher_t*, async::Wait*, zx_status_t, const zx_packet_signal_t*) {
CheckSuspendReceived(sys_proxy_remote_, DEVICE_SUSPEND_FLAG_MEXEC, ZX_OK);
});
ASSERT_OK(suspend_task_sys.Begin(devhost_loop.dispatcher()));
zx::channel services, services_remote;
ASSERT_OK(zx::channel::create(0, &services, &services_remote));
ASSERT_OK(coordinator()->BindOutgoingServices(std::move(services_remote)));
zx::channel channel, channel_remote;
ASSERT_OK(zx::channel::create(0, &channel, &channel_remote));
const char* service = "svc/" fuchsia_device_manager_Administrator_Name;
ASSERT_OK(fdio_service_connect_at(services.get(), service, channel_remote.release()));
bool callback_executed = false;
DoSuspend(DEVICE_SUSPEND_FLAG_MEXEC, [&](uint32_t flags) {
zx_status_t call_status = ZX_OK;
ASSERT_OK(fuchsia_device_manager_AdministratorSuspend(channel.get(), flags, &call_status));
ASSERT_EQ(call_status, ZX_ERR_TIMED_OUT);
callback_executed = true;
});
ASSERT_TRUE(callback_executed);
ASSERT_FALSE(suspend_task_pbus.is_pending());
ASSERT_TRUE(suspend_task_sys.is_pending());
}
} // namespace
int main(int argc, char** argv) { return RUN_ALL_TESTS(argc, argv); }