summaryrefslogtreecommitdiff
path: root/test/tx/module_manager/threadx_module_manager_thread_kernel_stack_test.c
blob: 1ab911451e45f036e63fbb4524ad360cdc193621 (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
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
/***************************************************************************
 * 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
 **************************************************************************/


/**************************************************************************/
/**************************************************************************/
/**                                                                       */
/** ThreadX Test                                                          */
/**                                                                       */
/**   Module thread kernel stack lifetime                                  */
/**                                                                       */
/**************************************************************************/
/**************************************************************************/

/* One logical thread of a user-mode module costs the object pool two allocations:
   the TX_THREAD control block the module asks for, and the privileged kernel stack
   the manager takes on the module's behalf and records in that control block. Both
   are linked onto the module's allocation list, and only the module's own request
   is visible to it.

   What this asserts is an equality rather than a bound: the pool's available bytes
   and the module's allocation-list count must come back to exactly what they were
   before the thread existed. A bound would pass while one of the two allocations
   was left behind on every cycle.  */

#include <stdio.h>

#include "tx_api.h"
#include "tx_thread.h"
#include "tx_timer.h"
#include "tx_queue.h"
#include "tx_semaphore.h"
#include "tx_mutex.h"
#include "tx_event_flags.h"
#include "tx_block_pool.h"
#include "tx_byte_pool.h"
#include "txm_module.h"
#include "txm_module_manager_util.h"
#include "txm_module_manager_dispatch.h"


/* A pool small enough that a leak of one kernel stack per cycle exhausts it well
   inside the cycle count below.  */
#define TEST_POOL_BYTES     12280

/* How many create and delete cycles to run. Far more than the pool can absorb if
   anything is left behind.  */
#define TEST_CYCLES         200


/* The arena the stand-in byte pool hands out of, aligned as the real pool is.  */
static union
{
    ULONG   test_pool_alignment;
    UCHAR   test_pool_bytes[TEST_POOL_BYTES];
} test_pool_arena;

/* Bump position, and a free list of blocks given back. A released block is offered
   again only for a request of exactly its size, which is all the cycles below need
   and keeps the arena from growing when nothing leaks.  */
static ULONG    test_pool_offset;

typedef struct TEST_FREE_BLOCK_STRUCT
{
    UCHAR   *test_free_start;
    ULONG   test_free_size;
} TEST_FREE_BLOCK;

#define TEST_FREE_BLOCKS    64

static TEST_FREE_BLOCK  test_free_list[TEST_FREE_BLOCKS];
static ULONG            test_free_count;

/* Counts of what the manager asked the pool to do.  */
static ULONG    test_allocations;
static ULONG    test_releases;

/* Expectation accounting.  */
static ULONG    test_checks;
static ULONG    test_failures;

/* Interrupt lock accounting, referenced by the force-included port shim.  */
unsigned int    test_interrupt_disable_depth;
unsigned int    test_interrupt_disable_max_depth;
unsigned int    test_interrupt_restore_underflows;

/* The kernel's created lists. A created object lives on the list for its type, and
   the manager reads those lists, so a test that stands in for the kernel has to
   provide them. Only the thread list is populated here, by the thread this test
   creates; the rest stay empty because nothing here creates an object of that type.  */
TX_BLOCK_POOL           *_tx_block_pool_created_ptr;
ULONG                   _tx_block_pool_created_count;
TX_BYTE_POOL            *_tx_byte_pool_created_ptr;
ULONG                   _tx_byte_pool_created_count;
TX_EVENT_FLAGS_GROUP    *_tx_event_flags_created_ptr;
ULONG                   _tx_event_flags_created_count;
TX_MUTEX                *_tx_mutex_created_ptr;
ULONG                   _tx_mutex_created_count;
TX_QUEUE                *_tx_queue_created_ptr;
ULONG                   _tx_queue_created_count;
TX_SEMAPHORE            *_tx_semaphore_created_ptr;
ULONG                   _tx_semaphore_created_count;
TX_THREAD               *_tx_thread_created_ptr;
ULONG                   _tx_thread_created_count;
TX_TIMER                *_tx_timer_created_ptr;
ULONG                   _tx_timer_created_count;

/* The manager globals the sources under test refer to.  */
TX_BYTE_POOL    _txm_module_manager_object_pool;
UINT            _txm_module_manager_object_pool_created;
TX_MUTEX        _txm_module_manager_mutex;
TX_THREAD       *_tx_thread_current_ptr;

/* The thread the requests are made on behalf of.  */
static TX_THREAD    test_current_thread;

/* What the stand-in thread delete was told, and what it should report.  */
static TX_THREAD    *test_deleted_thread;
static UINT         test_delete_status;


/* Record the outcome of one expectation.  */
static VOID  test_expect(const char *description, ULONG actual, ULONG expected)
{

    test_checks++;

    if (actual != expected)
    {
        printf("FAIL: %s (expected %lu, got %lu)\n", description,
               (unsigned long) expected, (unsigned long) actual);
        test_failures++;
    }
}


/* Stand in for the byte pool. Requests come only from the manager's allocator, and
   the accounting mirrors the real one closely enough for an equality assertion:
   available falls by exactly what was handed out and rises by exactly what came
   back.  */
UINT  _txe_byte_allocate(TX_BYTE_POOL *pool_ptr, VOID **memory_ptr, ULONG memory_size, ULONG wait_option)
{

ULONG   index;
UCHAR   *block_start;


    (VOID) wait_option;

    if ((pool_ptr != &_txm_module_manager_object_pool) || (memory_ptr == TX_NULL))
    {
        return(TX_PTR_ERROR);
    }

    block_start =  TX_NULL;

    /* Offer a block of exactly this size back before taking new arena.  */
    for (index = 0; index < test_free_count; index++)
    {
        if (test_free_list[index].test_free_size == memory_size)
        {
            block_start =  test_free_list[index].test_free_start;

            test_free_count--;
            test_free_list[index] =  test_free_list[test_free_count];
            break;
        }
    }

    if (block_start == TX_NULL)
    {

        /* Keep every block aligned the way the real pool does.  */
        ULONG rounded_size =  (memory_size + (sizeof(ULONG) - 1U)) & (~(sizeof(ULONG) - 1U));

        if ((test_pool_offset + rounded_size) > ((ULONG) TEST_POOL_BYTES))
        {
            return(TX_NO_MEMORY);
        }

        block_start =      &test_pool_arena.test_pool_bytes[test_pool_offset];
        test_pool_offset = test_pool_offset + rounded_size;
    }

    _txm_module_manager_object_pool.tx_byte_pool_available =
        _txm_module_manager_object_pool.tx_byte_pool_available - memory_size;

    test_allocations++;

    *memory_ptr =  (VOID *) block_start;

    return(TX_SUCCESS);
}


/* Stand in for the release side. An address outside the arena is refused, which is
   what makes a release of memory the pool never handed out visible as a failure
   rather than as silent bookkeeping.  */
UINT  _txe_byte_release(VOID *memory_ptr)
{

UCHAR   *block_start;
ULONG   index;


    block_start =  (UCHAR *) memory_ptr;

    if ((block_start < test_pool_arena.test_pool_bytes) ||
        (block_start >= &test_pool_arena.test_pool_bytes[TEST_POOL_BYTES]))
    {
        return(TX_PTR_ERROR);
    }

    /* The size is the one recorded in the manager's header, which sits at the start
       of the block, plus the header itself.  */
    {
    TXM_MODULE_ALLOCATED_OBJECT *header_ptr;
    ULONG                       block_size;

        header_ptr =  (TXM_MODULE_ALLOCATED_OBJECT *) memory_ptr;
        block_size =  header_ptr -> txm_module_object_size + (ULONG) sizeof(TXM_MODULE_ALLOCATED_OBJECT);

        _txm_module_manager_object_pool.tx_byte_pool_available =
            _txm_module_manager_object_pool.tx_byte_pool_available + block_size;

        if (test_free_count < ((ULONG) TEST_FREE_BLOCKS))
        {
            index =  test_free_count;
            test_free_list[index].test_free_start =  block_start;
            test_free_list[index].test_free_size =   block_size;
            test_free_count++;
        }
    }

    test_releases++;

    return(TX_SUCCESS);
}


/* Stand in for the module port's data-range check. Nothing here is inside a module's
   own data, so every address the manager asks about is outside it.  */
UINT  _txm_module_manager_inside_data_check(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE obj_ptr,
                                            ULONG obj_size, UINT write_request)
{
    (VOID) module_instance;
    (VOID) obj_ptr;
    (VOID) obj_size;
    (VOID) write_request;

    return(TX_FALSE);
}


/* No module data is modelled here, so every range lies outside it.  */
UINT  _txm_module_manager_outside_data_check(TXM_MODULE_INSTANCE *module_instance, ALIGN_TYPE obj_ptr,
                                             ULONG obj_size)
{
    (VOID) module_instance;
    (VOID) obj_ptr;
    (VOID) obj_size;

    return(TX_TRUE);
}


/* Stand in for the module port's alignment adjustment. It is referenced by a
   loading path this test does not drive, and is never reached from here.  */
VOID  _txm_module_manager_alignment_adjust(TXM_MODULE_PREAMBLE *module_preamble, ULONG *code_size,
                                           ULONG *code_alignment, ULONG *data_size, ULONG *data_alignment)
{

    (VOID) module_preamble;
    (VOID) code_size;
    (VOID) code_alignment;
    (VOID) data_size;
    (VOID) data_alignment;
}


UINT  _txe_mutex_get(TX_MUTEX *mutex_ptr, ULONG wait_option)
{

    (VOID) mutex_ptr;
    (VOID) wait_option;

    return(TX_SUCCESS);
}


UINT  _txe_mutex_put(TX_MUTEX *mutex_ptr)
{

    (VOID) mutex_ptr;

    return(TX_SUCCESS);
}


/* Stand in for the thread delete the dispatcher calls. It records what it was given
   and reports what the case under test wants, so that the dispatcher's behaviour
   either side of a refusal can be asserted.  */
UINT  _txe_thread_delete(TX_THREAD *thread_ptr)
{

    test_deleted_thread =  thread_ptr;

    /* A successful delete takes the thread off the kernel's created list.  */
    if (test_delete_status == TX_SUCCESS)
    {
        _tx_thread_created_ptr =    TX_NULL;
        _tx_thread_created_count =  (ULONG) 0;
    }

    return(test_delete_status);
}


/* Put the pool, the module and the counters back to a known state.  */
static VOID  test_reset(TXM_MODULE_INSTANCE *module_ptr, ULONG property_flags)
{

ULONG   index;


    for (index = 0; index < ((ULONG) TEST_POOL_BYTES); index++)
    {
        test_pool_arena.test_pool_bytes[index] =  (UCHAR) 0;
    }

    test_pool_offset =   0;
    test_free_count =    0;
    test_allocations =   0;
    test_releases =      0;

    test_deleted_thread =  TX_NULL;
    test_delete_status =   TX_SUCCESS;

    _txm_module_manager_object_pool.tx_byte_pool_id =         TX_BYTE_POOL_ID;
    _txm_module_manager_object_pool.tx_byte_pool_start =      test_pool_arena.test_pool_bytes;
    _txm_module_manager_object_pool.tx_byte_pool_size =       TEST_POOL_BYTES;
    _txm_module_manager_object_pool.tx_byte_pool_available =  TEST_POOL_BYTES;
    _txm_module_manager_object_pool_created =                 TX_TRUE;

    TX_MEMSET(module_ptr, 0, sizeof(TXM_MODULE_INSTANCE));
    module_ptr -> txm_module_instance_property_flags =  property_flags;

    TX_MEMSET(&test_current_thread, 0, sizeof(TX_THREAD));
    test_current_thread.tx_thread_module_instance_ptr =  module_ptr;
    _tx_thread_current_ptr =                             &test_current_thread;
}


/* Create a module thread the way the manager does: the control block the module
   asks for, and then, for a user-mode module, the kernel stack the manager takes on
   its behalf and records in the control block. The stack allocation is the one the
   module never sees and never asks to have back.  */
static TX_THREAD  *test_thread_create(TXM_MODULE_INSTANCE *module_ptr)
{

TX_THREAD   *thread_ptr;
VOID        *stack_ptr;


    thread_ptr =  TX_NULL;

    if (_txm_module_manager_object_allocate((VOID **) &thread_ptr, (ULONG) sizeof(TX_THREAD),
                                            module_ptr) != TX_SUCCESS)
    {
        return(TX_NULL);
    }

    TX_MEMSET(thread_ptr, 0, sizeof(TX_THREAD));

    thread_ptr -> tx_thread_id =                   TX_THREAD_ID;
    thread_ptr -> tx_thread_module_instance_ptr =  module_ptr;

    /* Put it on the kernel's created list, which is where a created thread lives.
       One thread exists at a time here, so the list is that thread alone.  */
    thread_ptr -> tx_thread_created_next =      thread_ptr;
    thread_ptr -> tx_thread_created_previous =  thread_ptr;
    _tx_thread_created_ptr =                    thread_ptr;
    _tx_thread_created_count =                  (ULONG) 1;

    if ((module_ptr -> txm_module_instance_property_flags & TXM_MODULE_USER_MODE) != 0U)
    {

        stack_ptr =  TX_NULL;

        if (_txm_module_manager_object_allocate(&stack_ptr, (ULONG) TXM_MODULE_KERNEL_STACK_SIZE,
                                                module_ptr) != TX_SUCCESS)
        {
            return(TX_NULL);
        }

        thread_ptr -> tx_thread_module_kernel_stack_start =  stack_ptr;
    }

    return(thread_ptr);
}


int  main(void)
{

TXM_MODULE_INSTANCE     module_instance;
TX_THREAD               *thread_ptr;
ULONG                   baseline_available;
ULONG                   baseline_count;
ULONG                   cycle;
ULONG                   status;


    /**********************************************************************/
    /* One user-mode thread, created and deleted.                         */
    /**********************************************************************/

    test_reset(&module_instance, (ULONG) (TXM_MODULE_USER_MODE | TXM_MODULE_MEMORY_PROTECTION));

    baseline_available =  _txm_module_manager_object_pool.tx_byte_pool_available;
    baseline_count =      module_instance.txm_module_instance_object_list_count;

    thread_ptr =  test_thread_create(&module_instance);

    test_expect("a user-mode module thread was created",
                (ULONG) (thread_ptr != TX_NULL), (ULONG) TX_TRUE);
    test_expect("...costing the pool two allocations", test_allocations, ((ULONG) 2));
    test_expect("...both on the module's allocation list",
                module_instance.txm_module_instance_object_list_count, (baseline_count + ((ULONG) 2)));
    test_expect("...and it holds a kernel stack",
                (ULONG) (thread_ptr -> tx_thread_module_kernel_stack_start != TX_NULL), (ULONG) TX_TRUE);

    status =  (ULONG) _txm_module_manager_tx_thread_delete_dispatch(&module_instance,
                                                                    (ALIGN_TYPE) thread_ptr);

    test_expect("deleting it succeeds", status, (ULONG) TX_SUCCESS);
    test_expect("...and gives both allocations back", test_releases, ((ULONG) 2));
    test_expect("...leaving the allocation list as it was",
                module_instance.txm_module_instance_object_list_count, baseline_count);
    test_expect("...and the pool's available bytes exactly as they were",
                _txm_module_manager_object_pool.tx_byte_pool_available, baseline_available);

    /**********************************************************************/
    /* Many cycles. A leak of one kernel stack per cycle would exhaust    */
    /* this pool long before the count below is reached.                  */
    /**********************************************************************/

    test_reset(&module_instance, (ULONG) (TXM_MODULE_USER_MODE | TXM_MODULE_MEMORY_PROTECTION));

    baseline_available =  _txm_module_manager_object_pool.tx_byte_pool_available;
    baseline_count =      module_instance.txm_module_instance_object_list_count;

    for (cycle = 0; cycle < ((ULONG) TEST_CYCLES); cycle++)
    {

        thread_ptr =  test_thread_create(&module_instance);

        if (thread_ptr == TX_NULL)
        {
            printf("FAIL: the pool was exhausted at cycle %lu\n", (unsigned long) cycle);
            test_failures++;
            test_checks++;
            break;
        }

        if (_txm_module_manager_tx_thread_delete_dispatch(&module_instance,
                                                          (ALIGN_TYPE) thread_ptr) != TX_SUCCESS)
        {
            printf("FAIL: the delete was refused at cycle %lu\n", (unsigned long) cycle);
            test_failures++;
            test_checks++;
            break;
        }
    }

    test_expect("every cycle completed", cycle, ((ULONG) TEST_CYCLES));
    test_expect("...with the allocation list back where it started",
                module_instance.txm_module_instance_object_list_count, baseline_count);
    test_expect("...and the pool's available bytes back where they started",
                _txm_module_manager_object_pool.tx_byte_pool_available, baseline_available);
    test_expect("...having released exactly what it allocated", test_releases, test_allocations);

    /**********************************************************************/
    /* A thread that carries no kernel stack the manager allocated.       */
    /**********************************************************************/

    test_reset(&module_instance, (ULONG) (TXM_MODULE_USER_MODE | TXM_MODULE_MEMORY_PROTECTION));

    baseline_available =  _txm_module_manager_object_pool.tx_byte_pool_available;

    thread_ptr =  TX_NULL;

    status =  (ULONG) _txm_module_manager_object_allocate((VOID **) &thread_ptr,
                                                          (ULONG) sizeof(TX_THREAD), &module_instance);

    test_expect("a control block with no kernel stack can be allocated", status, (ULONG) TX_SUCCESS);

    TX_MEMSET(thread_ptr, 0, sizeof(TX_THREAD));
    thread_ptr -> tx_thread_id =                   TX_THREAD_ID;
    thread_ptr -> tx_thread_module_instance_ptr =  &module_instance;
    thread_ptr -> tx_thread_created_next =         thread_ptr;
    thread_ptr -> tx_thread_created_previous =     thread_ptr;
    _tx_thread_created_ptr =                       thread_ptr;
    _tx_thread_created_count =                     (ULONG) 1;

    test_expect("...and holds no kernel stack",
                (ULONG) (thread_ptr -> tx_thread_module_kernel_stack_start == TX_NULL), (ULONG) TX_TRUE);

    test_releases =  0;

    status =  (ULONG) _txm_module_manager_tx_thread_delete_dispatch(&module_instance,
                                                                    (ALIGN_TYPE) thread_ptr);

    test_expect("a user-mode module deleting it succeeds", status, (ULONG) TX_SUCCESS);
    test_expect("...giving back the control block and nothing else", test_releases, ((ULONG) 1));
    test_expect("...and the pool's available bytes exactly as they were",
                _txm_module_manager_object_pool.tx_byte_pool_available, baseline_available);

    /**********************************************************************/
    /* A module that is not in user mode.                                 */
    /**********************************************************************/

    test_reset(&module_instance, (ULONG) TXM_MODULE_MEMORY_PROTECTION);

    baseline_available =  _txm_module_manager_object_pool.tx_byte_pool_available;

    thread_ptr =  test_thread_create(&module_instance);

    test_expect("a thread of a module that is not in user mode was created",
                (ULONG) (thread_ptr != TX_NULL), (ULONG) TX_TRUE);
    test_expect("...costing the pool one allocation", test_allocations, ((ULONG) 1));

    status =  (ULONG) _txm_module_manager_tx_thread_delete_dispatch(&module_instance,
                                                                    (ALIGN_TYPE) thread_ptr);

    test_expect("deleting it succeeds", status, (ULONG) TX_SUCCESS);
    test_expect("...giving back that one allocation", test_releases, ((ULONG) 1));
    test_expect("...and the pool's available bytes exactly as they were",
                _txm_module_manager_object_pool.tx_byte_pool_available, baseline_available);

    /**********************************************************************/
    /* A refused delete releases nothing.                                 */
    /**********************************************************************/

    test_reset(&module_instance, (ULONG) (TXM_MODULE_USER_MODE | TXM_MODULE_MEMORY_PROTECTION));

    thread_ptr =        test_thread_create(&module_instance);
    baseline_count =    module_instance.txm_module_instance_object_list_count;
    test_delete_status = TX_DELETE_ERROR;

    status =  (ULONG) _txm_module_manager_tx_thread_delete_dispatch(&module_instance,
                                                                    (ALIGN_TYPE) thread_ptr);

    test_expect("a refused delete is reported", status, (ULONG) TX_DELETE_ERROR);
    test_expect("...and releases nothing", test_releases, ((ULONG) 0));
    test_expect("...leaving both allocations on the module's list",
                module_instance.txm_module_instance_object_list_count, baseline_count);

    /**********************************************************************/
    /* What the whole run has to have held.                               */
    /**********************************************************************/

    test_expect("the interrupt lock was left balanced",
                (ULONG) test_interrupt_disable_depth, ((ULONG) 0));
    test_expect("...and was never restored more often than it was taken",
                (ULONG) test_interrupt_restore_underflows, ((ULONG) 0));

    if (test_failures == 0U)
    {
        printf("SUCCESS! %lu expectations\n", (unsigned long) test_checks);
        return(0);
    }

    printf("ERROR: %lu expectation(s) failed of %lu\n",
           (unsigned long) test_failures, (unsigned long) test_checks);
    return(1);
}