blob: 4f7c862b1b865fef3d0a9ed6ceb2d844ff1bb103 [file] [log] [blame]
// Copyright 2018 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 <inttypes.h>
#include <lib/unittest/unittest.h>
#include <platform.h>
#include <zircon/errors.h>
#include <zircon/types.h>
#include <dev/power.h>
#include <kernel/cpu.h>
#include <kernel/mp.h>
#include <kernel/thread.h>
#include <ktl/iterator.h>
#include "tests.h"
#include <ktl/enforce.h>
static int resume_cpu_test_thread(void* arg) {
*reinterpret_cast<cpu_num_t*>(arg) = arch_curr_cpu_num();
return 0;
}
// "Unplug" online secondary (non-BOOT) cores
static zx_status_t unplug_all_cores() {
cpu_mask_t cpumask = mp_get_online_mask() & ~cpu_num_to_mask(BOOT_CPU_ID);
return mp_unplug_cpu_mask(cpumask, ZX_TIME_INFINITE);
}
static zx_status_t hotplug_core(cpu_num_t i) {
cpu_mask_t cpumask = cpu_num_to_mask(i);
return mp_hotplug_cpu_mask(cpumask);
}
static unsigned get_num_cpus_online() {
unsigned count = 0;
cpu_mask_t online = mp_get_online_mask();
while (online) {
online >>= 1;
++count;
}
return count;
}
static zx_status_t wait_for_cpu_offline(cpu_num_t i) {
zx_time_t print_time = zx_time_add_duration(current_time(), ZX_SEC(5));
while (true) {
zx::result<power_cpu_state> res = platform_get_cpu_state(i);
if (res.is_error()) {
if (res.error_value() == ZX_ERR_NOT_SUPPORTED) {
// x86 does not implement platform_get_cpu_state, so return OK if the call returns
// ZX_ERR_NOT_SUPPORTED.
return ZX_OK;
}
return res.error_value();
} else if (res.value() == power_cpu_state::OFF || res.value() == power_cpu_state::STOPPED) {
return ZX_OK;
}
if (current_time() > print_time) {
print_time = zx_time_add_duration(current_time(), ZX_SEC(5));
printf("Still waiting for CPU %u to go offline, waiting 5 more seconds\n", i);
}
Thread::Current::SleepRelative(ZX_USEC(200));
}
}
static void wait_for_cpu_active(cpu_num_t i) {
zx_time_t print_time = zx_time_add_duration(current_time(), ZX_SEC(5));
while (true) {
if (mp_is_cpu_active(i)) {
return;
}
if (current_time() > print_time) {
print_time = zx_time_add_duration(current_time(), ZX_SEC(5));
printf("Still waiting for CPU %u to become active, waiting 5 more seconds\n", i);
}
Thread::Current::SleepRelative(ZX_USEC(200));
}
}
// Unplug all cores (except for Boot core), then hotplug
// the cores one by one and make sure that we can schedule
// tasks on that core.
[[maybe_unused]] static bool mp_hotplug_test() {
BEGIN_TEST;
// TODO(https://fxbug.dev/42086046): Re-enable test on RISC-V.
#if defined(__riscv)
printf("skipping test mp_hotplug, hotplug only suported on x64 and arm64\n");
END_TEST;
#endif
uint num_cores = get_num_cpus_online();
if (num_cores < 2) {
printf("skipping test mp_hotplug, not enough online cpus\n");
END_TEST;
}
Thread::Current::MigrateToCpu(BOOT_CPU_ID);
// "Unplug" online secondary (non-BOOT) cores
ASSERT_OK(unplug_all_cores(), "unplugging all cores failed");
for (cpu_num_t i = 0; i < num_cores; i++) {
if (i == BOOT_CPU_ID) {
continue;
}
// Wait until this core is fully offline.
ASSERT_OK(wait_for_cpu_offline(i), "waiting for core to go offline failed");
// Hotplug this core.
ASSERT_OK(hotplug_core(i), "hotplugging core failed");
// Wait until the core is active.
wait_for_cpu_active(i);
// Create a thread, affine it to the core just hotplugged
// and make sure the thread does get scheduled there.
cpu_num_t running_core;
Thread* nt = Thread::Create("resume-test-thread", resume_cpu_test_thread, &running_core,
DEFAULT_PRIORITY);
ASSERT_NE(nullptr, nt, "Thread create failed");
nt->SetCpuAffinity(cpu_num_to_mask(i));
nt->SetMigrateFn([](auto...) {});
nt->Resume();
ASSERT_OK(nt->Join(nullptr, ZX_TIME_INFINITE), "thread join failed");
ASSERT_EQ(i, running_core, "Thread not running on hotplugged core");
}
END_TEST;
}
UNITTEST_START_TESTCASE(mp_hotplug_tests)
UNITTEST("test unplug and hotplug cores one by one", mp_hotplug_test)
UNITTEST_END_TESTCASE(mp_hotplug_tests, "hotplug", "Tests for unplugging and hotplugging cores")