| // 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 ResumeTestCase : public MultipleDeviceTestCase { |
| public: |
| void ResumeTest(SystemPowerState target_state); |
| void StateTest(zx_status_t resume_status, Device::State want_device_state); |
| }; |
| |
| // Verify the device transitions in and out of the resuming state. |
| void ResumeTestCase::StateTest(zx_status_t resume_status, Device::State want_device_state) { |
| size_t index; |
| ASSERT_NO_FATAL_FAILURES(AddDevice(platform_bus(), "device", 0 /* protocol id */, "", &index)); |
| |
| // Mark all devices suspended. |
| coordinator().sys_device()->set_state(Device::State::kSuspended); |
| coordinator().sys_device()->proxy()->set_state(Device::State::kSuspended); |
| platform_bus()->set_state(Device::State::kSuspended); |
| device(index)->device->set_state(Device::State::kSuspended); |
| ASSERT_NO_FATAL_FAILURES(DoResume(SystemPowerState::kFullyOn)); |
| |
| zx_txid_t txid; |
| ASSERT_NO_FATAL_FAILURES( |
| CheckResumeReceived(sys_proxy_controller_remote_, SystemPowerState::kFullyOn, ZX_OK)); |
| coordinator_loop()->RunUntilIdle(); |
| ASSERT_NO_FATAL_FAILURES( |
| CheckResumeReceived(platform_bus_controller_remote(), SystemPowerState::kFullyOn, ZX_OK)); |
| coordinator_loop()->RunUntilIdle(); |
| // Check for the resume message without replying. |
| ASSERT_NO_FATAL_FAILURES( |
| CheckResumeReceived(device(index)->controller_remote, SystemPowerState::kFullyOn, &txid)); |
| |
| ASSERT_EQ(device(index)->device->state(), Device::State::kResuming); |
| |
| ASSERT_NO_FATAL_FAILURES(SendResumeReply(device(index)->controller_remote, resume_status, txid)); |
| coordinator_loop()->RunUntilIdle(); |
| |
| ASSERT_EQ(device(index)->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; |
| bool suspended = false; |
| bool resumed = 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)); |
| } |
| |
| // Mark all devices suspended. Otherwise resume will fail |
| coordinator().sys_device()->set_state(Device::State::kSuspended); |
| coordinator().sys_device()->proxy()->set_state(Device::State::kSuspended); |
| platform_bus()->set_state(Device::State::kSuspended); |
| for (auto& desc : devices) { |
| fbl::RefPtr<Device> dev; |
| size_t index = desc.index; |
| if (index == UINT32_MAX) { |
| continue; |
| } |
| dev = device(index)->device; |
| if (dev->state() != Device::State::kSuspended) { |
| dev->set_state(Device::State::kSuspended); |
| } |
| } |
| |
| ASSERT_NO_FATAL_FAILURES(DoResume(target_state)); |
| coordinator_loop()->RunUntilIdle(); |
| |
| ASSERT_TRUE(DeviceHasPendingMessages(sys_proxy_controller_remote_)); |
| ASSERT_NO_FATAL_FAILURES(CheckResumeReceived(sys_proxy_controller_remote_, target_state, ZX_OK)); |
| coordinator_loop()->RunUntilIdle(); |
| ASSERT_EQ(coordinator().sys_device()->state(), Device::State::kActive); |
| |
| ASSERT_TRUE(DeviceHasPendingMessages(platform_bus_controller_remote())); |
| ASSERT_NO_FATAL_FAILURES( |
| CheckResumeReceived(platform_bus_controller_remote(), target_state, ZX_OK)); |
| coordinator_loop()->RunUntilIdle(); |
| ASSERT_EQ(platform_bus()->state(), Device::State::kActive); |
| |
| size_t num_to_resume = std::size(devices); |
| while (num_to_resume > 0) { |
| bool made_progress = false; |
| for (size_t i = 0; i < std::size(devices); ++i) { |
| auto& desc = devices[i]; |
| if (desc.resumed) { |
| continue; |
| } |
| |
| if (!DeviceHasPendingMessages(desc.index)) { |
| continue; |
| } |
| ASSERT_NO_FATAL_FAILURES( |
| CheckResumeReceived(device(desc.index)->controller_remote, target_state, ZX_OK)); |
| coordinator_loop()->RunUntilIdle(); |
| |
| size_t parent_index = devices[i].parent_desc_index; |
| // Make sure all descendants of this device are not resumed yet. |
| // We just need to check immediate children since this will |
| // recursively enforce that property. |
| for (auto& other_desc : devices) { |
| if (parent_index == UINT32_MAX) { |
| // Make sure platform bus is resumed. |
| ASSERT_EQ(platform_bus()->state(), Device::State::kActive); |
| } else if (other_desc.index == parent_index) { |
| // Make sure parent is resumed. |
| ASSERT_EQ(device(desc.index)->device->state(), Device::State::kActive); |
| ASSERT_TRUE(other_desc.resumed); |
| } else if (other_desc.parent_desc_index == i) { |
| // if it has children, its state should be Suspended but not Active. |
| ASSERT_NE(device(other_desc.index)->device->state(), Device::State::kActive); |
| ASSERT_FALSE(other_desc.resumed); |
| } |
| } |
| |
| desc.resumed = true; |
| --num_to_resume; |
| made_progress = true; |
| } |
| // Make sure we're not stuck waiting |
| ASSERT_TRUE(made_progress); |
| coordinator_loop()->RunUntilIdle(); |
| } |
| } |
| |
| TEST_F(ResumeTestCase, FullyOnCheckOrder) { |
| ASSERT_NO_FATAL_FAILURES(ResumeTest(SystemPowerState::kFullyOn)); |
| } |
| |
| TEST_F(ResumeTestCase, ResumeSuccess) { |
| ASSERT_NO_FATAL_FAILURES(StateTest(ZX_OK, Device::State::kActive)); |
| } |
| |
| TEST_F(ResumeTestCase, ResumeFail) { |
| ASSERT_NO_FATAL_FAILURES(StateTest(ZX_ERR_BAD_STATE, Device::State::kSuspended)); |
| } |