blob: 5fbe9848f4c33b2caa6db5db0797da3e240a2190 [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 "multiple_device_test.h"
class SuspendTestCase : public MultipleDeviceTestCase {
public:
void SuspendTest(uint32_t flags);
void StateTest(zx_status_t suspend_status, 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<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 = std::size(devices);
while (num_to_suspend > 0) {
// Check that platform bus is not suspended yet.
ASSERT_FALSE(DeviceHasPendingMessages(platform_bus_controller_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 < std::size(devices); ++i) {
auto& desc = devices[i];
if (desc.suspended) {
continue;
}
if (!DeviceHasPendingMessages(desc.index)) {
continue;
}
ASSERT_NO_FATAL_FAILURES(
CheckSuspendReceivedAndReply(device(desc.index)->controller_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(
CheckSuspendReceivedAndReply(platform_bus_controller_remote(), flags, ZX_OK));
}
TEST_F(SuspendTestCase, SuspendSuccess) {
ASSERT_NO_FATAL_FAILURES(StateTest(ZX_OK, Device::State::kSuspended));
}
TEST_F(SuspendTestCase, SuspendFail) {
ASSERT_NO_FATAL_FAILURES(StateTest(ZX_ERR_BAD_STATE, Device::State::kActive));
}
// Verify the device transitions in and out of the suspending state.
void SuspendTestCase::StateTest(zx_status_t suspend_status, 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));
zx_txid_t txid;
// Check for the suspend message without replying.
ASSERT_NO_FATAL_FAILURES(CheckSuspendReceived(device(index)->controller_remote, flags, &txid));
ASSERT_EQ(device(index)->device->state(), Device::State::kSuspending);
ASSERT_NO_FATAL_FAILURES(
SendSuspendReply(device(index)->controller_remote, suspend_status, txid));
coordinator_loop()->RunUntilIdle();
ASSERT_EQ(device(index)->device->state(), want_device_state);
}