mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
tests/isr_context_switch: test context switching from ISR
The new tests triggers context switching from ISR with a high load with enabled stack tests. If the context switching code does not disable IRQs until a context switch is fully completed, an IRQ might be served while not all the saved context is consumed from the stack. Normally, this is not an issue, as the context switch is just resumed after the ISR completes. However, under high load data on the stack can accumulate and eventually overflow. This tests puts the board under high IRQ load to see if such stack overflows can happen.
This commit is contained in:
parent
bf2d4808c4
commit
e15b92976b
12
tests/isr_context_switch/Makefile
Normal file
12
tests/isr_context_switch/Makefile
Normal file
@ -0,0 +1,12 @@
|
||||
include ../Makefile.tests_common
|
||||
|
||||
FEATURES_REQUIRED += periph_timer
|
||||
USEMODULE += random
|
||||
|
||||
include $(RIOTBASE)/Makefile.include
|
||||
|
||||
CFLAGS += -DSCHED_TEST_STACK
|
||||
|
||||
# Optimize RAM usage rather than filling Makefile.ci:
|
||||
CFLAGS += -DTHREAD_STACKSIZE_MAIN=THREAD_STACKSIZE_DEFAULT
|
||||
CFLAGS += -DMAX_THREADS=4
|
16
tests/isr_context_switch/README.md
Normal file
16
tests/isr_context_switch/README.md
Normal file
@ -0,0 +1,16 @@
|
||||
Testing Context Switches from ISR under Load
|
||||
============================================
|
||||
|
||||
This tests launches two threads, t1 and t2, which each try to lock its own mutex for 500 times. A
|
||||
hardware timer is unlocking one of the mutexes in turn. Half of the time the timeout is set so
|
||||
short, that the timer IRQ triggers before the context switch is completed, the other half of the
|
||||
time the timer should trigger after the context switch is done.
|
||||
|
||||
If the context switch implementation fails to disable IRQs until the context switch is fully
|
||||
completed, this will result in (parts) of the previous context still being on the stack when the
|
||||
next ISR is executed. Over time, this will accumulate data on the stack and eventually the stack
|
||||
will overflow. Since the stack test is enabled, this should hopefully be caught and the test
|
||||
applications panics.
|
||||
|
||||
If the stack does not overflow during the test, it is assumed that context switches from ISR under
|
||||
load do not accumulate data on the stack. Hence, this is considered a pass.
|
144
tests/isr_context_switch/main.c
Normal file
144
tests/isr_context_switch/main.c
Normal file
@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Otto-von-Guericke-Universität Magdeburg
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU Lesser
|
||||
* General Public License v2.1. See the file LICENSE in the top level
|
||||
* directory for more details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ingroup tests
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Application for testing context switching triggered from IRQ
|
||||
*
|
||||
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
||||
*
|
||||
* @}
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "macros/units.h"
|
||||
#include "mutex.h"
|
||||
#include "periph/timer.h"
|
||||
#include "random.h"
|
||||
#include "thread.h"
|
||||
|
||||
#ifndef CHANNEL
|
||||
#define CHANNEL 0
|
||||
#endif
|
||||
#ifndef TEST_REPETITIONS
|
||||
#define TEST_REPETITIONS 500
|
||||
#endif
|
||||
#ifndef TEST_TIMEOUT_SHORT
|
||||
#define TEST_TIMEOUT_SHORT 8
|
||||
#endif
|
||||
#ifndef TEST_TIMEOUT_LONG
|
||||
#define TEST_TIMEOUT_LONG 1000
|
||||
#endif
|
||||
|
||||
static mutex_t sig_main = MUTEX_INIT_LOCKED;
|
||||
static mutex_t sig_t1 = MUTEX_INIT_LOCKED;
|
||||
static mutex_t sig_t2 = MUTEX_INIT_LOCKED;
|
||||
static char t1_stack[THREAD_STACKSIZE_TINY];
|
||||
static char t2_stack[THREAD_STACKSIZE_TINY];
|
||||
static tim_t timer;
|
||||
|
||||
static void _cb(void *unused1, int unused2)
|
||||
{
|
||||
(void)unused1;
|
||||
(void)unused2;
|
||||
static uint8_t counter = 0;
|
||||
|
||||
if (counter++ & 0x01) {
|
||||
mutex_unlock(&sig_t1);
|
||||
}
|
||||
else {
|
||||
mutex_unlock(&sig_t2);
|
||||
}
|
||||
|
||||
if (counter <= UINT8_MAX / 2) {
|
||||
timer_set(timer, CHANNEL, TEST_TIMEOUT_LONG);
|
||||
}
|
||||
else {
|
||||
timer_set(timer, CHANNEL, TEST_TIMEOUT_SHORT);
|
||||
}
|
||||
}
|
||||
|
||||
static void *t1_impl(void *unused)
|
||||
{
|
||||
(void)unused;
|
||||
for (unsigned repetitions = 0; repetitions < TEST_REPETITIONS; repetitions++) {
|
||||
mutex_lock(&sig_t1);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *t2_impl(void *unused)
|
||||
{
|
||||
(void)unused;
|
||||
for (unsigned repetitions = 0; repetitions < TEST_REPETITIONS; repetitions++) {
|
||||
mutex_lock(&sig_t2);
|
||||
}
|
||||
mutex_unlock(&sig_main);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int _init_timer(void)
|
||||
{
|
||||
/* Rather than manually having to maintain a mapping of which boards support
|
||||
* which frequency, just iterate over a set of frequencies until one is
|
||||
* supported.
|
||||
*/
|
||||
static const uint32_t timer_freqs[] = { MHZ(1), KHZ(500), KHZ(250), 32768 };
|
||||
|
||||
for (unsigned timer_num = 0; timer_num < TIMER_NUMOF; timer_num++) {
|
||||
timer = TIMER_DEV(timer_num);
|
||||
for (unsigned i = 0; i < ARRAY_SIZE(timer_freqs); i++) {
|
||||
if (timer_init(timer, timer_freqs[i], _cb, NULL) == 0) {
|
||||
printf("INFO: timer running at %" PRIu32 " Hz\n",
|
||||
timer_freqs[i]);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* on same boards CLOCK_CORECLOCK is not a compile time constant, but
|
||||
* determined at runtime. Hence, handle it separately from the
|
||||
* hard-coded list of frequencies above to ensure compatibility. */
|
||||
uint32_t clk = CLOCK_CORECLOCK;
|
||||
if (timer_init(timer, clk, _cb, NULL) == 0) {
|
||||
printf("INFO: timer running at %" PRIu32 " Hz\n", clk);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
printf("Testing %u context switches triggered from ISR\n", (unsigned)TEST_REPETITIONS);
|
||||
thread_create(t1_stack, sizeof(t1_stack),
|
||||
THREAD_PRIORITY_MAIN + 1,
|
||||
THREAD_CREATE_STACKTEST,
|
||||
t1_impl, NULL, "t1");
|
||||
thread_create(t2_stack, sizeof(t2_stack),
|
||||
THREAD_PRIORITY_MAIN + 1,
|
||||
THREAD_CREATE_STACKTEST,
|
||||
t2_impl, NULL, "t2");
|
||||
|
||||
if (_init_timer()) {
|
||||
puts("Failed to initialize timer.\n==>TEST FAILED");
|
||||
return 1;
|
||||
}
|
||||
|
||||
timer_set(timer, CHANNEL, TEST_TIMEOUT_LONG);
|
||||
mutex_lock(&sig_main);
|
||||
puts("TEST PASSED");
|
||||
|
||||
return 0;
|
||||
}
|
20
tests/isr_context_switch/tests/01-run.py
Executable file
20
tests/isr_context_switch/tests/01-run.py
Executable file
@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright (C) 2021 Otto-von-Guericke-Universität Magdeburg
|
||||
#
|
||||
# This file is subject to the terms and conditions of the GNU Lesser
|
||||
# General Public License v2.1. See the file LICENSE in the top level
|
||||
# directory for more details.
|
||||
|
||||
# @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
||||
|
||||
import sys
|
||||
from testrunner import run
|
||||
|
||||
|
||||
def testfunc(child):
|
||||
child.expect("TEST PASSED")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(run(testfunc))
|
Loading…
Reference in New Issue
Block a user