| /* 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(); | |
| } |