From 23dc390cc7341538955cf3fedd2f3ca535911d68 Mon Sep 17 00:00:00 2001 From: Peter Mitsis Date: Thu, 28 Mar 2024 10:57:49 -0400 Subject: [PATCH] tests: Add IPI_OPTIMIZE tests Adds several tests to verify that IPIs can be appropriately targeted to specific CPUs. Signed-off-by: Peter Mitsis --- tests/kernel/ipi_optimize/CMakeLists.txt | 12 + .../qemu_cortex_a53_qemu_cortex_a53_smp.conf | 4 + ...emu_cortex_a53_qemu_cortex_a53_smp.overlay | 19 + tests/kernel/ipi_optimize/prj.conf | 5 + tests/kernel/ipi_optimize/src/main.c | 475 ++++++++++++++++++ tests/kernel/ipi_optimize/testcase.yaml | 6 + 6 files changed, 521 insertions(+) create mode 100644 tests/kernel/ipi_optimize/CMakeLists.txt create mode 100644 tests/kernel/ipi_optimize/boards/qemu_cortex_a53_qemu_cortex_a53_smp.conf create mode 100644 tests/kernel/ipi_optimize/boards/qemu_cortex_a53_qemu_cortex_a53_smp.overlay create mode 100644 tests/kernel/ipi_optimize/prj.conf create mode 100644 tests/kernel/ipi_optimize/src/main.c create mode 100644 tests/kernel/ipi_optimize/testcase.yaml diff --git a/tests/kernel/ipi_optimize/CMakeLists.txt b/tests/kernel/ipi_optimize/CMakeLists.txt new file mode 100644 index 000000000000000..f32de519289c3bd --- /dev/null +++ b/tests/kernel/ipi_optimize/CMakeLists.txt @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(smp) + +target_sources(app PRIVATE src/main.c) + +target_include_directories(app PRIVATE + ${ZEPHYR_BASE}/kernel/include + ${ZEPHYR_BASE}/arch/${ARCH}/include + ) diff --git a/tests/kernel/ipi_optimize/boards/qemu_cortex_a53_qemu_cortex_a53_smp.conf b/tests/kernel/ipi_optimize/boards/qemu_cortex_a53_qemu_cortex_a53_smp.conf new file mode 100644 index 000000000000000..f0ee34b467edd44 --- /dev/null +++ b/tests/kernel/ipi_optimize/boards/qemu_cortex_a53_qemu_cortex_a53_smp.conf @@ -0,0 +1,4 @@ +# Copyright (c) 2022 Carlo Caione +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_MP_MAX_NUM_CPUS=4 diff --git a/tests/kernel/ipi_optimize/boards/qemu_cortex_a53_qemu_cortex_a53_smp.overlay b/tests/kernel/ipi_optimize/boards/qemu_cortex_a53_qemu_cortex_a53_smp.overlay new file mode 100644 index 000000000000000..5bb497069dd8fdb --- /dev/null +++ b/tests/kernel/ipi_optimize/boards/qemu_cortex_a53_qemu_cortex_a53_smp.overlay @@ -0,0 +1,19 @@ +/* Copyright 2022 Carlo Caione + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + cpus { + cpu@2 { + device_type = "cpu"; + compatible = "arm,cortex-a53"; + reg = <2>; + }; + + cpu@3 { + device_type = "cpu"; + compatible = "arm,cortex-a53"; + reg = <3>; + }; + }; +}; diff --git a/tests/kernel/ipi_optimize/prj.conf b/tests/kernel/ipi_optimize/prj.conf new file mode 100644 index 000000000000000..f337c89ff5bb413 --- /dev/null +++ b/tests/kernel/ipi_optimize/prj.conf @@ -0,0 +1,5 @@ +CONFIG_ZTEST=y +CONFIG_SMP=y +CONFIG_TRACE_SCHED_IPI=y +CONFIG_IPI_OPTIMIZE=y +CONFIG_SYS_CLOCK_TICKS_PER_SEC=50 diff --git a/tests/kernel/ipi_optimize/src/main.c b/tests/kernel/ipi_optimize/src/main.c new file mode 100644 index 000000000000000..029b79b6d3a85a4 --- /dev/null +++ b/tests/kernel/ipi_optimize/src/main.c @@ -0,0 +1,475 @@ +/* + * Copyright (c) 2024 Intel Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include + +#define STACK_SIZE (1024 + CONFIG_TEST_EXTRA_STACK_SIZE) + +#define NUM_THREADS (CONFIG_MP_MAX_NUM_CPUS - 1) + +#define DELAY_FOR_IPIS 200 + +static struct k_thread thread[NUM_THREADS]; +static struct k_thread alt_thread; + +static bool alt_thread_created; + +static K_THREAD_STACK_ARRAY_DEFINE(stack, NUM_THREADS, STACK_SIZE); +static K_THREAD_STACK_DEFINE(alt_stack, STACK_SIZE); + +static uint32_t ipi_count[CONFIG_MP_MAX_NUM_CPUS]; +static struct k_spinlock ipilock; +static atomic_t busy_started; +static volatile bool alt_thread_done; + +static K_SEM_DEFINE(sem, 0, 1); + +void z_trace_sched_ipi(void) +{ + k_spinlock_key_t key; + + key = k_spin_lock(&ipilock); + ipi_count[_current_cpu->id]++; + k_spin_unlock(&ipilock, key); +} + +static void clear_ipi_counts(void) +{ + k_spinlock_key_t key; + + key = k_spin_lock(&ipilock); + memset(ipi_count, 0, sizeof(ipi_count)); + k_spin_unlock(&ipilock, key); +} + +static void get_ipi_counts(uint32_t *set, size_t n_elem) +{ + k_spinlock_key_t key; + + key = k_spin_lock(&ipilock); + memcpy(set, ipi_count, n_elem * sizeof(*set)); + k_spin_unlock(&ipilock, key); +} + +static void busy_thread_entry(void *p1, void *p2, void *p3) +{ + int key; + uint32_t id; + + key = arch_irq_lock(); + id = _current_cpu->id; + arch_irq_unlock(key); + + atomic_or(&busy_started, BIT(id)); + + while (1) { + } +} + +static bool wait_until_busy_threads_ready(uint32_t id) +{ + uint32_t all; + uint32_t value; + unsigned int i; + + all = IPI_ALL_CPUS_MASK ^ BIT(id); + for (i = 0; i < 10; i++) { + k_busy_wait(1000); + + value = (uint32_t)atomic_get(&busy_started); + if (value == all) { + break; + } + } + + return (i < 10); +} + +static void pending_thread_entry(void *p1, void *p2, void *p3) +{ + int key; + + k_sem_take(&sem, K_FOREVER); + + while (!alt_thread_done) { + key = arch_irq_lock(); + arch_spin_relax(); + arch_irq_unlock(key); + } +} + +static void alt_thread_create(int priority, const char *desc) +{ + k_thread_create(&alt_thread, alt_stack, STACK_SIZE, + pending_thread_entry, NULL, NULL, NULL, + priority, 0, K_NO_WAIT); + alt_thread_created = true; + + /* Verify alt_thread is pending */ + + k_busy_wait(10000); + zassert_true(z_is_thread_pending(&alt_thread), + "%s priority thread has not pended.\n", desc); +} + +uint32_t busy_threads_create(int priority) +{ + unsigned int i; + uint32_t id; + int key; + + atomic_clear(&busy_started); + + for (i = 0; i < NUM_THREADS; i++) { + k_thread_create(&thread[i], stack[i], STACK_SIZE, + busy_thread_entry, NULL, NULL, NULL, + priority, 0, K_NO_WAIT); + } + + /* Align to tick boundary to minimize probability of timer ISRs */ + + k_sleep(K_TICKS(1)); + key = arch_irq_lock(); + id = _current_cpu->id; + arch_irq_unlock(key); + + /* + * Spin until all busy threads are ready. It is assumed that as this + * thread and the busy threads are cooperative that they will not be + * rescheduled to execute on a different CPU. + */ + + zassert_true(wait_until_busy_threads_ready(id), + "1 or more 'busy threads' not ready.\n"); + + return id; +} + +void busy_threads_priority_set(int priority, int delta) +{ + unsigned int i; + + for (i = 0; i < NUM_THREADS; i++) { + k_thread_priority_set(&thread[i], priority); + priority += delta; + } +} + +/** + * Verify that arch_sched_broadcast_ipi() broadcasts IPIs as expected. + */ +ZTEST(ipi, test_arch_sched_broadcast_ipi) +{ + uint32_t set[CONFIG_MP_MAX_NUM_CPUS]; + uint32_t id; + int priority; + unsigned int j; + + priority = k_thread_priority_get(k_current_get()); + + id = busy_threads_create(priority - 1); + + /* Broadcast the IPI. All other CPUs ought to receive and process it */ + + clear_ipi_counts(); + arch_sched_broadcast_ipi(); + k_busy_wait(DELAY_FOR_IPIS); + get_ipi_counts(set, CONFIG_MP_MAX_NUM_CPUS); + + for (j = 0; j < CONFIG_MP_MAX_NUM_CPUS; j++) { + if (id == j) { + zassert_true(set[j] == 0, + "Broadcast-Expected 0, got %u\n", + set[j]); + } else { + zassert_true(set[j] == 1, + "Broadcast-Expected 1, got %u\n", + set[j]); + } + } +} + +#ifdef CONFIG_ARCH_HAS_DIRECTED_IPIS +/** + * Verify that arch_sched_directed_ipi() directs IPIs as expected. + */ +ZTEST(ipi, test_arch_sched_directed_ipi) +{ + uint32_t set[CONFIG_MP_MAX_NUM_CPUS]; + uint32_t id; + int priority; + unsigned int j; + + priority = k_thread_priority_get(k_current_get()); + + id = busy_threads_create(priority - 1); + + /* + * Send an IPI to each CPU, one at a time. Verify that only the + * targeted CPU received the IPI. + */ + for (unsigned int i = 0; i < CONFIG_MP_MAX_NUM_CPUS; i++) { + if (i == id) { + continue; + } + + clear_ipi_counts(); + arch_sched_directed_ipi(BIT(i)); + k_busy_wait(DELAY_FOR_IPIS); + get_ipi_counts(set, CONFIG_MP_MAX_NUM_CPUS); + + for (j = 0; j < CONFIG_MP_MAX_NUM_CPUS; j++) { + if (i == j) { + zassert_true(set[j] == 1, + "Direct-Expected 1, got %u\n", + set[j]); + } else { + zassert_true(set[j] == 0, + "Direct-Expected 0, got %u\n", + set[j]); + } + } + } +} +#endif + +/** + * Verify that waking a thread whose priority is lower than any other + * currently executing thread does not result in any IPIs being sent. + */ +ZTEST(ipi, test_low_thread_wakes_no_ipis) +{ + uint32_t set[CONFIG_MP_MAX_NUM_CPUS]; + uint32_t id; + int priority; + unsigned int i; + + priority = k_thread_priority_get(k_current_get()); + atomic_clear(&busy_started); + + alt_thread_create(5, "Low"); + + id = busy_threads_create(priority - 1); + + /* + * Lower the priority of the busy threads now that we know that they + * have started. As this is expected to generate IPIs, busy wait for + * some small amount of time to give them time to be processed. + */ + + busy_threads_priority_set(0, 0); + k_busy_wait(DELAY_FOR_IPIS); + + /* + * Low priority thread is pended. Current thread is cooperative. + * Other CPUs are executing preemptible threads @ priority 0. + */ + + clear_ipi_counts(); + k_sem_give(&sem); + k_busy_wait(DELAY_FOR_IPIS); + get_ipi_counts(set, CONFIG_MP_MAX_NUM_CPUS); + + zassert_true(z_is_thread_ready(&alt_thread), + "Low priority thread is not ready.\n"); + + alt_thread_done = true; + + for (i = 0; i < CONFIG_MP_MAX_NUM_CPUS; i++) { + zassert_true(set[i] == 0, + "CPU %u unexpectedly received IPI.\n", i); + } +} + +/** + * Verify that waking a thread whose priority is higher than all currently + * executing threads results in the proper IPIs being sent and processed. + */ +ZTEST(ipi, test_high_thread_wakes_some_ipis) +{ + uint32_t set[CONFIG_MP_MAX_NUM_CPUS]; + uint32_t id; + int priority; + unsigned int i; + + priority = k_thread_priority_get(k_current_get()); + atomic_clear(&busy_started); + + alt_thread_create(priority - 1 - NUM_THREADS, "High"); + + id = busy_threads_create(priority - 1); + + /* + * Lower the priority of the busy threads now that we know that they + * have started and are busy waiting. As this is expected to generate + * IPIs, busy wait for some small amount of time to give them time to + * be processed. + */ + + busy_threads_priority_set(0, 1); + k_busy_wait(DELAY_FOR_IPIS); + + /* + * High priority thread is pended. Current thread is cooperative. + * Other CPUs are executing preemptible threads. + */ + + clear_ipi_counts(); + k_sem_give(&sem); + k_busy_wait(DELAY_FOR_IPIS); + get_ipi_counts(set, CONFIG_MP_MAX_NUM_CPUS); + + zassert_true(z_is_thread_ready(&alt_thread), + "High priority thread is not ready.\n"); + + alt_thread_done = true; + + for (i = 0; i < CONFIG_MP_MAX_NUM_CPUS; i++) { + if (i == id) { + continue; + } + + zassert_true(set[i] == 1, "CPU%u got %u IPIs", i, set[i]); + } + + zassert_true(set[id] == 0, "Current CPU got %u IPI(s).\n", set[id]); +} + +/** + * Verify that lowering the priority of an active thread results in an IPI. + * If directed IPIs are enabled, then only the CPU executing that active + * thread ought to receive the IPI. Otherwise if IPIs are broadcast, then all + * other CPUs save the current CPU ought to receive IPIs. + */ +ZTEST(ipi, test_thread_priority_set_lower) +{ + uint32_t set[CONFIG_MP_MAX_NUM_CPUS]; + uint32_t id; + int priority; + unsigned int i; + + priority = k_thread_priority_get(k_current_get()); + + id = busy_threads_create(priority - 1); + + clear_ipi_counts(); + k_thread_priority_set(&thread[0], priority); + k_busy_wait(DELAY_FOR_IPIS); + get_ipi_counts(set, CONFIG_MP_MAX_NUM_CPUS); + + for (i = 0; i < CONFIG_MP_MAX_NUM_CPUS; i++) { + if (i == id) { + continue; + } + +#ifdef CONFIG_ARCH_HAS_DIRECTED_IPIS + unsigned int j; + + for (j = 0; j < NUM_THREADS; j++) { + if (_kernel.cpus[i].current == &thread[j]) { + break; + } + } + + zassert_true(j < NUM_THREADS, + "CPU%u not executing expected thread\n", i); + + if (j == 0) { + zassert_true(set[i] == 1, "CPU%u got %u IPIs.\n", + i, set[i]); + } else { + zassert_true(set[i] == 0, "CPU%u got %u IPI(s).\n", + i, set[i]); + } +#else + zassert_true(set[i] == 1, "CPU%u got %u IPIs", i, set[i]); +#endif + } + + zassert_true(set[id] == 0, "Current CPU got %u IPI(s).\n", set[id]); +} + +/* + * Verify that IPIs are not sent to CPUs that are executing cooperative + * threads. + */ +ZTEST(ipi, test_thread_coop_no_ipis) +{ + uint32_t set[CONFIG_MP_MAX_NUM_CPUS]; + uint32_t id; + int priority; + unsigned int i; + + priority = k_thread_priority_get(k_current_get()); + atomic_clear(&busy_started); + + alt_thread_create(priority - 1 - NUM_THREADS, "High"); + + id = busy_threads_create(priority - 1); + + /* + * High priority thread is pended. Current thread is cooperative. + * Other CPUs are executing lower priority cooperative threads. + */ + + clear_ipi_counts(); + k_sem_give(&sem); + k_busy_wait(DELAY_FOR_IPIS); + get_ipi_counts(set, CONFIG_MP_MAX_NUM_CPUS); + + zassert_true(z_is_thread_ready(&alt_thread), + "High priority thread is not ready.\n"); + + alt_thread_done = true; + + for (i = 0; i < CONFIG_MP_MAX_NUM_CPUS; i++) { + zassert_true(set[i] == 0, "CPU%u got %u IPIs", i, set[i]); + } +} + +static void *ipi_tests_setup(void) +{ + /* + * Sleep a bit to guarantee that all CPUs enter an idle thread + * from which they can exit correctly to run the test. + */ + + k_sleep(K_MSEC(20)); + + return NULL; +} + +static void cleanup_threads(void *fixture) +{ + unsigned int i; + + ARG_UNUSED(fixture); + + /* + * Ensure that spawned busy threads are aborted before + * proceeding to the next test. + */ + + for (i = 0; i < NUM_THREADS; i++) { + k_thread_abort(&thread[i]); + } + + /* Ensure alt_thread ,if it was created, also gets aborted */ + + if (alt_thread_created) { + k_thread_abort(&alt_thread); + } + alt_thread_created = false; + + alt_thread_done = false; +} + +ZTEST_SUITE(ipi, NULL, ipi_tests_setup, NULL, cleanup_threads, NULL); diff --git a/tests/kernel/ipi_optimize/testcase.yaml b/tests/kernel/ipi_optimize/testcase.yaml new file mode 100644 index 000000000000000..49227a720cbac83 --- /dev/null +++ b/tests/kernel/ipi_optimize/testcase.yaml @@ -0,0 +1,6 @@ +tests: + kernel.ipi_optimize.smp: + tags: + - kernel + - smp + filter: (CONFIG_MP_MAX_NUM_CPUS > 1)