/* ChibiOS/RT - Copyright (C) 2006-2007 Giovanni Di Sirio. This file is part of ChibiOS/RT. ChibiOS/RT is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. ChibiOS/RT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include "test.h" /** * @page test_mtx Mutexes test * *

Description

* This module implements the test sequence for the @ref Mutexes and * @ref CondVars subsystems.
* Tests on those subsystems are particularly critical because the system-wide * implications of the Priority Inheritance mechanism. * *

Objective

* Objective of the test module is to cover 100% of the subsystem code * as a necessary step in order to assess their maturity level. * *

Preconditions

* The module requires the following kernel options: * - @p CH_USE_MUTEXES * - @p CH_USE_CONDVARS * - @p CH_DBG_THREADS_PROFILING * . * In case some of the required options are not enabled then some or all tests * may be skipped. * *

Test Cases

* - @subpage test_mtx_001 * - @subpage test_mtx_002 * - @subpage test_mtx_003 * - @subpage test_mtx_004 * - @subpage test_mtx_005 * - @subpage test_mtx_006 * - @subpage test_mtx_007 * - @subpage test_mtx_008 * . * @file testmtx.c * @brief @ref Mutexes and @ref CondVars test source file * @file testmtx.h * @brief @ref Mutexes and @ref CondVars test header file */ #if CH_USE_MUTEXES #define ALLOWED_DELAY 5 static Mutex m1, m2; static CondVar c1; /** * @page test_mtx_001 Priority enqueuing test * *

Description

* Five threads, with increasing priority, are enqueued on a locked mutex then * the mutex is unlocked.
* The test expects the threads to perform their operations in increasing * priority order regardless of the initial order. */ static char *mtx1_gettest(void) { return "Mutexes, priority enqueuing test"; } static void mtx1_setup(void) { chMtxInit(&m1); } static msg_t thread1(void *p) { chMtxLock(&m1); test_emit_token(*(char *)p); chMtxUnlock(); return 0; } static void mtx1_execute(void) { tprio_t prio = chThdGetPriority(); // Because priority inheritance. chMtxLock(&m1); threads[0] = chThdCreateStatic(wa[0], WA_SIZE, prio+1, thread1, "E"); threads[1] = chThdCreateStatic(wa[1], WA_SIZE, prio+2, thread1, "D"); threads[2] = chThdCreateStatic(wa[2], WA_SIZE, prio+3, thread1, "C"); threads[3] = chThdCreateStatic(wa[3], WA_SIZE, prio+4, thread1, "B"); threads[4] = chThdCreateStatic(wa[4], WA_SIZE, prio+5, thread1, "A"); chMtxUnlock(); test_wait_threads(); test_assert(1, prio == chThdGetPriority(), "wrong priority level"); test_assert_sequence(2, "ABCDE"); } const struct testcase testmtx1 = { mtx1_gettest, mtx1_setup, NULL, mtx1_execute }; /** * @page test_mtx_002 Priority inheritance, simple case * *

Description

* Three threads are involved in the classic priority inversion scenario, a * medium priority thread tries to starve an high priority thread by * blocking a low priority thread into a mutex lock zone.
* The test expects the threads to perform their operations in increasing * priority order by rearranging their priorities in order to avoid the * priority inversion trap. * *

Scenario

* This weird looking diagram should explain what happens in the test case: * @code * Time ----> 0 10 20 30 40 50 60 70 80 90 100 * 0 ......AL++++++++++............2+++++++++++AU0---------------++++++G... * 1 ..................++++++++++++------------------++++++++++++G......... * 2 .............................AL..........++++++AUG................... * ^ ^ * * Legend: * 0..2 - Priority levels * +++ - Running * --- - Ready * ... - Waiting * AL - Lock operation on mutex A * AUn - Unlock operation on mutex A with priority going to level 'n' * G - Goal * ^ - Priority transition (boost or return). * @endcode */ static char *mtx2_gettest(void) { return "Mutexes, priority inheritance, simple case"; } static void mtx2_setup(void) { chMtxInit(&m1); } /* Low priority thread */ static msg_t thread2L(void *p) { chMtxLock(&m1); test_cpu_pulse(40); chMtxUnlock(); test_cpu_pulse(10); test_emit_token('C'); return 0; } /* Medium priority thread */ static msg_t thread2M(void *p) { chThdSleepMilliseconds(20); test_cpu_pulse(40); test_emit_token('B'); return 0; } /* High priority thread */ static msg_t thread2H(void *p) { chThdSleepMilliseconds(40); chMtxLock(&m1); test_cpu_pulse(10); chMtxUnlock(); test_emit_token('A'); return 0; } static void mtx2_execute(void) { systime_t time; test_wait_tick(); time = chTimeNow(); threads[0] = chThdCreateStatic(wa[0], WA_SIZE, chThdGetPriority()-1, thread2H, 0); threads[1] = chThdCreateStatic(wa[1], WA_SIZE, chThdGetPriority()-3, thread2L, 0); threads[2] = chThdCreateStatic(wa[2], WA_SIZE, chThdGetPriority()-2, thread2M, 0); test_wait_threads(); test_assert_sequence(1, "ABC"); test_assert_time_window(2, time + MS2ST(100), time + MS2ST(100) + ALLOWED_DELAY); } const struct testcase testmtx2 = { mtx2_gettest, mtx2_setup, NULL, mtx2_execute }; /** * @page test_mtx_003 Priority inheritance, complex case * *

Description

* Five threads are involved in the complex priority inversion scenario, * please refer to the diagram below for the complete scenario.
* The test expects the threads to perform their operations in increasing * priority order by rearranging their priorities in order to avoid the * priority inversion trap. * *

Scenario

* This weird looking diagram should explain what happens in the test case: * @code * Time ----> * 0 10 20 30 40 50 * 0 +++BL++------------------2++++------4+++++BU0-------------------------- * 1 .......++AL++--2+++++++++BL.........4.....++++++++BU4++++AU1----------- * 2 .............++AL............................................------++AU * 3 ..............................++++-------------------------------++.... * 4 ..................................++AL...................++++AU++...... * ^ ^ * * Legend: * 0..2 - Priority levels * +++ - Running * --- - Ready * ... - Waiting * AL - Lock operation on mutex A * AUn - Unlock operation on mutex A with priority going to level 'n' * ^ - Priority transition (boost or return). * @endcode */ static char *mtx3_gettest(void) { return "Mutexes, priority inheritance, complex case"; } static void mtx3_setup(void) { chMtxInit(&m1); chMtxInit(&m2); } static msg_t thread5(void *p) { chMtxLock(&m1); test_cpu_pulse(50); chMtxUnlock(); test_emit_token(*(char *)p); return 0; } static msg_t thread6(void *p) { chThdSleepMilliseconds(10); chMtxLock(&m2); test_cpu_pulse(20); chMtxLock(&m1); test_cpu_pulse(50); chMtxUnlock(); test_cpu_pulse(20); chMtxUnlock(); test_emit_token(*(char *)p); return 0; } static msg_t thread7(void *p) { chThdSleepMilliseconds(20); chMtxLock(&m2); test_cpu_pulse(50); chMtxUnlock(); test_emit_token(*(char *)p); return 0; } static msg_t thread8(void *p) { chThdSleepMilliseconds(40); test_cpu_pulse(200); test_emit_token(*(char *)p); return 0; } static msg_t thread9(void *p) { chThdSleepMilliseconds(50); chMtxLock(&m2); test_cpu_pulse(50); chMtxUnlock(); test_emit_token(*(char *)p); return 0; } /* * Time 0 10 20 30 40 50 * 0 +++BL++------------------2++++------4+++++BU0-------------------------- * 1 .......++AL++--2+++++++++BL.........4.....++++++++BU4++++AU1----------- * 2 .............++AL............................................------++AU * 3 ..............................++++-------------------------------++.... * 4 ..................................++AL...................++++AU++...... */ static void mtx3_execute(void) { threads[0] = chThdCreateStatic(wa[0], WA_SIZE, chThdGetPriority()-5, thread5, "E"); threads[1] = chThdCreateStatic(wa[1], WA_SIZE, chThdGetPriority()-4, thread6, "D"); threads[2] = chThdCreateStatic(wa[2], WA_SIZE, chThdGetPriority()-3, thread7, "C"); threads[3] = chThdCreateStatic(wa[3], WA_SIZE, chThdGetPriority()-2, thread8, "B"); threads[4] = chThdCreateStatic(wa[4], WA_SIZE, chThdGetPriority()-1, thread9, "A"); test_wait_threads(); test_assert_sequence(1, "ABCDE"); } const struct testcase testmtx3 = { mtx3_gettest, mtx3_setup, NULL, mtx3_execute }; static char *mtx4_gettest(void) { return "Mutexes, priority return"; } static void mtx4_setup(void) { chMtxInit(&m1); chMtxInit(&m2); } static msg_t thread13(void *p) { chThdSleepMilliseconds(50); chMtxLock(&m2); chMtxUnlock(); return 0; } static msg_t thread14(void *p) { chThdSleepMilliseconds(150); chMtxLock(&m1); chMtxUnlock(); return 0; } static void mtx4_execute(void) { tprio_t p, p1, p2; p = chThdGetPriority(); p1 = p + 1; p2 = p + 2; threads[0] = chThdCreateStatic(wa[0], WA_SIZE, p1, thread13, "B"); threads[1] = chThdCreateStatic(wa[1], WA_SIZE, p2, thread14, "A"); chMtxLock(&m2); test_assert(1, chThdGetPriority() == p, "wrong priority level"); chThdSleepMilliseconds(100); test_assert(2, chThdGetPriority() == p1, "wrong priority level"); chMtxLock(&m1); test_assert(3, chThdGetPriority() == p1, "wrong priority level"); chThdSleepMilliseconds(100); test_assert(4, chThdGetPriority() == p2, "wrong priority level"); chMtxUnlock(); test_assert(5, chThdGetPriority() == p1, "wrong priority level"); chThdSleepMilliseconds(100); test_assert(6, chThdGetPriority() == p1, "wrong priority level"); chMtxUnlockAll(); test_assert(7, chThdGetPriority() == p, "wrong priority level"); test_wait_threads(); /* Test repeated in order to cover chMtxUnlockS().*/ threads[0] = chThdCreateStatic(wa[0], WA_SIZE, p1, thread13, "D"); threads[1] = chThdCreateStatic(wa[1], WA_SIZE, p2, thread14, "C"); chMtxLock(&m2); test_assert(8, chThdGetPriority() == p, "wrong priority level"); chThdSleepMilliseconds(100); test_assert(9, chThdGetPriority() == p1, "wrong priority level"); chMtxLock(&m1); test_assert(10, chThdGetPriority() == p1, "wrong priority level"); chThdSleepMilliseconds(100); test_assert(11, chThdGetPriority() == p2, "wrong priority level"); chSysLock(); chMtxUnlockS(); chSysUnlock(); test_assert(12, chThdGetPriority() == p1, "wrong priority level"); chThdSleepMilliseconds(100); test_assert(13, chThdGetPriority() == p1, "wrong priority level"); chMtxUnlockAll(); test_assert(14, chThdGetPriority() == p, "wrong priority level"); test_wait_threads(); } const struct testcase testmtx4 = { mtx4_gettest, mtx4_setup, NULL, mtx4_execute }; static char *mtx5_gettest(void) { return "Mutexes, coverage"; } static void mtx5_setup(void) { chMtxInit(&m1); } static void mtx5_execute(void) { bool_t b; tprio_t prio; prio = chThdGetPriority(); b = chMtxTryLock(&m1); test_assert(1, b, "already locked"); b = chMtxTryLock(&m1); test_assert(2, !b, "not locked"); chSysLock(); chMtxUnlockS(); chSysUnlock(); test_assert(3, isempty(&m1.m_queue), "queue not empty"); test_assert(4, m1.m_owner == NULL, "still owned"); test_assert(5, chThdGetPriority() == prio, "wrong priority level"); } const struct testcase testmtx5 = { mtx5_gettest, mtx5_setup, NULL, mtx5_execute }; #if CH_USE_CONDVARS static char *mtx6_gettest(void) { return "CondVar, signal test"; } static void mtx6_setup(void) { chCondInit(&c1); chMtxInit(&m1); } static msg_t thread10(void *p) { chMtxLock(&m1); chCondWait(&c1); test_emit_token(*(char *)p); chMtxUnlock(); return 0; } static void mtx6_execute(void) { tprio_t prio = chThdGetPriority(); threads[0] = chThdCreateStatic(wa[0], WA_SIZE, prio+1, thread10, "E"); threads[1] = chThdCreateStatic(wa[1], WA_SIZE, prio+2, thread10, "D"); threads[2] = chThdCreateStatic(wa[2], WA_SIZE, prio+3, thread10, "C"); threads[3] = chThdCreateStatic(wa[3], WA_SIZE, prio+4, thread10, "B"); threads[4] = chThdCreateStatic(wa[4], WA_SIZE, prio+5, thread10, "A"); chSysLock(); chCondSignalI(&c1); chCondSignalI(&c1); chCondSignalI(&c1); chCondSignalI(&c1); chCondSignalI(&c1); chSchRescheduleS(); chSysUnlock(); test_wait_threads(); test_assert_sequence(1, "ABCDE"); } const struct testcase testmtx6 = { mtx6_gettest, mtx6_setup, NULL, mtx6_execute }; static char *mtx7_gettest(void) { return "CondVar, broadcast test"; } static void mtx7_setup(void) { chCondInit(&c1); chMtxInit(&m1); } static void mtx7_execute(void) { // Bacause priority inheritance. tprio_t prio = chThdGetPriority(); threads[0] = chThdCreateStatic(wa[0], WA_SIZE, prio+1, thread10, "E"); threads[1] = chThdCreateStatic(wa[1], WA_SIZE, prio+2, thread10, "D"); threads[2] = chThdCreateStatic(wa[2], WA_SIZE, prio+3, thread10, "C"); threads[3] = chThdCreateStatic(wa[3], WA_SIZE, prio+4, thread10, "B"); threads[4] = chThdCreateStatic(wa[4], WA_SIZE, prio+5, thread10, "A"); chCondBroadcast(&c1); test_wait_threads(); test_assert_sequence(1, "ABCDE"); } const struct testcase testmtx7 = { mtx7_gettest, mtx7_setup, NULL, mtx7_execute }; static char *mtx8_gettest(void) { return "CondVar, inheritance boost test"; } static void mtx8_setup(void) { chCondInit(&c1); chMtxInit(&m1); chMtxInit(&m2); } static msg_t thread11(void *p) { chMtxLock(&m2); chMtxLock(&m1); #if CH_USE_CONDVARS_TIMEOUT chCondWaitTimeout(&c1, TIME_INFINITE); #else chCondWait(&c1); #endif test_emit_token(*(char *)p); chMtxUnlock(); chMtxUnlock(); return 0; } static msg_t thread12(void *p) { chMtxLock(&m2); test_emit_token(*(char *)p); chMtxUnlock(); return 0; } static void mtx8_execute(void) { tprio_t prio = chThdGetPriority(); threads[0] = chThdCreateStatic(wa[0], WA_SIZE, prio+1, thread11, "A"); threads[1] = chThdCreateStatic(wa[1], WA_SIZE, prio+2, thread10, "C"); threads[2] = chThdCreateStatic(wa[2], WA_SIZE, prio+3, thread12, "B"); chCondSignal(&c1); chCondSignal(&c1); test_wait_threads(); test_assert_sequence(1, "ABC"); } const struct testcase testmtx8 = { mtx8_gettest, mtx8_setup, NULL, mtx8_execute }; #endif /* CH_USE_CONDVARS */ #endif /* CH_USE_MUTEXES */ /* * Test sequence for mutexes pattern. */ const struct testcase * const patternmtx[] = { #if CH_USE_MUTEXES &testmtx1, &testmtx2, &testmtx3, &testmtx4, &testmtx5, #if CH_USE_CONDVARS &testmtx6, &testmtx7, &testmtx8, #endif #endif NULL };