| /* |
| * Copyright (c) 2021 Intel Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/ztest.h> |
| #include <zephyr/irq_offload.h> |
| #include <zephyr/interrupt_util.h> |
| #if defined(CONFIG_ARCH_POSIX) |
| #include <soc.h> |
| #endif |
| |
| #define STACK_SIZE 1024 |
| #define NUM_WORK 4 |
| |
| static struct k_work offload_work[NUM_WORK]; |
| static struct k_work_q wq_queue; |
| static K_THREAD_STACK_DEFINE(wq_stack, STACK_SIZE); |
| static K_THREAD_STACK_DEFINE(tstack, STACK_SIZE); |
| static struct k_thread tdata; |
| |
| static struct k_sem sync_sem; |
| static struct k_sem end_sem; |
| static bool wait_for_end; |
| static atomic_t submit_success; |
| static atomic_t offload_job_cnt; |
| |
| /* |
| * This global variable control if the priority of offload job |
| * is greater than the original thread. |
| */ |
| static bool offload_job_prio_higher; |
| |
| static volatile int orig_t_keep_run; |
| |
| /* record the initialized interrupt vector for reuse */ |
| static int vector_num; |
| |
| enum { |
| TEST_OFFLOAD_MULTI_JOBS, |
| TEST_OFFLOAD_IDENTICAL_JOBS |
| }; |
| |
| struct interrupt_param { |
| struct k_work *work; |
| }; |
| |
| static struct interrupt_param irq_param; |
| |
| /* thread entry of doing offload job */ |
| static void entry_offload_job(struct k_work *work) |
| { |
| if (offload_job_prio_higher) { |
| /*TESTPOINT: offload thread run right after irq end.*/ |
| zassert_equal(orig_t_keep_run, 0, |
| "the offload did not run immediately."); |
| } else { |
| /*TESTPOINT: original thread run right after irq end.*/ |
| zassert_equal(orig_t_keep_run, 1, |
| "the offload did not run immediately."); |
| } |
| |
| atomic_inc(&offload_job_cnt); |
| k_sem_give(&end_sem); |
| } |
| |
| /* offload work to work queue */ |
| void isr_handler(const void *param) |
| { |
| struct k_work *work = ((struct interrupt_param *)param)->work; |
| |
| zassert_not_null(work, "kwork should not be NULL"); |
| |
| orig_t_keep_run = 0; |
| |
| /* If the work is busy, we don't submit it. */ |
| if (!k_work_busy_get(work)) { |
| zassert_equal(k_work_submit_to_queue(&wq_queue, work), |
| 1, "kwork not submitted or queued"); |
| |
| atomic_inc(&submit_success); |
| } |
| } |
| |
| #if defined(CONFIG_DYNAMIC_INTERRUPTS) |
| /* |
| * So far, we only test x86 and arch posix by real dynamic interrupt. |
| * Other arch will be add later. |
| */ |
| #if defined(CONFIG_X86) |
| #define TEST_IRQ_DYN_LINE 26 |
| |
| #elif defined(CONFIG_ARCH_POSIX) |
| #if defined(OFFLOAD_SW_IRQ) |
| #define TEST_IRQ_DYN_LINE OFFLOAD_SW_IRQ |
| #else |
| #define TEST_IRQ_DYN_LINE 0 |
| #endif |
| |
| #else |
| #define TEST_IRQ_DYN_LINE 0 |
| #endif |
| |
| #endif |
| |
| static void init_dyn_interrupt(void) |
| { |
| /* If we cannot get a dynamic interrupt, skip test. */ |
| if (TEST_IRQ_DYN_LINE == 0) { |
| ztest_test_skip(); |
| } |
| |
| /* We just initialize dynamic interrupt once, then reuse them */ |
| if (!vector_num) { |
| vector_num = irq_connect_dynamic(TEST_IRQ_DYN_LINE, 1, |
| isr_handler, (void *)&irq_param, 0); |
| } |
| |
| TC_PRINT("vector(%d)\n", vector_num); |
| zassert_true(vector_num > 0, "no vector can be used"); |
| irq_enable(TEST_IRQ_DYN_LINE); |
| } |
| |
| static void trigger_offload_interrupt(const bool real_irq, void *work) |
| { |
| irq_param.work = work; |
| |
| if (real_irq) { |
| trigger_irq(vector_num); |
| } else { |
| irq_offload((irq_offload_routine_t)&isr_handler, &irq_param); |
| } |
| } |
| |
| static void t_running(void *p1, void *p2, void *p3) |
| { |
| ARG_UNUSED(p1); |
| ARG_UNUSED(p2); |
| ARG_UNUSED(p3); |
| |
| k_sem_give(&sync_sem); |
| |
| while (wait_for_end == false) { |
| orig_t_keep_run = 1; |
| k_usleep(150); |
| } |
| } |
| |
| static void init_env(int real_irq) |
| { |
| static bool wq_already_start; |
| |
| /* semaphore used to sync the end */ |
| k_sem_init(&sync_sem, 0, 1); |
| k_sem_init(&end_sem, 0, NUM_WORK); |
| |
| /* initialize global variables */ |
| submit_success = 0; |
| offload_job_cnt = 0; |
| orig_t_keep_run = 0; |
| wait_for_end = false; |
| |
| /* initialize the dynamic interrupt while using it */ |
| if (real_irq && !vector_num) { |
| init_dyn_interrupt(); |
| } |
| |
| /* initialize all the k_work */ |
| for (int i = 0; i < NUM_WORK; i++) { |
| k_work_init(&offload_work[i], entry_offload_job); |
| } |
| |
| /* start a work queue thread if not existing */ |
| if (!wq_already_start) { |
| k_work_queue_start(&wq_queue, wq_stack, STACK_SIZE, |
| K_PRIO_PREEMPT(1), NULL); |
| |
| wq_already_start = true; |
| } |
| } |
| |
| static void run_test_offload(int case_type, int real_irq) |
| { |
| int thread_prio = K_PRIO_PREEMPT(0); |
| |
| /* initialize the global variables */ |
| init_env(real_irq); |
| |
| /* set priority of offload job higher than thread */ |
| if (offload_job_prio_higher) { |
| thread_prio = K_PRIO_PREEMPT(2); |
| } |
| |
| k_tid_t tid = k_thread_create(&tdata, tstack, STACK_SIZE, |
| t_running, |
| NULL, NULL, NULL, thread_prio, |
| K_INHERIT_PERMS, K_NO_WAIT); |
| |
| /* wait for thread start */ |
| k_sem_take(&sync_sem, K_FOREVER); |
| |
| for (int i = 0; i < NUM_WORK; i++) { |
| |
| switch (case_type) { |
| case TEST_OFFLOAD_MULTI_JOBS: |
| trigger_offload_interrupt(real_irq, |
| (void *)&offload_work[i]); |
| break; |
| case TEST_OFFLOAD_IDENTICAL_JOBS: |
| trigger_offload_interrupt(real_irq, |
| (void *)&offload_work[0]); |
| break; |
| default: |
| ztest_test_fail(); |
| } |
| } |
| /* wait for all offload job complete */ |
| for (int i = 0; i < atomic_get(&submit_success); i++) { |
| k_sem_take(&end_sem, K_FOREVER); |
| } |
| |
| zassert_equal(submit_success, offload_job_cnt, |
| "submitted job unmatch offload"); |
| |
| /* notify the running thread to end */ |
| wait_for_end = true; |
| |
| k_thread_join(tid, K_FOREVER); |
| } |
| |
| /** |
| * @brief Test interrupt offload work to multiple jobs |
| * |
| * @ingroup kernel_interrupt_tests |
| * |
| * @details Validate isr can offload workload to multi work queue, and: |
| * |
| * - If the priority of the original thread < offload job, offload jobs |
| * could execute immediately. |
| * |
| * - If the priority of the original thread >= offload job, offload |
| * jobs will not execute immediately. |
| * |
| * We test this by irq_offload(). |
| */ |
| ZTEST(interrupt_feature, test_isr_offload_job_multiple) |
| { |
| offload_job_prio_higher = false; |
| run_test_offload(TEST_OFFLOAD_MULTI_JOBS, false); |
| |
| offload_job_prio_higher = true; |
| run_test_offload(TEST_OFFLOAD_MULTI_JOBS, false); |
| } |
| |
| /** |
| * @brief Test interrupt offload work to identical jobs |
| * |
| * @ingroup kernel_interrupt_tests |
| * |
| * @details Validate isr can offload workload to work queue, and all |
| * the offload jobs use the same thread entry, and: |
| * |
| * - If the priority of the original thread < offload job, offload jobs |
| * could execute immediately. |
| * |
| * - If the priority of the original thread >= offload job, offload |
| * jobs will not execute immediately. |
| * |
| * We test this by irq_offload(). |
| */ |
| ZTEST(interrupt_feature, test_isr_offload_job_identi) |
| { |
| offload_job_prio_higher = false; |
| run_test_offload(TEST_OFFLOAD_IDENTICAL_JOBS, false); |
| |
| offload_job_prio_higher = true; |
| run_test_offload(TEST_OFFLOAD_IDENTICAL_JOBS, false); |
| } |
| |
| /** |
| * @brief Test interrupt offload work by dynamic interrupt |
| * |
| * @ingroup kernel_interrupt_tests |
| * |
| * @details Validate isr can offload workload to work queue, and the |
| * offload jobs could execute immediately base on it's priority. |
| * We test this by dynamic interrupt. |
| */ |
| ZTEST(interrupt_feature, test_isr_offload_job) |
| { |
| if (!IS_ENABLED(CONFIG_DYNAMIC_INTERRUPTS)) { |
| ztest_test_skip(); |
| } |
| |
| offload_job_prio_higher = true; |
| run_test_offload(TEST_OFFLOAD_MULTI_JOBS, true); |
| run_test_offload(TEST_OFFLOAD_IDENTICAL_JOBS, true); |
| } |