summaryrefslogtreecommitdiff
path: root/test/smp/regression/threadx_timer_misra_loop_test.c
blob: 173c520dfa1dc0673fbd6063271baa316fb69289 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
/***************************************************************************
 * Copyright (c) 2026 Eclipse ThreadX contributors
 *
 * This program and the accompanying materials are made available under the
 * terms of the MIT License which is available at
 * https://opensource.org/licenses/MIT.
 *
 * AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5).
 * The AI-generated portions may be considered public domain (CC0-1.0)
 * and not subject to the project's licence. The human contributor has
 * reviewed and verified that the code is correct.
 *
 * SPDX-License-Identifier: MIT and CC0-1.0
 **************************************************************************/

/* This test takes the timer thread's loop round to its exit.

   The timer thread's body is one loop over a condition that under
   TX_MISRA_ENABLE is a call rather than the literal the other six
   configurations compile, so that the constant is not written into the
   controlling expression. The function behind it takes nothing and returns
   true, so the loop's exit arm never executes and, without the macro, does not
   exist at all.

   This test replaces a kernel symbol, which makes it one of the few tests in
   this suite whose kernel is not the certified kernel. The disclosure an
   assessor is owed is therefore here rather than in a note:

   - The symbol is `_tx_misra_always_true`, in common_smp/src/tx_misra.c.
   - The library's version has no behaviour of its own. Its whole body is
     `return(TX_TRUE);`: it reads no parameter, no variable and no state, and it
     exists so that a loop written to run forever does not carry a constant in
     its controlling expression. The version below returns the same value until
     the test arms it.
   - Replacing it leaves everything else intact because nothing else in the
     kernel depends on what it returns. It has exactly two callers in
     libthreadx_smp.so, and this test drives both: this loop, and the per-core
     state initialisation loop in _tx_thread_smp_current_state_set, which the
     kernel itself reaches only from _tx_initialize_kernel_enter,
     _tx_initialize_kernel_setup and _tx_thread_smp_high_level_initialize, all
     of which run before the scheduler starts a thread. The second is therefore
     called here directly, with the shim armed, and is given back the state its
     last core already holds so that the one assignment a shortened loop
     performs writes the value that was there. The shim is armed from a running
     thread and is disarmed before the test returns.
   - The library's own version keeps its coverage from every other test in the
     suite, all of which drive this same loop through it thousands of times.

   What the coverage costs is the timer thread. The loop is the whole body of
   the timer thread's entry function, so an exit ends the thread, and from that
   point the system has no thread to process timer expirations, thread timeouts
   or time slices. The test therefore does it deliberately and last: it proves
   the timer is working first, arms the shim, waits for the thread to end, and
   reports. Nothing after the arming depends on a tick.  */


#include   <stdio.h>
#include   "tx_api.h"
#define    TX_SOURCE_CODE
#define    TX_THREAD_SMP_SOURCE_CODE
#include   "tx_thread.h"
#include   "tx_timer.h"


#define TEST_PRIORITY               16

/* The timer must expire enough times before the shim is armed for the test to
   have shown that the thread is doing its work.  */

#define EXPIRATIONS_BEFORE_ARMING   3

/* How long to wait, in ticks. This is a bound, so that a shim the kernel never
   reaches fails the test rather than hanging it.  */

#define ARMED_TICK_BUDGET           200


static unsigned long   thread_0_counter =  0;
static TX_THREAD       thread_0;

static TX_TIMER        timer_0;

static volatile ULONG  timer_0_counter =  0;


#if defined(TX_MISRA_ENABLE) && !defined(TX_TIMER_PROCESS_IN_ISR) && !defined(TX_NO_TIMER)
#define TEST_DRIVES_THE_LOOP

/* While this is set the shim below reports that the loop should end.  */

static volatile UINT   shim_armed =  TX_FALSE;

/* How many times it has done so, which is how many times the exit arm has been
   taken.  */

static volatile ULONG  shim_exits =  0;


/* The MISRA loop condition, defined here so that it is this one the timer
   thread's loop calls. The kernel is built shared and every internal reference
   goes through the procedure linkage table, so a definition in the test
   executable is the one reached, with no linker option and no preloading. The
   library's returns true; this one returns false once it is armed.  */

UINT  _tx_misra_always_true(void)
{

    if (shim_armed == TX_TRUE)
    {

        shim_exits++;

        return(TX_FALSE);
    }

    return(TX_TRUE);
}
#endif


/* Define thread prototypes.  */

static void    thread_0_entry(ULONG thread_input);


/* Prototype for test control return.  */

void  test_control_return(UINT status);


/* Count the expirations, so the test can show the timer thread was running
   before it ends it.  */

static void  timer_0_entry(ULONG timer_input)
{

    timer_0_counter++;
}


static void    thread_0_entry(ULONG thread_input)
{

UINT    status;
ULONG   deadline;
#ifdef TEST_DRIVES_THE_LOOP
UINT    core_index;
ULONG   saved_state[TX_THREAD_SMP_MAX_CORES];
#endif


    /* Increment thread 0 counter.  */
    thread_0_counter++;

    /* Inform user of success getting to this test.  */
    printf("Running Timer MISRA Loop Test..................................... ");

    status =  tx_timer_activate(&timer_0);

    if (status != TX_SUCCESS)
    {

        printf("ERROR #2\n");
        test_control_return(1);
    }

    /* The timer thread has to be doing its work before the test ends it,
       otherwise an exit proves nothing about the loop it exited from.  */
    deadline =  tx_time_get() + ((ULONG) ARMED_TICK_BUDGET);

    while (timer_0_counter < ((ULONG) EXPIRATIONS_BEFORE_ARMING))
    {

        if (tx_time_get() > deadline)
        {

            printf("ERROR #3\n");
            test_control_return(1);
        }
    }

#ifdef TEST_DRIVES_THE_LOOP

    /* From here on nothing may depend on a tick.  */
    shim_armed =  TX_TRUE;

    /* The thread ends the next time the tick resumes it and the loop retests
       its condition.  */
    deadline =  tx_time_get() + ((ULONG) ARMED_TICK_BUDGET);

    while (_tx_timer_thread.tx_thread_state != TX_COMPLETED)
    {

        if (tx_time_get() > deadline)
        {

            /* The tick is still running -- the clock is what this waits on --
               so a thread that has not ended means the loop reached the
               library's condition rather than this test's.  */
            shim_armed =  TX_FALSE;

            printf("ERROR #4\n");
            test_control_return(1);
        }
    }

    /* The loop tested its condition once more and took the arm that leaves
       it.  */
    if (shim_exits != ((ULONG) 1))
    {

        shim_armed =  TX_FALSE;

        printf("ERROR #5\n");
        test_control_return(1);
    }

    /* The same construct a second time, in the only other loop the shim
       controls. _tx_thread_smp_current_state_set walks the cores downwards and
       leaves through a break on the lowest, so its controlling expression is
       never false in service. With the shim armed the highest core is written
       and the loop leaves through the expression instead -- so the value handed
       to it is the one that core already holds, and the state every core sees
       is the state it saw before the call.  */
    for (core_index = ((UINT) 0); core_index < ((UINT) TX_THREAD_SMP_MAX_CORES); core_index++)
    {

        saved_state[core_index] =  _tx_thread_system_state[core_index];
    }

    _tx_thread_smp_current_state_set(saved_state[TX_THREAD_SMP_MAX_CORES - ((UINT) 1)]);

    shim_armed =  TX_FALSE;

    /* The second loop tested its condition and took the same arm.  */
    if (shim_exits != ((ULONG) 2))
    {

        printf("ERROR #6\n");
        test_control_return(1);
    }

    /* And left every core's state as it found it.  */
    for (core_index = ((UINT) 0); core_index < ((UINT) TX_THREAD_SMP_MAX_CORES); core_index++)
    {

        if (_tx_thread_system_state[core_index] != saved_state[core_index])
        {

            printf("ERROR #7\n");
            test_control_return(1);
        }
    }
#endif

    status =  tx_timer_deactivate(&timer_0);

    if (status != TX_SUCCESS)
    {

        printf("ERROR #8\n");
        test_control_return(1);
    }

    /* Success!  */
    printf("SUCCESS!\n");
    test_control_return(0);
}


/* Define what the initial system looks like.  */

#ifdef CTEST
void test_application_define(void *first_unused_memory)
#else
void    threadx_timer_misra_loop_application_define(void *first_unused_memory)
#endif
{

UINT    status;
CHAR    *pointer;


    /* Put first available memory address into a character pointer.  */
    pointer =  (CHAR *) first_unused_memory;

    status =  tx_thread_create(&thread_0, "thread 0", thread_0_entry, 0,
            pointer, TEST_STACK_SIZE_PRINTF,
            TEST_PRIORITY, TEST_PRIORITY, TX_NO_TIME_SLICE, TX_AUTO_START);
    pointer =  pointer + TEST_STACK_SIZE_PRINTF;

    /* Check for status.  */
    if (status != TX_SUCCESS)
    {

        printf("Running Timer MISRA Loop Test..................................... ERROR #1\n");
        test_control_return(1);
    }

    /* A tick's worth of work every tick, so the timer thread is resumed
       repeatedly and the loop retests its condition each time.  */
    status =  tx_timer_create(&timer_0, "timer 0", timer_0_entry, 0,
            1, 1, TX_NO_ACTIVATE);

    /* Check for status.  */
    if (status != TX_SUCCESS)
    {

        printf("Running Timer MISRA Loop Test..................................... ERROR #1\n");
        test_control_return(1);
    }
}