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