1
0
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:
Marian Buschsieweke 2021-04-22 08:05:44 +02:00
parent bf2d4808c4
commit e15b92976b
No known key found for this signature in database
GPG Key ID: CB8E3238CE715A94
4 changed files with 192 additions and 0 deletions

View 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

View 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.

View 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;
}

View 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))