blob: 29279d9c89c5b11e87c3af447c5ce8c91a3a5939 [file] [log] [blame]
/*
* Copyright (c) 2019 Demant
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/ztest.h>
/*
* Test that meta-IRQs return to the cooperative thread they preempted.
*
* A meta-IRQ thread unblocks first a long-running low-priority
* cooperative thread, sleeps a little, and then unblocks a
* high-priority cooperative thread before the low-priority thread has
* finished. The correct behavior is to continue execution of the
* low-priority thread and schedule the high-priority thread
* afterwards.
*/
#if defined(CONFIG_SMP) && CONFIG_MP_NUM_CPUS > 1
#error Meta-IRQ test requires single-CPU operation
#endif
#if CONFIG_NUM_METAIRQ_PRIORITIES < 1
#error Need one metairq priority
#endif
#if CONFIG_NUM_COOP_PRIORITIES < 2
#error Need two cooperative priorities
#endif
#define STACKSIZE 1024
#define DEFINE_PARTICIPANT_THREAD(id) \
K_THREAD_STACK_DEFINE(thread_##id##_stack_area, STACKSIZE); \
struct k_thread thread_##id##_thread_data; \
k_tid_t thread_##id##_tid;
#define PARTICIPANT_THREAD_OPTIONS (0)
#define CREATE_PARTICIPANT_THREAD(id, pri, entry) \
k_thread_create(&thread_##id##_thread_data, thread_##id##_stack_area, \
K_THREAD_STACK_SIZEOF(thread_##id##_stack_area), \
(k_thread_entry_t)entry, \
NULL, NULL, NULL, \
pri, PARTICIPANT_THREAD_OPTIONS, K_FOREVER);
#define START_PARTICIPANT_THREAD(id) k_thread_start(&(thread_##id##_thread_data));
#define JOIN_PARTICIPANT_THREAD(id) k_thread_join(&(thread_##id##_thread_data), K_FOREVER);
K_SEM_DEFINE(metairq_sem, 0, 1);
K_SEM_DEFINE(coop_sem1, 0, 1);
K_SEM_DEFINE(coop_sem2, 0, 1);
/* Variables to track progress of cooperative threads */
volatile int coop_cnt1;
volatile int coop_cnt2;
#define WAIT_MS 10 /* Time to wait/sleep between actions */
#define LOOP_CNT 4 /* Number of times low priority thread waits */
/* Meta-IRQ thread */
void metairq_thread(void)
{
k_sem_take(&metairq_sem, K_FOREVER);
printk("metairq start\n");
coop_cnt1 = 0;
coop_cnt2 = 0;
printk("give sem2\n");
k_sem_give(&coop_sem2);
k_msleep(WAIT_MS);
printk("give sem1\n");
k_sem_give(&coop_sem1);
printk("metairq end, should switch back to co-op thread2\n");
k_sem_give(&metairq_sem);
}
/* High-priority cooperative thread */
void coop_thread1(void)
{
int cnt1, cnt2;
printk("thread1 take sem\n");
k_sem_take(&coop_sem1, K_FOREVER);
printk("thread1 got sem\n");
/* Expect that low-priority thread has run to completion */
cnt1 = coop_cnt1;
zassert_equal(cnt1, 0, "Unexpected cnt1 at start: %d", cnt1);
cnt2 = coop_cnt2;
zassert_equal(cnt2, LOOP_CNT, "Unexpected cnt2 at start: %d", cnt2);
printk("thread1 increments coop_cnt1\n");
coop_cnt1++;
/* Expect that both threads have run to completion */
cnt1 = coop_cnt1;
zassert_equal(cnt1, 1, "Unexpected cnt1 at end: %d", cnt1);
cnt2 = coop_cnt2;
zassert_equal(cnt2, LOOP_CNT, "Unexpected cnt2 at end: %d", cnt2);
k_sem_give(&coop_sem1);
}
/* Low-priority cooperative thread */
void coop_thread2(void)
{
int cnt1, cnt2;
printk("thread2 take sem\n");
k_sem_take(&coop_sem2, K_FOREVER);
printk("thread2 got sem\n");
/* Expect that this is run first */
cnt1 = coop_cnt1;
zassert_equal(cnt1, 0, "Unexpected cnt1 at start: %d", cnt1);
cnt2 = coop_cnt2;
zassert_equal(cnt2, 0, "Unexpected cnt2 at start: %d", cnt2);
/* At some point before this loop has finished, the meta-irq thread
* will have woken up and given the semaphore which thread1 was
* waiting on. It then exits. We need to ensure that this thread
* continues to run after that instead of scheduling thread 1
* when the meta-irq exits.
*/
for (int i = 0; i < LOOP_CNT; i++) {
printk("thread2 loop iteration %d\n", i);
coop_cnt2++;
k_busy_wait(WAIT_MS * 1000);
}
/* Expect that this runs to completion before high-priority
* thread is started
*/
cnt1 = coop_cnt1;
zassert_equal(cnt1, 0, "Unexpected cnt1 at end: %d", cnt1);
cnt2 = coop_cnt2;
zassert_equal(cnt2, LOOP_CNT, "Unexpected cnt2 at end: %d", cnt2);
k_sem_give(&coop_sem2);
}
DEFINE_PARTICIPANT_THREAD(metairq_thread_id);
DEFINE_PARTICIPANT_THREAD(coop_thread1_id);
DEFINE_PARTICIPANT_THREAD(coop_thread2_id);
void create_participant_threads(void)
{
CREATE_PARTICIPANT_THREAD(metairq_thread_id, K_PRIO_COOP(0), metairq_thread);
CREATE_PARTICIPANT_THREAD(coop_thread1_id, K_PRIO_COOP(1), coop_thread1);
CREATE_PARTICIPANT_THREAD(coop_thread2_id, K_PRIO_COOP(2), coop_thread2);
}
void start_participant_threads(void)
{
START_PARTICIPANT_THREAD(metairq_thread_id);
START_PARTICIPANT_THREAD(coop_thread1_id);
START_PARTICIPANT_THREAD(coop_thread2_id);
}
void join_participant_threads(void)
{
JOIN_PARTICIPANT_THREAD(metairq_thread_id);
JOIN_PARTICIPANT_THREAD(coop_thread1_id);
JOIN_PARTICIPANT_THREAD(coop_thread2_id);
}
ZTEST(suite_preempt_metairq, test_preempt_metairq)
{
create_participant_threads();
start_participant_threads();
/* This unit test function runs on the ztest thread when
* CONFIG_MULTITHREADING=y.
* The ztest thread has a priority of CONFIG_ZTEST_THREAD_PRIORITY=-1.
* So it is cooperative, which cannot be preempted by the coop_thread1
* and coop_thread2 created and started above.
* This test requires coop_thread1/2 to wait on coop_sem1/2 before the
* metairq thread starts.
* Below sleep ensures the ztest thread relinquish the cpu and give
* coop_thread1/2 a chance to run and wait on coop_sem1/2.
*/
k_msleep(10);
/* Kick off meta-IRQ */
k_sem_give(&metairq_sem);
/* Wait for all threads to finish */
k_sem_take(&coop_sem2, K_FOREVER);
k_sem_take(&coop_sem1, K_FOREVER);
k_sem_take(&metairq_sem, K_FOREVER);
join_participant_threads();
}
ZTEST_SUITE(suite_preempt_metairq, NULL, NULL, NULL, NULL, NULL);