summaryrefslogtreecommitdiff
path: root/test/smp/regression/threadx_trace_entry_update_test.c
blob: 17c366004e6c7eda26b16df0ecaa77547b68f359 (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
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
/***************************************************************************
 * 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 is the SMP counterpart of the ThreadX suite's trace entry update test.
   It drives the trace entry update paths -- the blocks guarded by
   TX_ENABLE_EVENT_TRACE that go back and patch a trace entry after the call
   they belong to has finished its work.

   Those blocks all have the same shape:

       if (entry_ptr != TX_NULL)
       {
           if (time_stamp == entry_ptr -> tx_trace_buffer_entry_time_stamp)
           {
               entry_ptr -> tx_trace_buffer_entry_information_field_N = ...;
           }
       }

   entry_ptr comes from _tx_trace_buffer_current_ptr, which stays TX_NULL until
   tx_trace_enable() is called at run time. Compiling with TX_ENABLE_EVENT_TRACE
   is therefore not enough to reach them, and the trace_build configuration on
   its own never does: the only other test that enables tracing --
   threadx_trace_basic_test -- exercises the enable API itself and never calls
   tx_block_allocate or tx_byte_allocate at all. So the update blocks in
   tx_block_allocate.c, tx_byte_allocate.c and tx_thread_system_suspend.c were
   absent from the coverage report's covered set while appearing in its
   denominator.

   Both allocators are driven twice, because they carry two of these blocks
   each and only one is on the path that succeeds immediately: the second sits
   after a suspension, where the allocation completes in the releasing thread's
   context. Hence the second thread, which exists only to release what
   thread 0 is waiting on.

   The one thing this version has to do that the ThreadX version does not is
   pin both threads to a single core. On four cores the two threads run at the
   same time, and thread 1 would give the block back before thread 0 had
   suspended waiting for it -- the suspension the second update block sits
   behind would never happen. Excluding both from cores 1 to 3 restores the
   strict alternation the ThreadX suite gets for free.

   The test is built in all five configurations, and four of them do not define
   TX_ENABLE_EVENT_TRACE. There tx_trace_enable reports TX_FEATURE_NOT_ENABLED,
   the update blocks do not exist, and the allocation sequence below is still a
   valid exercise of both allocators' suspension paths -- so the test is
   meaningful in every configuration rather than skipped in four.  */

#include   <stdio.h>
#include   "tx_api.h"
#include   "threadx_test_port.h"
#define TX_SOURCE_CODE
#include   "tx_trace.h"


void  test_control_return(UINT status);


static TX_THREAD       thread_0;
static TX_THREAD       thread_1;

static TX_BLOCK_POOL   block_pool_0;
static TX_BYTE_POOL    byte_pool_0;
static TX_SEMAPHORE    semaphore_0;

/* Every thread this test creates is confined to core 0, so that the two of
   them take turns the way they would on a uniprocessor. Bits 1, 2 and 3 set
   means cores 1, 2 and 3 are excluded.  */

#define TRACE_ENTRY_UPDATE_CORE_0_ONLY  ((ULONG) 0xE)

/* The trace buffer is sized generously on purpose. The inner test of each
   update block compares the saved time stamp against the one in the entry, and
   a buffer small enough to wrap between the call and its completion would
   overwrite the entry and take the false branch every time -- the block would
   be reached and its body still never run.  */

static UCHAR           trace_buffer[16384];

/* Four blocks of 20 bytes.  */

static UCHAR           block_pool_area[TX_TEST_BLOCK_POOL_BYTES(20, 4)];
static UCHAR           byte_pool_area[TX_TEST_BYTE_POOL_BYTES(512)];

/* Allocated by thread 0 and released by thread 1, which is what lets thread 0's
   suspended allocations complete.  */

static void            *held_block;
static void            *held_bytes;

static UINT            error =  0;


static void    thread_0_entry(ULONG thread_input);
static void    thread_1_entry(ULONG thread_input);


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

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

UINT    status;
CHAR    *pointer;


    /* Setup a pointer.  */
    pointer =  (CHAR *) first_unused_memory;

    /* Create the pools before tracing is enabled, so that the events this test
       cares about are the allocations rather than the creates.  */
    status =  tx_block_pool_create(&block_pool_0, "trace entry update block pool",
                                   20, block_pool_area, sizeof(block_pool_area));

    if (status != TX_SUCCESS)
    {

        printf("Running Trace Entry Update Test..................................... ERROR #1\n");
        test_control_return(1);
    }

    status =  tx_byte_pool_create(&byte_pool_0, "trace entry update byte pool",
                                  byte_pool_area, sizeof(byte_pool_area));

    if (status != TX_SUCCESS)
    {

        printf("Running Trace Entry Update Test..................................... ERROR #2\n");
        test_control_return(1);
    }

    /* Turn tracing on. This is the whole point of the test: without it
       _tx_trace_buffer_current_ptr stays TX_NULL and every update block below
       takes its guarding false branch.  */
    _tx_trace_initialize();
    status =  tx_trace_enable(trace_buffer, sizeof(trace_buffer), 16);

#ifdef TX_ENABLE_EVENT_TRACE

    if (status != TX_SUCCESS)
    {

        printf("Running Trace Entry Update Test..................................... ERROR #3\n");
        test_control_return(1);
    }
#else

    /* Four of the five build configurations reach this branch. The rest of the
       test still runs; it just is not exercising any update block.  */
    if (status != TX_FEATURE_NOT_ENABLED)
    {

        printf("Running Trace Entry Update Test..................................... ERROR #4\n");
        test_control_return(1);
    }
#endif

    /* Register an object with no name at all, now that tracing is on. The name
       copy in _tx_trace_object_register guards against a TX_NULL name and
       breaks out of the copy loop, and nothing else in the suite takes that
       branch with tracing enabled -- threadx_trace_basic_test makes the
       equivalent call only under #ifndef TX_ENABLE_EVENT_TRACE, against the
       no-op stub.

       A semaphore rather than a thread, deliberately: for a thread object the
       register function dereferences the pointer it is handed to read the
       thread's priority, so that type needs a real TX_THREAD behind it.  */
    status =  tx_semaphore_create(&semaphore_0, TX_NULL, 1);

    if (status != TX_SUCCESS)
    {

        printf("Running Trace Entry Update Test..................................... ERROR #5\n");
        test_control_return(1);
    }

    /* Thread 0 drives the test and must run first, so it takes the higher
       priority -- the lower number. Both threads are created stopped so that
       the core exclusion is in place before either of them is schedulable.  */
    status =  tx_thread_create(&thread_0, "thread 0", thread_0_entry, 0,
            pointer, TEST_STACK_SIZE_PRINTF,
            15, 15, TX_NO_TIME_SLICE, TX_DONT_START);
    pointer =  pointer + TEST_STACK_SIZE_PRINTF;
    status +=  tx_thread_smp_core_exclude(&thread_0, TRACE_ENTRY_UPDATE_CORE_0_ONLY);

    if (status != TX_SUCCESS)
    {

        printf("Running Trace Entry Update Test..................................... ERROR #6\n");
        test_control_return(1);
    }

    /* Thread 1 releases what thread 0 waits on. It runs only while thread 0 is
       suspended, which is exactly when it is needed -- and only because it
       shares thread 0's single core.  */
    status =  tx_thread_create(&thread_1, "thread 1", thread_1_entry, 0,
            pointer, TEST_STACK_SIZE_PRINTF,
            16, 16, TX_NO_TIME_SLICE, TX_DONT_START);
    pointer =  pointer + TEST_STACK_SIZE_PRINTF;
    status +=  tx_thread_smp_core_exclude(&thread_1, TRACE_ENTRY_UPDATE_CORE_0_ONLY);

    if (status != TX_SUCCESS)
    {

        printf("Running Trace Entry Update Test..................................... ERROR #7\n");
        test_control_return(1);
    }

    /* Start them now that both exclusions are set. Priority decides the order,
       not this one.  */
    status =  tx_thread_resume(&thread_1);
    status += tx_thread_resume(&thread_0);

    if (status != TX_SUCCESS)
    {

        printf("Running Trace Entry Update Test..................................... ERROR #8\n");
        test_control_return(1);
    }
}


static void    thread_0_entry(ULONG thread_input)
{

UINT    status;
void    *block_ptr;
void    *byte_ptr;
UINT    i;


    /* Inform user.  */
    printf("Running Trace Entry Update Test..................................... ");

    /* Empty the block pool. The first of these takes the immediate-success path
       through tx_block_allocate, which carries the first update block.  */
    held_block =  TX_NULL;
    for (i = 0; i < 4; i++)
    {

        status =  tx_block_allocate(&block_pool_0, &block_ptr, TX_NO_WAIT);

        if (status != TX_SUCCESS)
        {

            error++;
            break;
        }

        /* Keep the first one for thread 1 to give back.  */
        if (held_block == TX_NULL)
        {

            held_block =  block_ptr;
        }
    }

    /* The pool is empty now, so this suspends. It completes in thread 1's
       context when the block comes back, which is the second update block in
       tx_block_allocate -- and the suspend it goes through carries the update
       block in tx_thread_system_suspend that names the next thread to run on
       this core.  */
    status =  tx_block_allocate(&block_pool_0, &block_ptr, TX_WAIT_FOREVER);

    if (status != TX_SUCCESS)
    {

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

    /* Now the same shape for the byte pool. Take most of it first, on the path
       that succeeds immediately.  */
    status =  tx_byte_allocate(&byte_pool_0, &held_bytes, 300, TX_NO_WAIT);

    if (status != TX_SUCCESS)
    {

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

    /* Not enough left, so this one suspends and is satisfied by thread 1's
       release.  */
    status =  tx_byte_allocate(&byte_pool_0, &byte_ptr, 300, TX_WAIT_FOREVER);

    if (status != TX_SUCCESS)
    {

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

    /* Sleep, so that thread 1 gets to run out its entry function and suspend
       itself. That leaves nothing ready to execute anywhere, which is a second
       update block in tx_thread_system_suspend -- the one on the branch taken
       when the priority map is empty. The two suspensions above never reach it,
       because thread 1 was always ready to take over.  */
    status =  tx_thread_sleep(5);

    if (status != TX_SUCCESS)
    {

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

    if (error)
    {

        printf("ERROR #13\n");
        test_control_return(1);
    }
    else
    {

        /* Successful test.  */
        printf("SUCCESS!\n");
        test_control_return(0);
    }
}


static void    thread_1_entry(ULONG thread_input)
{

UINT    status;


    /* Reached only when thread 0 has suspended on the empty block pool.  */
    status =  tx_block_release(held_block);

    if (status != TX_SUCCESS)
    {

        error++;
    }

    /* And again when thread 0 has suspended on the byte pool. Thread 0 preempts
       on the release above, runs until it suspends a second time, and control
       comes back here.  */
    status =  tx_byte_release(held_bytes);

    if (status != TX_SUCCESS)
    {

        error++;
    }

    /* Thread 0 reports the result; this thread has nothing left to do.  */
    tx_thread_suspend(&thread_1);
}