/*************************************************************************** * 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 drives every thread service with event tracing enabled. Each service carries one in-line trace insert, and that insert behaves differently according to the system state it runs in, whether its event is filtered, whether it is the entry that wraps the trace buffer, and whether a buffer-full notification is registered. The test walks the thread services once per combination, so every service's insert is driven through all of them. The scheduler's own inserts sit behind the published services: a resume and a suspend each record an event and then update it in place once the next thread to execute is known. Those inserts are reached by positioning the buffer two entries short rather than one, and the preemption pass at the end drives the update itself, which is only reached when an entry was recorded to update. The SMP insert reads the system state and the current thread of the core it is running on rather than a single global, so the passes below that force a system state write the entry of the core this test is running on, under the SMP protection primitive. Every thread in this suite is created mapped to core 0 alone, but the core is read rather than assumed. The time-slice check differs from its monoprocessor twin: its insert sits inside the test for an active time slice on some core, so it is only reached while one is running. The test thread therefore carries a time slice. Without event tracing compiled in the inserts expand to nothing, so the test only checks that the trace services report the feature as disabled. */ #include #include "tx_api.h" #define TX_SOURCE_CODE /* The system state is per core on SMP, and tx_thread.h remaps the name to a function call returning the running core's entry unless this macro says the file wants the array itself. The forced-state passes below have to write it, so they need the array. */ #define TX_THREAD_SMP_SOURCE_CODE #include "tx_trace.h" #include "tx_thread.h" #include "tx_initialize.h" /* Define the ISR dispatch. */ extern VOID (*test_isr_dispatch)(void); /* Define the thread services, in the order the test drives them. The scratch thread is created by the first and deleted by the last, so a pass over the whole list leaves the system as it found it. */ #define THREAD_SERVICE_CREATE 0 #define THREAD_SERVICE_IDENTIFY 1 #define THREAD_SERVICE_INFO_GET 2 #define THREAD_SERVICE_ENTRY_EXIT_NOTIFY 3 #define THREAD_SERVICE_PREEMPTION_CHANGE 4 #define THREAD_SERVICE_PRIORITY_CHANGE 5 #define THREAD_SERVICE_TIME_SLICE_CHANGE 6 #define THREAD_SERVICE_RESUME 7 #define THREAD_SERVICE_SUSPEND 8 #define THREAD_SERVICE_WAIT_ABORT 9 #define THREAD_SERVICE_RELINQUISH 10 #define THREAD_SERVICE_TIME_SLICE 11 #define THREAD_SERVICE_SLEEP 12 #define THREAD_SERVICE_RESET 13 #define THREAD_SERVICE_TERMINATE 14 #define THREAD_SERVICE_DELETE 15 #define THREAD_SERVICES 16 /* The priorities the test runs at. The scratch thread sits below the test thread so that creating, resuming and suspending it never schedules it; the preempting thread sits above so that resuming it always does. The test thread is alone at its own priority, so a relinquish finds no other thread to hand the processor to and returns. */ #define TEST_PRIORITY 16 #define SCRATCH_PRIORITY 20 #define PREEMPT_PRIORITY 10 /* The time slice the test thread carries. The SMP time-slice check only reaches its trace insert while a time slice is active on some core, and a relinquish reloads the core's slice from this value, so it has to be non-zero for the whole run. */ #define TEST_TIME_SLICE 200 static unsigned long thread_0_counter = 0; static TX_THREAD thread_0; /* The scratch thread the lifecycle services are driven against. */ static TX_THREAD thread_1; static CHAR *thread_1_stack; /* The reset target. It is left terminated between passes, which is the only state a thread can be reset from, and it is also what the interrupt pass drives its services against. */ static TX_THREAD thread_2; /* The preempting thread, used by the last pass only. */ static unsigned long thread_3_counter = 0; static TX_THREAD thread_3; static ULONG buffer_full_count = 0; static ULONG isr_pass_done = 0; static UINT isr_status = TX_SUCCESS; /* The trace buffer is deliberately small. Several passes over the services fit in it many times over, so the wrap path is reached by ordinary use as well as by the positioned wrap below. */ static UCHAR trace_buffer[512]; #define TRACE_REGISTRY_ENTRIES 4 #define SCRATCH_STACK_SIZE TEST_STACK_SIZE_PRINTF /* Define thread prototypes. */ static void thread_0_entry(ULONG thread_input); static void thread_1_entry(ULONG thread_input); static void thread_3_entry(ULONG thread_input); /* Prototype for test control return. */ void test_control_return(UINT status); /* Drive one thread service through its published entry point. */ static UINT thread_service(UINT service) { UINT status; CHAR *name; UINT state; ULONG run_count; UINT priority; UINT threshold; ULONG time_slice; TX_THREAD *next_thread; TX_THREAD *next_suspended_thread; ULONG old_time_slice; switch (service) { case THREAD_SERVICE_CREATE: status = tx_thread_create(&thread_1, "thread 1", thread_1_entry, 1, thread_1_stack, SCRATCH_STACK_SIZE, SCRATCH_PRIORITY, SCRATCH_PRIORITY, TX_NO_TIME_SLICE, TX_DONT_START); break; case THREAD_SERVICE_IDENTIFY: if (tx_thread_identify() == TX_NULL) { status = TX_NOT_DONE; } else { status = TX_SUCCESS; } break; case THREAD_SERVICE_INFO_GET: status = tx_thread_info_get(&thread_1, &name, &state, &run_count, &priority, &threshold, &time_slice, &next_thread, &next_suspended_thread); break; case THREAD_SERVICE_ENTRY_EXIT_NOTIFY: status = tx_thread_entry_exit_notify(&thread_1, TX_NULL); break; case THREAD_SERVICE_PREEMPTION_CHANGE: status = tx_thread_preemption_change(&thread_1, SCRATCH_PRIORITY, &threshold); break; case THREAD_SERVICE_PRIORITY_CHANGE: status = tx_thread_priority_change(&thread_1, SCRATCH_PRIORITY, &priority); break; case THREAD_SERVICE_TIME_SLICE_CHANGE: status = tx_thread_time_slice_change(&thread_1, TX_NO_TIME_SLICE, &old_time_slice); break; case THREAD_SERVICE_RESUME: status = tx_thread_resume(&thread_1); break; case THREAD_SERVICE_SUSPEND: status = tx_thread_suspend(&thread_1); break; case THREAD_SERVICE_WAIT_ABORT: status = tx_thread_wait_abort(&thread_1); break; case THREAD_SERVICE_RELINQUISH: tx_thread_relinquish(); status = TX_SUCCESS; break; case THREAD_SERVICE_TIME_SLICE: /* The time-slice check has no published entry point of its own: the timer drives it. Calling it directly is what puts its insert on the boundary in the wrap passes rather than leaving it to a tick. */ _tx_thread_time_slice(); status = TX_SUCCESS; break; case THREAD_SERVICE_SLEEP: status = tx_thread_sleep(1); break; case THREAD_SERVICE_RESET: /* The reset is followed by a terminate so that the next pass finds the thread in the one state a reset is allowed from. The terminate runs after the reset, so the reset's own insert is still the one that wraps the buffer in the wrap passes. */ status = tx_thread_reset(&thread_2); if (tx_thread_terminate(&thread_2) != TX_SUCCESS) { status = TX_NOT_DONE; } break; case THREAD_SERVICE_TERMINATE: status = tx_thread_terminate(&thread_1); break; default: status = tx_thread_delete(&thread_1); break; } return(status); } /* The status a service returns when the pass above is run to completion. The create and delete balance, the resume and suspend balance, and the wait abort is driven against a thread that is suspended rather than waiting, so the pass is repeatable. */ static UINT thread_service_expected(UINT service) { UINT expected; if (service == THREAD_SERVICE_WAIT_ABORT) { expected = TX_WAIT_ABORT_ERROR; } #ifdef TX_DISABLE_NOTIFY_CALLBACKS else if (service == THREAD_SERVICE_ENTRY_EXIT_NOTIFY) { expected = TX_FEATURE_NOT_ENABLED; } #endif else { expected = TX_SUCCESS; } return(expected); } #ifdef TX_ENABLE_EVENT_TRACE /* The services an ISR is allowed to call. The rest are rejected by the error checking shell from any caller that is not a thread, so their inserts cannot be reached this way; the forced system state pass below reaches them instead. They are driven against the terminated reset target, so the pass changes no scheduling state at all. */ static UINT isr_services[] = {THREAD_SERVICE_IDENTIFY, THREAD_SERVICE_INFO_GET, THREAD_SERVICE_ENTRY_EXIT_NOTIFY, THREAD_SERVICE_RESUME, THREAD_SERVICE_SUSPEND, THREAD_SERVICE_WAIT_ABORT}; #define ISR_SERVICES (sizeof(isr_services)/sizeof(isr_services[0])) /* Drive one thread service from interrupt context, against the terminated thread. */ static UINT thread_service_isr(UINT service) { UINT status; CHAR *name; UINT state; ULONG run_count; UINT priority; UINT threshold; ULONG time_slice; TX_THREAD *next_thread; TX_THREAD *next_suspended_thread; switch (service) { case THREAD_SERVICE_IDENTIFY: /* An interrupt that arrives while a core is idle has no thread to identify, so the return value carries nothing to assert here. What is under test is the insert the service runs on the way. */ (VOID) tx_thread_identify(); status = TX_SUCCESS; break; case THREAD_SERVICE_INFO_GET: status = tx_thread_info_get(&thread_2, &name, &state, &run_count, &priority, &threshold, &time_slice, &next_thread, &next_suspended_thread); break; case THREAD_SERVICE_ENTRY_EXIT_NOTIFY: status = tx_thread_entry_exit_notify(&thread_2, TX_NULL); break; case THREAD_SERVICE_RESUME: status = tx_thread_resume(&thread_2); break; case THREAD_SERVICE_SUSPEND: status = tx_thread_suspend(&thread_2); break; default: status = tx_thread_wait_abort(&thread_2); break; } return(status); } /* What each of those returns against a terminated thread. */ static UINT thread_service_isr_expected(UINT service) { UINT expected; switch (service) { case THREAD_SERVICE_RESUME: expected = TX_RESUME_ERROR; break; case THREAD_SERVICE_SUSPEND: expected = TX_SUSPEND_ERROR; break; case THREAD_SERVICE_WAIT_ABORT: expected = TX_WAIT_ABORT_ERROR; break; #ifdef TX_DISABLE_NOTIFY_CALLBACKS case THREAD_SERVICE_ENTRY_EXIT_NOTIFY: expected = TX_FEATURE_NOT_ENABLED; break; #endif default: expected = TX_SUCCESS; break; } return(expected); } /* Drive one thread service through its internal entry point. The error checking shell rejects most of these from any caller that is not a thread, so the inserts behind them cannot be reached through the published entry points at a forced system state. The shell itself is covered by its own tests; what is under test here is the insert behind it. */ static UINT thread_service_internal(UINT service) { UINT status; CHAR *name; UINT state; ULONG run_count; UINT priority; UINT threshold; ULONG time_slice; TX_THREAD *next_thread; TX_THREAD *next_suspended_thread; ULONG old_time_slice; switch (service) { case THREAD_SERVICE_CREATE: status = _tx_thread_create(&thread_1, "thread 1", thread_1_entry, 1, thread_1_stack, SCRATCH_STACK_SIZE, SCRATCH_PRIORITY, SCRATCH_PRIORITY, TX_NO_TIME_SLICE, TX_DONT_START); break; case THREAD_SERVICE_IDENTIFY: if (_tx_thread_identify() == TX_NULL) { status = TX_NOT_DONE; } else { status = TX_SUCCESS; } break; case THREAD_SERVICE_INFO_GET: status = _tx_thread_info_get(&thread_1, &name, &state, &run_count, &priority, &threshold, &time_slice, &next_thread, &next_suspended_thread); break; case THREAD_SERVICE_ENTRY_EXIT_NOTIFY: status = _tx_thread_entry_exit_notify(&thread_1, TX_NULL); break; case THREAD_SERVICE_PREEMPTION_CHANGE: status = _tx_thread_preemption_change(&thread_1, SCRATCH_PRIORITY, &threshold); break; case THREAD_SERVICE_PRIORITY_CHANGE: status = _tx_thread_priority_change(&thread_1, SCRATCH_PRIORITY, &priority); break; case THREAD_SERVICE_TIME_SLICE_CHANGE: status = _tx_thread_time_slice_change(&thread_1, TX_NO_TIME_SLICE, &old_time_slice); break; case THREAD_SERVICE_RESUME: status = _tx_thread_resume(&thread_1); break; case THREAD_SERVICE_SUSPEND: status = _tx_thread_suspend(&thread_1); break; case THREAD_SERVICE_WAIT_ABORT: status = _tx_thread_wait_abort(&thread_1); break; case THREAD_SERVICE_RELINQUISH: _tx_thread_relinquish(); status = TX_SUCCESS; break; case THREAD_SERVICE_TIME_SLICE: _tx_thread_time_slice(); status = TX_SUCCESS; break; case THREAD_SERVICE_SLEEP: /* The sleep service tests the system state itself, ahead of its own insert, and refuses every caller that is not a thread. The insert behind it is therefore only ever reached at system state zero. The call is made anyway so that the refusal is what the pass records. */ status = _tx_thread_sleep(1); break; case THREAD_SERVICE_RESET: status = _tx_thread_reset(&thread_2); if (_tx_thread_terminate(&thread_2) != TX_SUCCESS) { status = TX_NOT_DONE; } break; case THREAD_SERVICE_TERMINATE: status = _tx_thread_terminate(&thread_1); break; default: status = _tx_thread_delete(&thread_1); break; } return(status); } /* What each service returns at a forced system state. */ static UINT thread_service_internal_expected(UINT service) { UINT expected; if (service == THREAD_SERVICE_SLEEP) { expected = TX_CALLER_ERROR; } else { expected = thread_service_expected(service); } return(expected); } /* Run a pass over the services with the system state forced to the value the insert has to see. The SMP protection is held across the pass, so no other core and no interrupt can observe the forced value, and the interrupt nesting count the same array element carries is restored before anything else reads it. The element is the running core's, read rather than assumed. */ static UINT pass_with_system_state(ULONG system_state) { TX_INTERRUPT_SAVE_AREA UINT service; UINT core; UINT status = TX_SUCCESS; ULONG saved_system_state; TX_DISABLE core = tx_thread_smp_core_get(); saved_system_state = _tx_thread_system_state[core]; _tx_thread_system_state[core] = system_state; for (service = 0; service < THREAD_SERVICES; service++) { if (thread_service_internal(service) != thread_service_internal_expected(service)) { status = TX_NOT_DONE; } } _tx_thread_system_state[core] = saved_system_state; TX_RESTORE return(status); } /* Position the trace buffer so that the given event is the one that wraps it. A wrap reached by filling the buffer lands on whichever service happens to write the last entry; positioning puts each service's own insert on the boundary in turn. The scheduler's inserts sit behind the published ones, so the count says how many entries a service writes before the insert under test. */ static void position_before_buffer_end(ULONG entries) { _tx_trace_buffer_current_ptr = _tx_trace_buffer_end_ptr - entries; _tx_trace_header_ptr -> tx_trace_header_buffer_current_pointer = (ULONG) _tx_trace_buffer_current_ptr; } /* Position the buffer and drive one service, holding one SMP protection across both so that the boundary entry is the service's own by construction: every trace insert in the kernel is written inside that same protection, so nothing on another core can take the slot in between. The sleep is the one service that cannot be driven that way, because it suspends the calling thread and would carry the test's own protection into the scheduler. It is positioned and called without it, and the caller checks that the wrap happened. */ static UINT wrap_one_service(UINT service, ULONG entries) { TX_INTERRUPT_SAVE_AREA UINT status; if (service == THREAD_SERVICE_SLEEP) { TX_DISABLE position_before_buffer_end(entries); TX_RESTORE status = thread_service(service); } else { TX_DISABLE position_before_buffer_end(entries); status = thread_service(service); TX_RESTORE } return(status); } /* Count the trace buffer wraps the kernel reports. */ static VOID trace_buffer_full(void *buffer) { buffer_full_count++; } /* Run a pass over the services, wrapping the trace buffer on every one of them. The resume and the suspend are driven twice: once with the scheduler's insert behind them on the boundary, and once with their own insert there. Once a notification is registered, every service in the pass has to report a wrap of its own. Checking that per service rather than in total is what makes the positioning prove itself: a service whose boundary entry was taken by something else fails here instead of quietly losing coverage. */ static UINT pass_with_buffer_wrap(UINT check_wraps) { UINT service; UINT status = TX_SUCCESS; ULONG before; for (service = 0; service < THREAD_SERVICES; service++) { before = buffer_full_count; if ((service == THREAD_SERVICE_RESUME) || (service == THREAD_SERVICE_SUSPEND)) { /* Two entries short, so the scheduler's own insert behind the published one lands on the boundary. */ if (wrap_one_service(service, 2) != thread_service_expected(service)) { status = TX_NOT_DONE; } } else { if (wrap_one_service(service, 1) != thread_service_expected(service)) { status = TX_NOT_DONE; } } if ((check_wraps == TX_TRUE) && (buffer_full_count == before)) { status = TX_NOT_DONE; } } /* Now with the published service's own insert on the boundary. The scratch thread was deleted by the pass above, so the lifecycle is walked from the create. */ if (wrap_one_service(THREAD_SERVICE_CREATE, 1) != TX_SUCCESS) { status = TX_NOT_DONE; } if (wrap_one_service(THREAD_SERVICE_RESUME, 1) != TX_SUCCESS) { status = TX_NOT_DONE; } if (wrap_one_service(THREAD_SERVICE_SUSPEND, 1) != TX_SUCCESS) { status = TX_NOT_DONE; } if (wrap_one_service(THREAD_SERVICE_TERMINATE, 1) != TX_SUCCESS) { status = TX_NOT_DONE; } if (wrap_one_service(THREAD_SERVICE_DELETE, 1) != TX_SUCCESS) { status = TX_NOT_DONE; } return(status); } /* Drive the services an ISR may call, once, from interrupt context. */ static void test_isr(void) { UINT service; if (isr_pass_done == 0) { for (service = 0; service < ISR_SERVICES; service++) { if (thread_service_isr(isr_services[service]) != thread_service_isr_expected(isr_services[service])) { isr_status = TX_NOT_DONE; } } isr_pass_done = 1; } } #endif /* Define what the initial system looks like. */ #ifdef CTEST void test_application_define(void *first_unused_memory) #else void threadx_thread_trace_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; /* Put system definition stuff in here, e.g. thread creates and other assorted create information. The test thread carries a time slice, which the SMP time-slice check needs before it reaches its own trace insert. */ status = tx_thread_create(&thread_0, "thread 0", thread_0_entry, 0, pointer, TEST_STACK_SIZE_PRINTF, TEST_PRIORITY, TEST_PRIORITY, TEST_TIME_SLICE, TX_AUTO_START); pointer = pointer + TEST_STACK_SIZE_PRINTF; /* Check for status. */ if (status != TX_SUCCESS) { printf("Running Thread Trace Test.......................................... ERROR #1\n"); test_control_return(1); } /* Hold the scratch thread's stack. The thread itself is created and deleted once per pass, so only its stack outlives a pass. */ thread_1_stack = pointer; pointer = pointer + SCRATCH_STACK_SIZE; /* Create the reset target and leave it terminated, which is the one state a thread can be reset from. */ status = tx_thread_create(&thread_2, "thread 2", thread_1_entry, 2, pointer, SCRATCH_STACK_SIZE, SCRATCH_PRIORITY, SCRATCH_PRIORITY, TX_NO_TIME_SLICE, TX_DONT_START); pointer = pointer + SCRATCH_STACK_SIZE; /* Check for status. */ if (status != TX_SUCCESS) { printf("Running Thread Trace Test.......................................... ERROR #2\n"); test_control_return(1); } status = tx_thread_terminate(&thread_2); /* Check for status. */ if (status != TX_SUCCESS) { printf("Running Thread Trace Test.......................................... ERROR #3\n"); test_control_return(1); } /* Create the preempting thread. It runs above the test thread, so resuming it always preempts and suspending it always gives the processor back. */ status = tx_thread_create(&thread_3, "thread 3", thread_3_entry, 3, pointer, TEST_STACK_SIZE_PRINTF, PREEMPT_PRIORITY, PREEMPT_PRIORITY, TX_NO_TIME_SLICE, TX_DONT_START); pointer = pointer + TEST_STACK_SIZE_PRINTF; /* Check for status. */ if (status != TX_SUCCESS) { printf("Running Thread Trace Test.......................................... ERROR #4\n"); test_control_return(1); } } /* The scratch thread never runs: it is created below the test thread's priority and is suspended or terminated whenever the test thread gives up the processor. */ static void thread_1_entry(ULONG thread_input) { while (1) { tx_thread_suspend(&thread_1); } } /* The preempting thread gives the processor straight back, so a resume of it is a preemption and nothing else. */ static void thread_3_entry(ULONG thread_input) { while (1) { thread_3_counter++; tx_thread_suspend(&thread_3); } } /* Define the test thread. */ static void thread_0_entry(ULONG thread_input) { UINT status; UINT service; #ifdef TX_ENABLE_EVENT_TRACE ULONG wait; #endif /* Increment thread 0 counter. */ thread_0_counter++; /* Inform user of success getting to this test. */ printf("Running Thread Trace Test.......................................... "); /* Enable event tracing. */ status = tx_trace_enable(trace_buffer, sizeof(trace_buffer), TRACE_REGISTRY_ENTRIES); #ifndef TX_ENABLE_EVENT_TRACE /* Without tracing compiled in the inserts expand to nothing, so there is nothing here to drive. Check that the service says so and finish. */ if (status != TX_FEATURE_NOT_ENABLED) { printf("ERROR #5\n"); test_control_return(1); } /* One pass over the services, so the test still exercises them. */ for (service = 0; service < THREAD_SERVICES; service++) { if (thread_service(service) != thread_service_expected(service)) { printf("ERROR #6\n"); test_control_return(1); } } printf("SUCCESS!\n"); test_control_return(0); #else if (status != TX_SUCCESS) { printf("ERROR #7\n"); test_control_return(1); } /* Thread context, every event enabled: the insert runs and takes the current thread's priority. */ for (service = 0; service < THREAD_SERVICES; service++) { if (thread_service(service) != thread_service_expected(service)) { printf("ERROR #8\n"); test_control_return(1); } } /* Thread events filtered out: the insert is reached and declines to record anything. The scheduler's own events are filtered with them, so the inserts behind the published services are covered as well. */ status = tx_trace_event_filter(TX_TRACE_THREAD_EVENTS | TX_TRACE_INTERNAL_EVENTS); if (status != TX_SUCCESS) { printf("ERROR #9\n"); test_control_return(1); } for (service = 0; service < THREAD_SERVICES; service++) { if (thread_service(service) != thread_service_expected(service)) { printf("ERROR #10\n"); test_control_return(1); } } status = tx_trace_event_unfilter(TX_TRACE_THREAD_EVENTS | TX_TRACE_INTERNAL_EVENTS); if (status != TX_SUCCESS) { printf("ERROR #11\n"); test_control_return(1); } /* Interrupt context: the insert records the interrupted thread instead of a priority. Bounded, so a tick that never arrives fails the test rather than hanging it. */ test_isr_dispatch = test_isr; wait = 0; while ((isr_pass_done == 0) && (wait < 100)) { tx_thread_sleep(1); wait++; } test_isr_dispatch = TX_NULL; if (isr_pass_done == 0) { printf("ERROR #12\n"); test_control_return(1); } if (isr_status != TX_SUCCESS) { printf("ERROR #13\n"); test_control_return(1); } /* Initialization context: the insert has no thread to record at all. */ if (pass_with_system_state(TX_INITIALIZE_IN_PROGRESS) != TX_SUCCESS) { printf("ERROR #14\n"); test_control_return(1); } /* The same state an ISR presents, for the services an ISR may not call. */ if (pass_with_system_state((ULONG) 1) != TX_SUCCESS) { printf("ERROR #15\n"); test_control_return(1); } /* Wrap the trace buffer on every service with no notification registered, then again with one. Only the second pass can check that each service wrapped, because the count is what the notification keeps. */ if (pass_with_buffer_wrap(TX_FALSE) != TX_SUCCESS) { printf("ERROR #16\n"); test_control_return(1); } status = tx_trace_buffer_full_notify(trace_buffer_full); if (status != TX_SUCCESS) { printf("ERROR #17\n"); test_control_return(1); } if (pass_with_buffer_wrap(TX_TRUE) != TX_SUCCESS) { printf("ERROR #18\n"); test_control_return(1); } /* Every service in the last pass wrapped the buffer, so the notification has to have been called once for each of them. */ if (buffer_full_count < THREAD_SERVICES) { printf("ERROR #19\n"); test_control_return(1); } status = tx_trace_buffer_full_notify(TX_NULL); if (status != TX_SUCCESS) { printf("ERROR #20\n"); test_control_return(1); } /* Preemption, with tracing on. The scheduler records a resume before it knows which thread will run next and updates that entry in place once it does, so the update is only reached when a resume actually preempts and an entry was recorded to update. The same holds for the suspend that gives the processor back. */ for (wait = 0; wait < 4; wait++) { status = tx_thread_resume(&thread_3); if (status != TX_SUCCESS) { printf("ERROR #21\n"); test_control_return(1); } } if (thread_3_counter != 4) { printf("ERROR #22\n"); test_control_return(1); } status = tx_trace_disable(); if (status != TX_SUCCESS) { printf("ERROR #23\n"); test_control_return(1); } printf("SUCCESS!\n"); test_control_return(0); #endif }