Add a priority inversion test for Semaphores.
[bertos.git] / bertos / kern / sem_test.c
index b5a1da375f88a5c4e258d15e96c637d659bef18d..8f88ced309f6b8d8f6d931b630f8c0b7e82e9bf1 100644 (file)
  *
  * \brief Semaphore test.
  *
- * \version $Id$
- * 
+ * For testing priority inversion (avoidance) a set of processes
+ * interacting among each others by means of two semaphores are
+ * disturbed by an unrelated process, i.e., a process not using
+ * any semaphore at all.
+ *
+ * In case of priority inversion, high priority processes
+ * are affected (delayed!) by such process, even it has lower
+ * priority, because of semaphores. On the other hand, when priority
+ * inheritance is enabled, non interacting low priority processes
+ * can't affect the execution of high priority ones.
+ *
+ * It all can be seen looking at the finishing time of the various
+ * processes involved in sem_inv_test (logged).
+ *
+ * Notice that priority inheritance makes sense iff priorities
+ * exist, so the whole test depends on CONFIG_KERN_PRI.
+ *
  * \author Daniele Basile <asterix@develer.com>
- * \author Stefano Fedrigo <aleph@develer.com> 
- * 
+ * \author Stefano Fedrigo <aleph@develer.com>
+ * \author Dario Faggioli <raistlin@linux.it>
+ *
+ * $test$: cp bertos/cfg/cfg_proc.h $cfgdir/
+ * $test$: echo  "#undef CONFIG_KERN" >> $cfgdir/cfg_proc.h
+ * $test$: echo "#define CONFIG_KERN 1" >> $cfgdir/cfg_proc.h
+ * $test$: echo  "#undef CONFIG_KERN_PRI" >> $cfgdir/cfg_proc.h
+ * $test$: echo "#define CONFIG_KERN_PRI 1" >> $cfgdir/cfg_proc.h
+ * $test$: cp bertos/cfg/cfg_sem.h $cfgdir/
+ * $test$: echo  "#undef CONFIG_KERN_SEMAPHORES" >> $cfgdir/cfg_sem.h
+ * $test$: echo "#define CONFIG_KERN_SEMAPHORES 1" >> $cfgdir/cfg_sem.h
  */
 
+#include <cfg/debug.h>
 #include <cfg/test.h>
 
 #include <kern/sem.h>
 
 #include <drv/timer.h>
 
-// Global settings for the test.
-#define MAX_GLOBAL_COUNT               10
-#define TEST_TIME_OUT_MS             1000
-#define DELAY                          10
+// Global settings for the serialization test.
+#define MAX_GLOBAL_COUNT             1024
+#define TEST_TIME_OUT_MS             6000
+#define DELAY                           5
+
+// Settings for the test processes (serialization test).
+//Process 1
 #define INC_PROC_T1                     1
 #define DELAY_PROC_T1   INC_PROC_T1*DELAY
+//Process 2
 #define INC_PROC_T2                     3
 #define DELAY_PROC_T2   INC_PROC_T2*DELAY
+//Process 3
+#define INC_PROC_T3                     5
+#define DELAY_PROC_T3   INC_PROC_T3*DELAY
+//Process 4
+#define INC_PROC_T4                     7
+#define DELAY_PROC_T4   INC_PROC_T4*DELAY
+//Process 5
+#define INC_PROC_T5                    11
+#define DELAY_PROC_T5   INC_PROC_T5*DELAY
+//Process 6
+#define INC_PROC_T6                    13
+#define DELAY_PROC_T6   INC_PROC_T6*DELAY
+//Process 7
+#define INC_PROC_T7                    17
+#define DELAY_PROC_T7   INC_PROC_T7*DELAY
+//Process 8
+#define INC_PROC_T8                    19
+#define DELAY_PROC_T8   INC_PROC_T8*DELAY
 
 Semaphore sem;
 unsigned int global_count = 0;
 
 /*
- * Proc scheduling test subthread 1
+ * These macros generate the code needed to create the test process functions.
  */
-static void proc_test1(void)
-{
-       unsigned int local_count = 0;
-       
-       for (int i = 0; i < INC_PROC_T1; ++i)
-       {
-               kputs("> test1\n");
-               sem_obtain(&sem);
-               kputs("> test1: Obtain semaphore.\n");
-               local_count = global_count;
-               kprintf("> test1: Read global count [%d]\n", local_count);
-               timer_delay(DELAY_PROC_T1);
-               local_count += INC_PROC_T1;
-               global_count = local_count;
-               kprintf("> test1: Update count g[%d] l[%d]\n", global_count, local_count);
-               sem_release(&sem);
-               kputs("> test1: Relase semaphore.\n");
-       }
-}
+#define PROC_TEST(num) static void proc_semTest##num(void) \
+{ \
+       unsigned int local_count = 0; \
+       \
+       for (int i = 0; i < INC_PROC_T##num; ++i) \
+       { \
+               sem_obtain(&sem); \
+               kprintf("> test%d: Obtain semaphore.\n", num); \
+               local_count = global_count; \
+               kprintf("> test%d: Read global count [%d]\n", num, local_count); \
+               timer_delay(DELAY_PROC_T##num); \
+               local_count += INC_PROC_T##num; \
+               global_count = local_count; \
+               kprintf("> test%d: Update count g[%d] l[%d]\n", num, global_count, local_count); \
+               sem_release(&sem); \
+               kprintf("> test%d: Relase semaphore.\n", num); \
+       } \
+} \
 
+#define PROC_TEST_STACK(num)  PROC_DEFINE_STACK(proc_sem_test##num##_stack, KERN_MINSTACKSIZE * 2)
+#define PROC_TEST_INIT(num)   proc_new(proc_semTest##num, NULL, sizeof(proc_sem_test##num##_stack), proc_sem_test##num##_stack);
+
+// Define processes for the serialization test.
+PROC_TEST(1)
+PROC_TEST(2)
+PROC_TEST(3)
+PROC_TEST(4)
+PROC_TEST(5)
+PROC_TEST(6)
+PROC_TEST(7)
+PROC_TEST(8)
+
+#if CONFIG_KERN_PRI
+
+// Global settings for the priority inversion test.
+// 0.5 secs, enough for seeing the effects
+#define BASETIME 500
+
+Semaphore s1, s2;
+unsigned int loops = 0;        // For counting iterations
+int finishing_time[8];
+
+enum ProcType {NONE, S1, S2, S1S2};
 /*
- * Proc scheduling test subthread 2
+ * Macros for the processes of the priority inversion test.
  */
-static void proc_test2(void)
-{
-       unsigned int local_count = 0;
-       
-       for (int i = 0; i < INC_PROC_T2; ++i)
-       {
-               kputs("> test2\n");
-               sem_obtain(&sem);
-               kputs("> test2: Obtain semaphore.\n");
-               local_count = global_count;
-               kprintf("> test2: Read global count [%d]\n", local_count);
-               timer_delay(DELAY_PROC_T2);
-               local_count += INC_PROC_T2;
-               global_count = local_count;
-               kprintf("> test2: Update count g[%d] l[%d]\n", global_count, local_count);
-               sem_release(&sem);
-               kputs("> test2: Relase semaphore.\n");
-       }
-}
+#define PROC_INV_TEST(num) static void proc_semInvTest##num(void) \
+{ \
+       ProcType p_type = (ProcType)((long) proc_currentUserData()); \
+       int mult = p_type == NONE ? 5 : 1; \
+       unsigned int i, local_count = 0; \
+       ticks_t start; \
+       \
+       kprintf("> test%d(%d): Start.\n", num, proc_current()->link.pri); \
+       finishing_time[num-1] = timer_clock(); \
+       \
+       if (p_type == S1 || p_type == S1S2) { \
+               kprintf("> test%d(prio=%d): Obtain %p..\n", num, \
+                               proc_current()->link.pri, &s1); \
+               sem_obtain(&s1); \
+               kprintf("> test%d(prio=%d): Obtained %p.\n", num, \
+                               proc_current()->link.pri, &s1); \
+       } \
+       if (p_type == S2 || p_type == S1S2) { \
+               kprintf("> test%d(prio=%d): Obtain %p..\n", num, \
+                               proc_current()->link.pri, &s2); \
+               sem_obtain(&s2); \
+               kprintf("> test%d(prio=%d): Obtained %p.\n", num, \
+                               proc_current()->link.pri, &s2); \
+       } \
+       \
+       start = timer_clock(); \
+       for (i = 0; i < loops * mult && (((unsigned)timer_clock()-start) <= loops*mult); i++) { \
+               local_count++; \
+       } \
+       \
+       sem_obtain(&sem); \
+       global_count += local_count; \
+       kprintf("> test%d(prio=%d): global_count=%u..\n", num, \
+                       proc_current()->link.pri, global_count); \
+       sem_release(&sem); \
+       \
+       if (p_type == S2 || p_type == S1S2) { \
+               kprintf("> test%d(prio=%d): Release %p..\n", num, \
+                               proc_current()->link.pri, &s2); \
+               sem_release(&s2); \
+               kprintf("> test%d(prio=%d): %p Released.\n", num, \
+                               proc_current()->link.pri, &s2); \
+       } \
+       if (p_type == S1 || p_type == S1S2) { \
+               kprintf("> test%d(prio=%d): Release %p..\n", num, \
+                               proc_current()->link.pri, &s1); \
+               sem_release(&s1); \
+               kprintf("> test%d(prio=%d): %p Released.\n", num, \
+                               proc_current()->link.pri, &s1); \
+       } \
+       \
+       finishing_time[num-1] = timer_clock() - finishing_time[num-1]; \
+       kprintf("> test%d(prio=%d): Exit.\n", num, proc_current()->link.pri); \
+} \
 
-// Define process stacks for test.
-static cpu_stack_t proc_test1_stack[CONFIG_KERN_MINSTACKSIZE / sizeof(cpu_stack_t)];
-static cpu_stack_t proc_test2_stack[CONFIG_KERN_MINSTACKSIZE / sizeof(cpu_stack_t)];
+#define PROC_INV_TEST_INIT(num, pri, type) \
+do { \
+       struct Process *p; \
+       \
+       timer_delay(10); \
+       p = proc_new(proc_semInvTest##num, \
+                       ((void*)type), sizeof(proc_sem_test##num##_stack), \
+                       proc_sem_test##num##_stack); \
+       proc_setPri(p, pri); \
+} while (0) \
 
+// Define processes for the priority inversion test.
+PROC_INV_TEST(1)
+PROC_INV_TEST(2)
+PROC_INV_TEST(3)
+PROC_INV_TEST(4)
+PROC_INV_TEST(5)
+PROC_INV_TEST(6)
+PROC_INV_TEST(7)
+PROC_INV_TEST(8)
 
-/**
- * Run semaphore test
- */
-int sem_testRun(void)
+#endif /* CONFIG_KERN_PRI */
+
+// Define process stacks for both of the tests.
+PROC_TEST_STACK(1)
+PROC_TEST_STACK(2)
+PROC_TEST_STACK(3)
+PROC_TEST_STACK(4)
+PROC_TEST_STACK(5)
+PROC_TEST_STACK(6)
+PROC_TEST_STACK(7)
+PROC_TEST_STACK(8)
+
+int sem_ser_test(void)
 {
        ticks_t start_time = timer_clock();
 
-       kprintf("Run semaphore test..\n");
-       
-       proc_new(proc_test1, NULL, sizeof(proc_test1_stack), proc_test1_stack);
-       proc_new(proc_test2, NULL, sizeof(proc_test2_stack), proc_test2_stack);
+       sem_init(&sem);
+       global_count = 0;
+
+       kprintf("Run semaphore serialization test..\n");
+
+       // Initialize the processes.
+       PROC_TEST_INIT(1)
+       PROC_TEST_INIT(2)
+       PROC_TEST_INIT(3)
+       PROC_TEST_INIT(4)
+       PROC_TEST_INIT(5)
+       PROC_TEST_INIT(6)
+       PROC_TEST_INIT(7)
+       PROC_TEST_INIT(8)
        kputs("> Main: Processes created\n");
-       
+
        /*
-        * Wait until all process finishing, if some going wrong we return 
-        * error after time_out_ms ms.
-        */ 
+        * Wait until all processes exit, if something goes wrong we return an
+        * error after timeout_ms.
+        */
        while((timer_clock() - start_time) < ms_to_ticks(TEST_TIME_OUT_MS))
        {
                if (sem_attempt(&sem))
                {
-                       kputs("> Main: Check if test is finish..\n");
+                       kputs("> Main: Check if test has finished..\n");
                        if(global_count == MAX_GLOBAL_COUNT)
                        {
                                kputs("> Main: Test Finished..Ok!\n");
@@ -141,33 +281,106 @@ int sem_testRun(void)
                }
                proc_yield();
        }
-       
-       kputs("Semaphore Test fail..\n");
+
+       kputs("Semaphore serialization test failed..\n");
        return -1;
 }
 
-int sem_testSetup(void)
+#if CONFIG_KERN_PRI
+
+int sem_inv_test(void)
 {
-       kdbg_init();
+       int i, orig_pri = proc_current()->link.pri;
+       ticks_t fake, start_time;
 
-       kprintf("Init Semaphore..");
        sem_init(&sem);
-       kprintf("Done.\n");
-       
-       #if CONFIG_KERN_PREEMPT
-               kprintf("Init Interrupt (preempt mode)..");
-               irq_init();
-               kprintf("Done.\n");
-       #endif
+       global_count = 0;
+       loops = 0;
+
+       sem_init(&s1);
+       sem_init(&s2);
+
+       kputs("> Main: calibration for the busy wait cycle..\n");
+       proc_setPri(proc_current(), 10);
+
+       fake = start_time = timer_clock();
+       while ((fake - start_time) < ms_to_ticks(BASETIME)) {
+               fake = timer_clock();
+               loops++;
+       }
+       kprintf("> Main: calibration done, %dms equals to %u cycles!\n", BASETIME, loops);
+
+       kputs("> Main: Run Priority Inversion test...\n");
+
+       // Will take s2
+       PROC_INV_TEST_INIT(1, 2, S2);
+
+       // 2 will block on s2; 3 will take s2 and still block on s2
+       PROC_INV_TEST_INIT(2, 3, S2);
+       PROC_INV_TEST_INIT(3, 4, S1S2);
+
+       // Will block on s1, nothing happens..
+       PROC_INV_TEST_INIT(4, 5, S1);
+
+       // No semaphore, without PI this will delay everyone!
+       PROC_INV_TEST_INIT(5, 6, NONE);
+
+       // Will block on s1 and boost
+       PROC_INV_TEST_INIT(6, 7, S1);
+       PROC_INV_TEST_INIT(7, 8, S1);
+       PROC_INV_TEST_INIT(8, 9, S1);
+
+       // All processes created, let them run.
+       proc_setPri(proc_current(), orig_pri);
+       while ((timer_clock() - start_time) < ms_to_ticks(TEST_TIME_OUT_MS*2)) {
+               if (sem_attempt(&sem)) {
+                       if (global_count >= loops*7 + loops*5) {
+                               for (i = 0; i < 8; i++)
+                                       kprintf("> Main: I-O latency of %d = %dms\n", i+1, ms_to_ticks(finishing_time[i]));
+                               kputs("> Main: Test Finished..Ok!\n");
+                               return 0;
+                       }
+                       sem_release(&sem);
+               }
+               proc_yield();
+       }
+
+       kputs("> Main: Priority Inversion Test failed..\n");
+       return -1;
+}
+
+#else
+
+void sem_inv_test(void)
+{
+}
+
+#endif /* CONFIG_KERN_PRI */
+
+/**
+ * Run semaphore test
+ */
+int sem_testRun(void)
+{
+       /* Start tests */
+       sem_ser_test();         // Serialization
+       sem_inv_test();         // Priority Inversion
+
+       return 0;
+}
+
+int sem_testSetup(void)
+{
+       kdbg_init();
 
        kprintf("Init Timer..");
        timer_init();
        kprintf("Done.\n");
-       
+
        kprintf("Init Process..");
        proc_init();
        kprintf("Done.\n");
-       
+
        return 0;
 }
 
@@ -177,4 +390,4 @@ int sem_testTearDown(void)
        return 0;
 }
 
-TEST_MAIN(sem);
\ No newline at end of file
+TEST_MAIN(sem);