blob: 644cf1c75059a92e01d67d7e0e43bdf4eac1271b [file] [log] [blame]
/* SPDX-License-Identifier: LGPL-2.0-only */
/*
* SMP and IPI Tests
*
* Copyright 2024 Nicholas Piggin, IBM Corp.
*/
#include <libcflat.h>
#include <asm/atomic.h>
#include <asm/barrier.h>
#include <asm/processor.h>
#include <asm/time.h>
#include <asm/smp.h>
#include <asm/setup.h>
#include <asm/ppc_asm.h>
#include <devicetree.h>
static volatile bool start_test_running = true;
static volatile int nr_cpus_started;
static void start_fn(int cpu_id)
{
atomic_fetch_inc(&nr_cpus_started);
while (start_test_running)
cpu_relax();
atomic_fetch_dec(&nr_cpus_started);
}
static void test_start_cpus(int argc, char **argv)
{
uint64_t tb;
if (argc > 2)
report_abort("Unsupported argument: '%s'", argv[2]);
nr_cpus_started = 1;
if (!start_all_cpus(start_fn))
report_abort("Failed to start secondary cpus");
tb = get_tb();
while (nr_cpus_started < nr_cpus_present) {
cpu_relax();
if (get_tb() - tb > tb_hz * 5)
report_abort("Failed to start all secondaries");
}
if (nr_cpus_started != nr_cpus_online)
report_abort("Started CPUs does not match online");
barrier();
start_test_running = false;
barrier();
tb = get_tb();
while (nr_cpus_started > 1) {
cpu_relax();
if (get_tb() - tb > tb_hz * 5)
report_abort("Failed to stop all secondaries");
}
stop_all_cpus();
report(true, "start cpus");
}
static volatile int nr_cpus_ipi = 0;
static void ipi_handler(struct pt_regs *regs, void *data)
{
atomic_fetch_inc(&nr_cpus_ipi);
}
static volatile bool ipi_test_running = true;
static void ipi_fn(int cpu_id)
{
local_ipi_enable();
mtspr(SPR_DEC, 0x7fffffff);
local_irq_enable();
while (ipi_test_running)
cpu_relax();
local_irq_disable();
local_ipi_disable();
}
static void test_ipi_cpus(int argc, char **argv)
{
uint64_t tb;
int i;
if (argc > 2)
report_abort("Unsupported argument: '%s'", argv[2]);
if (nr_cpus_present < 2) {
report_skip("Requires SMP (2 or more CPUs)");
return;
}
register_ipi(ipi_handler, NULL);
if (!start_all_cpus(ipi_fn))
report_abort("Failed to start secondary cpus");
for (i = 1; i < nr_cpus_online; i++)
send_ipi(cpus[i].server_no);
tb = get_tb();
while (nr_cpus_ipi < nr_cpus_online - 1) {
cpu_relax();
if (get_tb() - tb > tb_hz * 5)
report_abort("Secondaries failed to respond to IPIs");
}
send_ipi(cpus[1].server_no);
tb = get_tb();
while (nr_cpus_ipi < nr_cpus_online) {
cpu_relax();
if (get_tb() - tb > tb_hz * 5)
report_abort("Secondaries failed to respond to IPIs");
}
ipi_test_running = false;
stop_all_cpus();
assert(nr_cpus_ipi == nr_cpus_present);
unregister_ipi();
report(true, "IPI cpus");
}
static uint64_t time;
static bool time_went_backward;
static void check_and_record_time(void)
{
uint64_t tb;
uint64_t t;
uint64_t old;
t = time;
again:
barrier();
tb = get_tb();
asm volatile("1: ldarx %0,0,%1 ; cmpd %0,%2 ; bne 2f ; stdcx. %3,0,%1 ; bne- 1b; 2:" : "=&r"(old) : "r"(&time), "r"(t), "r"(tb) : "memory", "cr0");
assert(tb >= t);
if (old != t) {
t = old;
goto again;
}
if (old > tb)
time_went_backward = true;
}
static void update_time(int64_t tb_offset)
{
uint64_t new_tb;
new_tb = get_tb() + tb_offset;
mtspr(SPR_TBU40, new_tb);
if ((get_tb() & 0xFFFFFF) < (new_tb & 0xFFFFFF)) {
new_tb += 0x1000000;
mtspr(SPR_TBU40, new_tb);
}
}
static void time_sync_fn(int cpu_id)
{
uint64_t start = get_tb();
while (!time_went_backward && get_tb() - start < tb_hz*2) {
check_and_record_time();
cpu_relax();
}
while (!time_went_backward && get_tb() - start < tb_hz*2) {
check_and_record_time();
udelay(1);
}
if (machine_is_powernv()) {
while (!time_went_backward && get_tb() - start < tb_hz*2) {
check_and_record_time();
update_time(0x1234000000);
cpu_relax();
update_time(-0x1234000000);
}
}
}
static void test_time_sync(int argc, char **argv)
{
if (argc > 2)
report_abort("Unsupported argument: '%s'", argv[2]);
if (nr_cpus_present < 2) {
report_skip("Requires SMP (2 or more CPUs)");
return;
}
time_went_backward = false;
if (!start_all_cpus(time_sync_fn))
report_abort("Failed to start secondary cpus");
time_sync_fn(-1);
stop_all_cpus();
report(!time_went_backward, "time sync");
}
static volatile bool relax_test_running = true;
static int relax_loop_count[NR_CPUS];
static void relax_fn(int cpu_id)
{
volatile int i = 0;
while (relax_test_running) {
cpu_relax();
i++;
}
relax_loop_count[cpu_id] = i;
}
#define ITERS 1000000
static void test_relax(int argc, char **argv)
{
volatile int i;
int count;
if (argc > 2)
report_abort("Unsupported argument: '%s'", argv[2]);
if (nr_cpus_present < 2) {
report_skip("Requires SMP (2 or more CPUs)");
return;
}
if (!start_all_cpus(relax_fn))
report_abort("Failed to start secondary cpus");
for (i = 0; i < ITERS; i++)
;
relax_test_running = false;
stop_all_cpus();
count = 0;
for (i = 0; i < NR_CPUS; i++)
count += relax_loop_count[i];
if (count == 0)
count = 1;
report(true, "busy-loops on CPU:%d vs cpu_relax-loops on others %ld%%", smp_processor_id(), (long)ITERS * 100 / count);
}
static volatile bool pause_test_running = true;
static int pause_loop_count[NR_CPUS];
static void pause_fn(int cpu_id)
{
volatile int i = 0;
while (pause_test_running) {
pause_short();
i++;
}
pause_loop_count[cpu_id] = i;
}
#define ITERS 1000000
static void test_pause(int argc, char **argv)
{
volatile int i;
int count;
if (argc > 2)
report_abort("Unsupported argument: '%s'", argv[2]);
if (!cpu_has_pause_short)
return;
if (nr_cpus_present < 2) {
report_skip("Requires SMP (2 or more CPUs)");
return;
}
if (!start_all_cpus(pause_fn))
report_abort("Failed to start secondary cpus");
for (i = 0; i < ITERS; i++)
;
pause_test_running = false;
stop_all_cpus();
count = 0;
for (i = 0; i < NR_CPUS; i++)
count += pause_loop_count[i];
report(true, "busy-loops on CPU:%d vs pause_short-loops on others %ld%%", smp_processor_id(), (long)ITERS * 100 / count);
}
struct {
const char *name;
void (*func)(int argc, char **argv);
} hctests[] = {
{ "start_cpus", test_start_cpus },
{ "ipi_cpus", test_ipi_cpus },
{ "time_sync", test_time_sync },
{ "cpu_relax", test_relax },
{ "pause", test_pause },
{ NULL, NULL }
};
int main(int argc, char **argv)
{
bool all;
int i;
all = argc == 1 || !strcmp(argv[1], "all");
report_prefix_push("smp");
for (i = 0; hctests[i].name != NULL; i++) {
if (all || strcmp(argv[1], hctests[i].name) == 0) {
report_prefix_push(hctests[i].name);
hctests[i].func(argc, argv);
report_prefix_pop();
}
}
report_prefix_pop();
return report_summary();
}