/* SPDX-License-Identifier: LGPL-2.0-only */ /* * SMP and IPI Tests * * Copyright 2024 Nicholas Piggin, IBM Corp. */ #include #include #include #include #include #include #include #include #include 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(); }