blob: 91260d454bfa8cecb5ae7f5bc33ae2c6294eaa39 [file] [log] [blame]
// Copyright 2025 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include <assert.h>
#include <lib/unittest/unittest.h>
#include <platform.h>
#include <zircon/errors.h>
#include <arch/interrupt.h>
#include <kernel/auto_preempt_disabler.h>
#include <kernel/mp.h>
#include <kernel/scheduler.h>
namespace {
bool pending_ipi() {
BEGIN_TEST;
if (!platform_supports_suspend_cpu()) {
printf("platform_suspend_cpu is not supported, skipping test\n");
END_TEST;
}
{
AutoPreemptDisabler apd;
InterruptDisableGuard irqd;
const cpu_num_t curr_cpu = arch_curr_cpu_num();
// Pend an interrupt to ourselves to ensure that platform_suspend_cpu returns quickly.
mp_interrupt(mp_ipi_target::MASK, cpu_num_to_mask(curr_cpu));
const zx_status_t status = platform_suspend_cpu(PlatformAllowDomainPowerDown::No);
ASSERT_OK(status);
}
END_TEST;
}
// This test creates two threads, A and B. Each on their own CPU. A will suspend and wait to be
// woken by B. B will wait to observe that A has suspended before waking it via IPI. Because A
// might wake from suspend due to a stray interrupt, we retry the test until it succeeds, an error
// occurs or the timeout has elapsed.
bool one_wakes_another() {
BEGIN_TEST;
constexpr zx_duration_boot_t kTimeout = ZX_SEC(10);
if (!platform_supports_suspend_cpu()) {
printf("platform_suspend_cpu is not supported, skipping test\n");
END_TEST;
}
cpu_mask_t active_cpus = Scheduler::PeekActiveMask();
if (ktl::popcount(active_cpus) <= 1) {
printf("not enough active CPUs, skipping test\n");
END_TEST;
}
enum class State : uint32_t {
READY = 0,
SUSPENDING,
DONE,
};
struct Arg {
const cpu_num_t target;
ktl::atomic<State> target_state;
};
thread_start_routine a_entry = [](void* arg_) -> int {
auto* arg = reinterpret_cast<Arg*>(arg_);
AutoPreemptDisabler apd;
InterruptDisableGuard irqd;
arg->target_state.store(State::SUSPENDING, ktl::memory_order_release);
const zx_status_t status = platform_suspend_cpu(PlatformAllowDomainPowerDown::No);
arg->target_state.store(State::DONE, ktl::memory_order_release);
return status;
};
thread_start_routine b_entry = [](void* arg_) -> int {
auto* arg = reinterpret_cast<Arg*>(arg_);
// Wait until the target reports that it's about to suspend.
State reported;
while ((reported = arg->target_state.load(ktl::memory_order_acquire)) == State::READY) {
arch::Yield();
}
if (reported == State::SUSPENDING) {
// Wait a short amount of time, see that the thread is still suspended, then wake it.
Thread::Current::SleepRelative(ZX_MSEC(1));
if (arg->target_state.load(ktl::memory_order_acquire) == State::SUSPENDING) {
mp_interrupt(mp_ipi_target::MASK, cpu_num_to_mask(arg->target));
return ZX_OK;
}
}
// Somehow the CPU must have returned from suspend.
return ZX_ERR_BAD_STATE;
};
// First, select the CPU that will wake the other. Choose the boot CPU because we know it's must
// be active and because we want the suspending CPU to be one of the secondaries since they
// receive fewer (if any) hardware interrupts (SPIs).
ASSERT_TRUE(active_cpus & cpu_num_to_mask(BOOT_CPU_ID));
const cpu_num_t cpu_b = BOOT_CPU_ID;
active_cpus &= ~cpu_num_to_mask(BOOT_CPU_ID);
const cpu_num_t cpu_a = remove_cpu_from_mask(active_cpus);
const auto deadline = Deadline::after_boot(kTimeout);
for (size_t attempt = 0; current_boot_time() < deadline.when(); ++attempt) {
printf("attempt %zu\n", attempt);
Arg arg(cpu_a, State::READY);
Thread* a = Thread::Create("one_wakes_another-a", a_entry, &arg, DEFAULT_PRIORITY);
ASSERT_NONNULL(a);
a->SetCpuAffinity(cpu_num_to_mask(cpu_a));
a->Resume();
Thread* b = Thread::Create("one_wakes_another-b", b_entry, &arg, DEFAULT_PRIORITY);
ASSERT_NONNULL(a);
b->SetCpuAffinity(cpu_num_to_mask(cpu_b));
b->Resume();
zx_status_t a_status;
a->Join(&a_status, ZX_TIME_INFINITE);
ASSERT_OK(a_status);
zx_status_t b_status;
b->Join(&b_status, ZX_TIME_INFINITE);
// Perhaps CPU-A woke from suspend early. Try again.
if (b_status == ZX_ERR_BAD_STATE) {
continue;
}
ASSERT_OK(b_status);
END_TEST;
}
printf("failed to complete successfully before timeout\n");
ASSERT_TRUE(false);
END_TEST;
}
} // namespace
UNITTEST_START_TESTCASE(platform_suspend_cpu_tests)
UNITTEST("pending_ipi", pending_ipi)
UNITTEST("one_wakes_another", one_wakes_another)
UNITTEST_END_TESTCASE(platform_suspend_cpu_tests, "platform_suspend_cpu_tests",
"platform_suspend_cpu tests")