blob: 2f4b637fae45bb505095ba28035cece5f08211bd [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 <lib/fidl/cpp/message.h>
#include <lib/fidl/txn_header.h>
#include <zircon/fidl.h>
#include <zircon/types.h>
#include "multiple_device_test.h"
// Reads a CreateDevice from remote, checks expectations, and sends a ZX_OK
// response.
void MultipleDeviceTestCase::CheckCreateDeviceReceived(const zx::channel& remote,
const char* expected_driver,
zx::channel* device_coordinator_remote,
zx::channel* device_controller_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), std::size(handles),
&actual_bytes, &actual_handles);
ASSERT_OK(status);
ASSERT_LT(0, actual_bytes);
ASSERT_EQ(4, actual_handles);
*device_coordinator_remote = zx::channel(handles[0]);
*device_controller_remote = zx::channel(handles[1]);
status = zx_handle_close(handles[2]);
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 MultipleDeviceTestCase::CheckSuspendReceived(const zx::channel& remote,
uint32_t expected_flags, zx_txid_t* txid) {
// 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), std::size(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);
*txid = hdr->txid;
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 MultipleDeviceTestCase::SendSuspendReply(const zx::channel& remote, zx_status_t return_status,
zx_txid_t txid) {
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);
fidl_init_txn_header(&resp->hdr, txid, fuchsia_device_manager_DeviceControllerSuspendOrdinal);
resp->status = return_status;
zx_status_t status =
fidl_encode(&fuchsia_device_manager_DeviceControllerSuspendResponseTable, bytes,
sizeof(*resp), handles, std::size(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 MultipleDeviceTestCase::CheckSuspendReceivedAndReply(const zx::channel& remote,
uint32_t expected_flags,
zx_status_t return_status) {
zx_txid_t txid;
CheckSuspendReceived(remote, expected_flags, &txid);
SendSuspendReply(remote, return_status, txid);
}
void MultipleDeviceTestCase::SetUp() {
ASSERT_NO_FATAL_FAILURES(InitializeCoordinator(&coordinator_));
{
zx::channel local;
zx_status_t status = zx::channel::create(0, &local, &driver_host_remote_);
ASSERT_OK(status);
driver_host_ = fbl::MakeRefCounted<DriverHost>(&coordinator_, std::move(local), zx::channel{},
zx::process{});
}
// Start the mock server thread.
ASSERT_OK(mock_server_loop_.StartThread("mock-admin-server"));
// Set up the sys device proxy, inside of the driver_host
ASSERT_OK(coordinator_.PrepareProxy(coordinator_.sys_device(), driver_host_));
coordinator_loop_.RunUntilIdle();
ASSERT_NO_FATAL_FAILURES(CheckCreateDeviceReceived(driver_host_remote_, kSystemDriverPath,
&sys_proxy_coordinator_remote_,
&sys_proxy_controller_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_.controller_remote);
ASSERT_OK(status);
zx::channel local2;
status = zx::channel::create(0, &local2, &platform_bus_.coordinator_remote);
ASSERT_OK(status);
status = coordinator_.AddDevice(
coordinator_.sys_device()->proxy(), std::move(local), std::move(local2),
/* props_data */ nullptr, /* props_count */ 0, "platform-bus", 0, /* driver_path */ {},
/* args */ {}, /* invisible */ false, /* skip_autobind */ false, /* has_init */ false,
/* always_init */ true,
/*inspect*/ zx::vmo(), /* client_remote */ zx::channel(), &platform_bus_.device);
ASSERT_OK(status);
coordinator_loop_.RunUntilIdle();
ASSERT_NO_FATAL_FAILURES(CheckInitReceivedAndReply(platform_bus_.controller_remote));
coordinator_loop()->RunUntilIdle();
}
coordinator_.SetFshostAdminClient(
coordinator_.admin_server_.CreateClient(mock_server_loop_.dispatcher()));
}
void MultipleDeviceTestCase::TearDown() {
// Stop any threads, so we're serialized here.
if (coordinator_loop_thread_running_) {
coordinator_loop_.Quit();
coordinator_loop_.JoinThreads();
coordinator_loop_.ResetQuit();
}
coordinator_loop_.RunUntilIdle();
// Remove the devices in the opposite order that we added them
while (!devices_.is_empty()) {
devices_.pop_back();
coordinator_loop_.RunUntilIdle();
}
coordinator_.RemoveDevice(std::move(platform_bus_.device), /* forced */ false);
coordinator_loop_.RunUntilIdle();
// We need to explicitly remove this proxy device, because it holds a reference to devhost_.
// Other devices will be removed via the DeviceState dtor.
fbl::RefPtr<Device> sys_proxy = coordinator_.sys_device()->proxy();
if (sys_proxy) {
coordinator_.RemoveDevice(std::move(sys_proxy), /* forced */ false);
coordinator_loop_.RunUntilIdle();
}
// We no longer need the async loop.
// If we do not shutdown here, the destructor
// could be cleaning up the vfs, before the loop clears the
// connections.
coordinator_loop_.Shutdown();
}
void MultipleDeviceTestCase::AddDevice(const fbl::RefPtr<Device>& parent, const char* name,
uint32_t protocol_id, fbl::String driver, bool invisible,
bool has_init, bool reply_to_init, bool always_init,
zx::vmo inspect, size_t* index) {
DeviceState state;
zx::channel local, local2;
zx_status_t status = zx::channel::create(0, &local, &state.controller_remote);
ASSERT_OK(status);
status = zx::channel::create(0, &local2, &state.coordinator_remote);
ASSERT_OK(status);
status = coordinator_.AddDevice(
parent, std::move(local), std::move(local2), /* props_data */ nullptr, /* props_count */ 0,
name, /* driver_path */ protocol_id, driver.data(), /* args */ {}, invisible,
/* skip_autobind */ false, has_init, always_init, std::move(inspect),
/* client_remote */ zx::channel(), &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;
if (reply_to_init) {
ASSERT_NO_FATAL_FAILURES(CheckInitReceivedAndReply(device(*index)->controller_remote));
coordinator_loop()->RunUntilIdle();
}
}
void MultipleDeviceTestCase::AddDevice(const fbl::RefPtr<Device>& parent, const char* name,
uint32_t protocol_id, fbl::String driver, size_t* index) {
AddDevice(parent, name, protocol_id, driver, /* invisible */ false, /* has_init */ false,
/* reply_to_init */ true, /* always_init */ true, /* inspect */ zx::vmo(), index);
}
void MultipleDeviceTestCase::AddDeviceSkipAutobind(const fbl::RefPtr<Device>& parent,
const char* name, uint32_t protocol_id,
size_t* index) {
DeviceState state;
zx::channel local, local2;
zx_status_t status = zx::channel::create(0, &local, &state.controller_remote);
ASSERT_OK(status);
status = zx::channel::create(0, &local2, &state.coordinator_remote);
ASSERT_OK(status);
status = coordinator_.AddDevice(
parent, std::move(local), std::move(local2), /* props_data */ nullptr, /* props_count */ 0,
name, /* driver_path */ protocol_id, /* driver */ "", /* args */ {}, /* invisible */ false,
/* skip_autobind */ true, /* has_init */ false, /* always_init */ true,
/* inspect */ zx::vmo(),
/* client_remote */ zx::channel(), &state.device);
ASSERT_OK(status);
coordinator_loop_.RunUntilIdle();
devices_.push_back(std::move(state));
*index = devices_.size() - 1;
ASSERT_NO_FATAL_FAILURES(CheckInitReceivedAndReply(device(*index)->controller_remote));
coordinator_loop()->RunUntilIdle();
}
void MultipleDeviceTestCase::RemoveDevice(size_t device_index) {
auto& state = devices_[device_index];
ASSERT_OK(coordinator_.RemoveDevice(state.device, false));
state.device.reset();
state.controller_remote.reset();
state.coordinator_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].controller_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);
suspend_cb(flags);
if (!coordinator_loop_thread_running()) {
coordinator_loop()->RunUntilIdle();
}
ASSERT_EQ(vfs_exit_expected, coordinator_.admin_server_.has_been_shutdown_);
}
void MultipleDeviceTestCase::DoSuspend(uint32_t flags) {
DoSuspend(flags, [this](uint32_t flags) { coordinator()->Suspend(flags); });
}
void MultipleDeviceTestCase::DoSuspendWithCallback(
uint32_t flags, fit::function<void(zx_status_t status)> suspend_complete_cb) {
DoSuspend(flags, [this, suspend_cb = std::move(suspend_complete_cb)](uint32_t flags) mutable {
coordinator()->Suspend(SuspendContext(SuspendContext::Flags::kSuspend, flags),
std::move(suspend_cb));
});
}
// 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, zx_txid_t* txid) {
// 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), std::size(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);
*txid = hdr->txid;
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, zx_txid_t txid) {
namespace fdm = ::llcpp::fuchsia::device::manager;
fidl::aligned<fdm::DeviceController_Unbind_Response> result_resp;
auto result = fdm::DeviceController_Unbind_Result::WithResponse(fidl::unowned_ptr(&result_resp));
fdm::DeviceController::UnbindResponse resp(result);
resp._hdr.txid = txid;
fidl::OwnedEncodedMessage<fdm::DeviceController::UnbindResponse> encoded(&resp);
ASSERT_TRUE(encoded.ok());
encoded.Write(remote.get());
ASSERT_TRUE(encoded.ok());
}
void MultipleDeviceTestCase::CheckUnbindReceivedAndReply(const zx::channel& remote) {
zx_txid_t txid;
CheckUnbindReceived(remote, &txid);
SendUnbindReply(remote, txid);
}
// Reads the request from |remote| and verifies whether it matches the expected
// Unbind request.
// |SendRemoveReply| can be used to send the desired response.
void MultipleDeviceTestCase::CheckRemoveReceived(const zx::channel& remote, zx_txid_t* txid) {
// 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), std::size(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);
*txid = hdr->txid;
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, zx_txid_t txid) {
namespace fdm = ::llcpp::fuchsia::device::manager;
fidl::aligned<fdm::DeviceController_CompleteRemoval_Response> result_resp;
auto result =
fdm::DeviceController_CompleteRemoval_Result::WithResponse(fidl::unowned_ptr(&result_resp));
fdm::DeviceController::CompleteRemovalResponse resp(result);
resp._hdr.txid = txid;
fidl::OwnedEncodedMessage<fdm::DeviceController::CompleteRemovalResponse> encoded(&resp);
ASSERT_TRUE(encoded.ok());
encoded.Write(remote.get());
ASSERT_TRUE(encoded.ok());
}
void MultipleDeviceTestCase::CheckRemoveReceivedAndReply(const zx::channel& remote) {
zx_txid_t txid;
CheckRemoveReceived(remote, &txid);
SendRemoveReply(remote, txid);
}
// 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 MultipleDeviceTestCase::CheckResumeReceived(const zx::channel& remote,
SystemPowerState target_state, zx_txid_t* txid) {
// 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), std::size(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);
*txid = hdr->txid;
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 MultipleDeviceTestCase::SendResumeReply(const zx::channel& remote, zx_status_t return_status,
zx_txid_t txid) {
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);
fidl_init_txn_header(&resp->hdr, txid, fuchsia_device_manager_DeviceControllerResumeOrdinal);
resp->status = return_status;
zx_status_t status =
fidl_encode(&fuchsia_device_manager_DeviceControllerResumeResponseTable, bytes, sizeof(*resp),
handles, std::size(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 MultipleDeviceTestCase::CheckResumeReceived(const zx::channel& remote,
SystemPowerState target_state,
zx_status_t return_status) {
zx_txid_t txid;
CheckResumeReceived(remote, target_state, &txid);
SendResumeReply(remote, return_status, txid);
}
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, ResumeCallback callback) {
DoResume(target_state, [this, callback = std::move(callback)](SystemPowerState target_state) {
coordinator()->Resume(target_state, callback);
});
}
// Reads the request from |remote| and verifies whether it matches the expected Init request.
// |SendInitReply| can be used to send the desired response.
void MultipleDeviceTestCase::CheckInitReceived(const zx::channel& remote, zx_txid_t* txid) {
// Read the init 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), std::size(handles),
&actual_bytes, &actual_handles);
ASSERT_OK(status);
ASSERT_LT(0, actual_bytes);
ASSERT_EQ(0, actual_handles);
// Validate the init request.
auto hdr = reinterpret_cast<fidl_message_header_t*>(bytes);
*txid = hdr->txid;
ASSERT_EQ(fuchsia_device_manager_DeviceControllerInitOrdinal, hdr->ordinal);
status = fidl_decode(&fuchsia_device_manager_DeviceControllerInitRequestTable, 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 |CheckInitializingReceived|.
void MultipleDeviceTestCase::SendInitReply(const zx::channel& remote, zx_txid_t txid,
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_DeviceControllerInitResponse*>(bytes);
fidl_init_txn_header(&resp->hdr, txid, fuchsia_device_manager_DeviceControllerInitOrdinal);
resp->status = return_status;
zx_status_t status =
fidl_encode(&fuchsia_device_manager_DeviceControllerInitResponseTable, bytes, sizeof(*resp),
handles, std::size(handles), &actual_handles, nullptr);
ASSERT_OK(status);
ASSERT_EQ(0, actual_handles);
status = remote.write(0, bytes, sizeof(*resp), nullptr, 0);
ASSERT_OK(status);
}
void MultipleDeviceTestCase::CheckInitReceivedAndReply(const zx::channel& remote,
zx_status_t return_status) {
zx_txid_t txid;
CheckInitReceived(remote, &txid);
SendInitReply(remote, txid, return_status);
}