| Age | Commit message (Collapse) | Author |
|
Fixes #723
With `TX_ENABLE_STACK_CHECKING` and `TX_ENABLE_RANDOM_NUMBER_STACK_FILLING` both
enabled, `_tx_thread_create` picked a random byte, stored it in
`tx_thread_stack_fill_value`, filled the stack with it -- and then cleared the
whole control block with `TX_MEMSET`. The stack held the pattern while the
control block claimed zero, so `TX_THREAD_STACK_CHECK` and
`_tx_thread_stack_analyze` compared against the wrong value for the entire life
of the thread.
The value is now computed into a local and written back after the clear. The
clear stays where it is, because the module manager's error checking walks the
created list before the control block may be touched.
Fixed in `common/src/tx_thread_create.c`, `common_smp/src/tx_thread_create.c`
and `txm_module_manager_thread_create.c`, where it additionally left a user-mode
module thread's kernel stack filled with zeros.
`threadx_thread_stack_fill_value_test` creates sixteen unstarted threads and
checks each control block against the pattern in its stack, tolerating a random
zero byte without letting that hide the defect. Added to both suites: `ERROR #3`
on `dev` in `stack_checking_rand_fill_build`, green with the fix. Five tx
configurations at 104 tests, five SMP at 117, and all three sources clean under
`-Wall -Wextra` across every combination of the four stack-filling switches.
Assisted-by: Copilot (Opus 5) <[email protected]>
|
|
_tx_thread_shell_entry and _tx_thread_terminate both publish a thread's
terminal state -- TX_COMPLETED or TX_TERMINATED -- and then call that
thread's exit notification callback, before the thread has been detached
from the ready list and before either service has finished with the pointer
it holds to the control block. That terminal state is exactly the state
_tx_thread_delete and _tx_thread_reset accept as authorization to
invalidate or rebuild the control block, and neither service tested whether
the transition producing it had finished.
A callback could therefore delete the thread it was called for -- and then
lawfully recreate it over the same memory, since delete exists to permit
that -- while the scheduler was still linked to the old incarnation.
tx_thread_create zeroes the whole control block and can auto-start the new
one, so the old priority list is left heading at a block whose own priority
field names a different list, with the old priority's map bit set behind
nothing. Alternatively a callback could reset a terminated thread, which
moves it out of the terminal state, and then resume it: the interrupted-
suspension logic in _tx_thread_system_resume refuses to void a suspension
only while the state is still terminal, so with the reset allowed first the
resume clears the suspending flag and restores TX_READY, the outer service
then finds the flag clear and skips the removal, and tx_thread_terminate
returns TX_SUCCESS for a thread that is runnable again.
Interrupt masking does not close the window, because the kernel restores
the prior posture before invoking the callback deliberately. On SMP
TX_RESTORE also releases the global protection, so the target can be
executing on another core while its callback runs -- and a reset there
memsets the stack a live core is running on. On the Linux, Win32 and Win64
host simulation ports the consequence is more immediate than corruption:
TX_THREAD_DELETE_PORT_COMPLETION cancels and joins the host thread backing
the deleted thread, so a callback-side delete of the completing thread
destroys the host thread the callback is running on.
The fix marks the transition and has the two services refuse a marked
target, returning the errors they already document, TX_DELETE_ERROR and
TX_NOT_DONE. The refusal is transient and the same call succeeds once the
transition has completed, so no documented lifecycle is lost; and it is in
the core services rather than the _txe_ wrappers, so disabling error
checking cannot disable it. Refusing the reset is also what closes the
resume path, without touching _tx_thread_system_resume: its existing
terminal-state test is sufficient once nothing can turn the terminal state
into TX_SUSPENDED from inside the window.
tx_thread_suspending is the marker, rather than a new control-block field.
It already means "a suspension is in progress" and is already true across
the callback in the two interruptable paths, so no field is added, the
public structure is unchanged, and sizeof(TX_THREAD) is unchanged --
which matters, because the Module Manager's object handling depends on the
sizes of the control blocks. Widening its lifetime was checked against
every reader rather than assumed. There are four: two in
_tx_thread_system_suspend and two in _tx_thread_system_resume. In every
window this change widens, the state is TX_COMPLETED or TX_TERMINATED, and
both resume readers already refuse to void a suspension for exactly those
two states, so their behaviour is unchanged; and no suspension routine is
called on the target in those windows, so the suspend readers never see
them. No suspension-initiating service can set the marker again inside a
window either: every one of them acts on a thread that is ready or
suspended.
Three sites needed changing beyond the two refusals, and the shape of each
was decided by where the marker can safely be cleared:
- The non-ready branch of _tx_thread_terminate cleared the marker before
the terminated extension and the callback, which is what left them free
to act on a control block the service still had mutex-release
processing to do against. The clear moves to the common tail, after the
last dereference of the target, and becomes the single clear site for
the whole service. In the interruptable ready branch the flag is
already false there, because _tx_thread_system_suspend cleared it when
it detached the thread, so the tail store is a second store of a value
the flag already holds -- cheaper than testing for it, and it keeps one
clear site.
- Under TX_NOT_INTERRUPTABLE neither path set the marker at all, because
that configuration does not use the interruptable suspension path that
sets it. Both now set it before the callback. Interrupts being disabled
there does not help: the callback is reached by a direct call.
- In the TX_NOT_INTERRUPTABLE completion path the marker is cleared
before _tx_thread_system_ni_suspend rather than after it. That call
returns to the scheduler for a thread that is the current thread, which
a completing thread is, and does not come back; clearing afterwards
would leave a normally completed thread marked for ever and therefore
permanently undeletable. Nothing is lost by clearing early there,
because everything from that point to the detachment runs with
interrupts disabled and calls no application code.
The change is the same change twice. All four files are byte-for-byte
identical between common and common_smp at this commit and stay so after
it, so common_smp was written by copying rather than by repeating the
edits. tx_thread_system_suspend.c and tx_thread_system_resume.c, which do
differ between the kernels, are deliberately untouched.
Tests. The in-tree regression test goes to both trees and is byte-for-byte
identical between them. It drives seven scenarios: terminating a ready
non-current target with two peers ready at the same priority, with the
callback attempting the delete and recreating the block if it succeeded;
the same with the callback attempting the reset and then the resume;
terminating a target suspended on a semaphore while owning a mutex, which
is the non-ready branch; natural completion alone at its priority,
including the safe post-completion reset, terminate, delete and recreate at
another priority; self termination; a benign callback, whose notification
count and ordering are unchanged; and the state and boundary cases, where
the new refusal must not fire.
It measures rather than describes. The callback records the published
state, the marker, and the status of every lifecycle service it can reach,
calling the core service as well as the wrapper wherever a refusal is
expected. A snapshot taken under interrupt lockout -- which is the global
SMP protection on an SMP port -- checks that every ready list agrees with
the control blocks it heads, that the priority map agrees with the lists,
and that each execute pointer is a member of the list its own priority
field names. Every walk is bounded, so a corrupted ring costs an assertion
and not a hang, and no test in the suite can hang. Expectations are counted
inside a scenario and gated between scenarios, so a failing kernel reports
how much it failed by without being driven further into its own
corruption.
The consequences are demonstrated from the terminator's context rather than
the completing thread's, which is what makes the pre-fix behaviour an
assertion instead of a wedged simulator. Compiled against the unfixed
sources the test fails 9 of the 24 expectations it reaches in the
uniprocessor tree and 10 of 24 in the SMP tree, and the failures are the
finding: the callback-side delete succeeds, the recreate succeeds, the
consistency snapshot disagrees, the target is neither terminal nor detached
when the service returns, and a peer has left the ready ring the recreated
block hijacked.
The SMP tree gets a second test for the case that needs concurrency. The
victim is excluded to core 1 and spins there without relinquishing while
the controller, excluded to core 0, terminates it, so the callback runs on
one core while the target executes on another. The callback-side reset and
delete must both be refused, and a sentinel written into the unused low end
of the victim's stack must survive -- a reset would have memset the whole
stack before rebuilding the frame. Every wait is bounded, and if the remote
precondition cannot be established the test says so and drops only the
assertions that depend on it rather than reporting a pass it did not earn;
measured over twenty consecutive runs it established the precondition every
time.
TX_NOT_INTERRUPTABLE and TX_DISABLE_ERROR_CHECKING are not among the five
build configurations either tree compiles, and each tree builds the whole
library once per configuration, so neither can be reached from inside the
suites. The lines this change adds under TX_NOT_INTERRUPTABLE are therefore
in no configuration the trees build, and they are where the permanent-
undeletability failure mode lives, so they get their own harness rather
than a compile check: the four sources plus the two error wrappers are
compiled directly into a test executable, once per combination, with
recorders standing behind the scheduler services they call. That is what
makes the marker's value at the moment of detachment directly observable.
It runs 66 expectations under TX_NOT_INTERRUPTABLE, 66 under that with
error checking disabled, 45 under that with notification disabled, and 63
under error checking disabled alone; against the unfixed sources those fail
25, 22, 6 and 20 respectively. The harness lives in the uniprocessor tree
only, because the four sources are identical between the kernels and the
shim replaces the very primitive the SMP port differs in, so a second copy
would compile the same text under the same macros. It is deliberately left
out of the coverage instrumentation, since the same source under different
feature macros has a different line set and merging those would confuse the
union rather than add to it.
Results. Both suites pass in all five configurations with GCC 14: 103 of
103 in the uniprocessor tree, up from 98, and 116 of 116 in the SMP tree,
up from 114. Merged line coverage is 100% in the uniprocessor tree and
5172 of 5183 in the SMP tree, whose eleven uncovered lines are the same
eleven that were uncovered before this change and are in tx_byte_pool_search
and tx_thread_smp_utilities; all four changed files are at 100% line
coverage in both trees, and SMP branch coverage rises from 2819 of 3548 to
2831 of 3556. Cross-compiled with arm-none-eabi-gcc at -Wall -Wextra for
Cortex-M4 against common and for Cortex-A7 SMP against common_smp, all four
files produce no diagnostics at all and an identical warning set to before
the change, under -std=gnu99 and -std=c99 alike -- unlike the module ports,
-std=c99 does not fail on these base ports, and even -Wconversion is clean.
No MISRA deviation is required: explicit comparisons to TX_TRUE, existing
ThreadX types, single-entry and single-exit control flow, no goto, and two
added constant-time tests that change no real-time complexity.
Assisted-by: Claude Code (Opus 5) <[email protected]>
|
|
Against the merged coverage report -- every build configuration instrumented and
unioned -- eighteen lines of common/src were uncovered. Seventeen of them were
one missing scenario rather than eighteen separate gaps.
tx_block_allocate, tx_byte_allocate, tx_thread_system_suspend and
tx_thread_system_resume each carry blocks under TX_ENABLE_EVENT_TRACE that go
back and patch a trace entry once the call has done its work, all of the shape:
if (entry_ptr != TX_NULL)
{
if (time_stamp == entry_ptr -> tx_trace_buffer_entry_time_stamp)
entry_ptr comes from _tx_trace_buffer_current_ptr, which stays TX_NULL until
tx_trace_enable is called at run time. Building with TX_ENABLE_EVENT_TRACE is
not enough, and exactly one test in the suite enables tracing --
threadx_trace_basic_test -- which tests the enable API itself and never calls
either allocator. So those blocks sat in the report's denominator and never in
its covered set.
threadx_trace_entry_update_test enables tracing and then drives both allocators
twice each, once on the path that succeeds immediately and once through a
suspension that a second thread satisfies, since each allocator carries one
update block on either side. It then sleeps so that the last runnable thread
suspends with nothing ready to take over: tx_thread_system_suspend lines 345 and
351 are on the branch that sets _tx_thread_execute_ptr to TX_NULL, and the two
allocator suspensions never reach it because the other thread was always ready.
The same test closes tx_trace_object_register's TX_NULL name break by creating a
semaphore with no name. A semaphore and not a thread deliberately: for
TX_TRACE_OBJECT_TYPE_THREAD the register function dereferences the pointer it is
given to read the thread's priority, so that type needs a real TX_THREAD behind
it. threadx_trace_basic_test makes the equivalent call only under
ifndef TX_ENABLE_EVENT_TRACE, against the no-op stub.
threadx_thread_misaligned_stack_test covers the remaining line,
tx_thread_create.c:136, where a stack that does not begin on a ULONG boundary
costs a ULONG of size so that rounding the start up cannot run past the end of
the caller's buffer. Every other test hands tx_thread_create an aligned stack.
That line is compiled only under TX_ENABLE_STACK_CHECKING, so it is absent from
three of the five configurations' reports rather than uncovered in them, and it
was verified under stack_checking_build.
Measured on the merged report, all 480 tests passing and 5 of 5 configurations
green: 4485 of 4503 covered before, 4502 of 4503 after, denominator unchanged.
One line remains, tx_thread_system_resume.c:529, and it is the report's last
flapping line rather than a standing gap -- two clean runs of the same tree gave
4503 of 4503 and 4502 of 4503. Reaching it by construction was tried twice and
failed both times, so it is left alone here. It needs the preempt disable flag
and the system state both clear, and tx_thread_resume raises the preempt disable
flag before calling _tx_thread_system_resume, as do the put and send paths;
creating a higher priority auto-start thread from thread context does not raise
it but does not reach the check either, which a probe showed is executed only
during initialisation, with the system state at TX_INITIALIZE_IN_PROGRESS.
Assisted-by: Claude Opus 5 <[email protected]>
|
|
Applied the standard MIT license header to all project-owned C, header,
assembly, shell, and Python files that were missing a copyright notice.
Third-party, toolchain startup, and auto-generated files were excluded.
Co-authored-by: Copilot <[email protected]>
|
|
risc-v: refactor, consolidate, and fix RV32/RV64 ports
Consolidates the RISC-V 32-bit and 64-bit GNU/Clang port sources, fixes two
pre-existing assembly bugs discovered during testing, and hardens the build
infrastructure for both the regression suite and the CORE-V MCU example.
--- Port consolidation (RV32 GNU + Clang) ---
- Delete ports/risc-v32/clang/src/ (8 .S files had no Clang-specific
directives; diverged from GNU only due to missing bug fixes). The Clang
port CMakeLists.txt now compiles from ../gnu/src/.
- Change .global -> .weak for _tx_initialize_low_level in gnu/src/ to allow
BSP-level override without a linker conflict (adopted from Clang port).
- Create ports/risc-v32/common/tx_port_riscv32_common.h with all definitions
shared between GNU and Clang ports. Reduce both tx_port.h files to thin
wrappers.
- Add a prominent comment in risc-v64/gnu/inc/tx_port.h explaining why
LONG/ULONG are intentionally 32-bit on RV64 (ThreadX ABI requirement,
mirrors win64/MSVC LLP64).
--- Shared CMake helper ---
- Add cmake/threadx_riscv_port.cmake with threadx_add_riscv_port(). All
three port CMakeLists.txt files are reduced to ~8 lines each. Include path
is relative to CMAKE_CURRENT_LIST_DIR so the helper works whether ports
are built standalone or as a subdirectory of the test framework.
--- Shared example-build drivers ---
- Create canonical driver files under ports/risc-v_common/:
inc/csr.h (uintptr_t-based; portable RV32 + RV64)
example_build/plic/ (plic.c, plic.h)
example_build/uart/ (uart_qemu_ns16550.c/h; static inline putc_nolock)
example_build/trap/ (trap_qemu.c; XLEN-portable mcause constants)
- Replace per-example copies with symlinks in all qemu_virt and cva6_ariane
example directories.
- Fix OS_IS_INTERRUPT typo (was OS_IS_INTERUPT) in shared trap_qemu.c.
- Gate print_hex() behind TX_RISCV_TRAP_DEBUG.
--- Bug fixes in RV32 assembly ---
tx_thread_schedule.S:
- Solicited-return FP path: reload t0 from the mepc stack slot before
csrw mepc, t0. After the FP restore block, t0 held the fcsr value (0 for
new threads), which caused mepc = 0 and an immediate instruction-address
fault on the first context switch.
- Same path: reload t0 from the mstatus stack slot before csrw mstatus, t0
to avoid writing the stale fcsr value into mstatus.
tx_thread_system_return.S:
- FP callee-saved registers were saved unconditionally before the mstatus.FS
check, causing an illegal instruction trap (mcause=0x2) when a thread with
FS=Off (lazy FPU, thread has never used FP) voluntarily yielded.
- Apply the same FS guard pattern used in tx_thread_context_save.S: read
mstatus first, isolate FS[1:0], and skip fsw/fsd if FS == Off.
Both bugs were pre-existing on origin/dev and are unrelated to the
consolidation changes.
--- RV64 64-bit pointer compatibility ---
- Add TX_TIMER_INTERNAL_EXTENSION, TX_THREAD_CREATE_TIMEOUT_SETUP, and
TX_THREAD_TIMEOUT_POINTER_SETUP to risc-v64/gnu/inc/tx_port.h to store the
thread timeout pointer in a VOID
* extension field rather than truncating it
into a 32-bit ULONG. Mirrors the win64 port pattern.
- Define TX_TIMER_EXTENSION_PTR_DEFINED as a portable sentinel.
- Update threadx_thread_basic_execution_test.c guard from #if defined(_WIN64)
to #if defined(_WIN64) || defined(TX_TIMER_EXTENSION_PTR_DEFINED).
- Disable -Wconversion for the RV64 test build: ULONG = unsigned int (32-bit)
is intentional for ThreadX ABI but triggers spurious warnings when sizeof()
(8 bytes on RV64) appears in arithmetic with ULONG in common/src/.
--- Regression suite cmake fixes ---
test/tx/cmake/riscv/regression/CMakeLists.txt:
- Build testcontrol_weak_defaults.c as a separate OBJECT library and include
it in every test executable via $<TARGET_OBJECTS:>. GNU ld does not extract
objects from a static archive to satisfy weak symbols, so bundling it in
test_utility was insufficient for the standalone
threadx_initialize_kernel_setup_test.
test/tx/cmake/regression/CMakeLists.txt,
test/smp/cmake/regression/CMakeLists.txt:
- Same fix applied to the Linux and SMP regression builds. The symbols
abort_all_threads_suspended_on_mutex, suspend_lowest_priority, and
abort_and_resume_byte_allocating_thread were introduced by the win64 merge
and left the standalone test unlinkable.
--- CORE-V MCU toolchain and build fixes ---
cmake/riscv64-gcc-rv32imc.cmake:
- Resolve riscv64-unknown-elf-gcc via PATH so the riscv-collab toolchain in
/opt/riscv/bin is preferred when it appears first.
ports/risc-v32/gnu/example_build/core_v_mcu/bsp/clz.c (new):
- The riscv-collab toolchain is built without rv32 multilib, so its libgcc
does not define __clzsi2 (the helper emitted for __builtin_clz() in fll.c).
Add a weak __clzsi2 fallback so the build is self-contained with any
riscv64-unknown-elf toolchain. The weak attribute yields to a
libgcc-provided strong symbol when the Ubuntu multilib package is used.
core_v_mcu/CMakeLists.txt:
- Add bsp/clz.c to sources.
- Reference CMAKE_TOOLCHAIN_FILE via message(STATUS) to suppress the false-
positive "Manually-specified variables were not used by the project" CMake
warning and to show the active toolchain at configure time.
--- Housekeeping ---
- Rename azrtos_test_* -> threadx_test_* (eliminate Azure RTOS branding).
- Add RV64 QEMU CI test script:
ports/risc-v64/gnu/example_build/qemu_virt/test/
threadx_test_tx_gnu_riscv64_qemu.py
- Normalize entry.s -> entry.S in all 4 example directories.
- .gitignore: exclude build_m7/ and .codex local artifacts.
- CI: comment out the riscv regression workflow job and remove it from the
deploy job's needs list (preserved in-place for easy re-enablement).
--- Verified ---
- 95/95 RV32 regression tests pass (QEMU virt)
- 95/95 RV64 regression tests pass (QEMU virt)
- All 5 Linux build configurations build cleanly (default_build_coverage,
disable_notify_callbacks_build, stack_checking_build,
stack_checking_rand_fill_build, trace_build)
- CORE-V MCU example_build links cleanly with /opt/riscv toolchain
Co-authored-by: Copilot [email protected]
|
|
Windows x64 port and regression suite
This PR adds the Windows x64 (Win64) simulation port for both the standalone
and SMP variants of ThreadX, along with the full CMake build and test
infrastructure needed to run the regression suite on Windows.
New ports
Win64 standalone (ports/win64/vs_2022): self-contained Windows simulation
port using Win32 threading primitives as virtual cores. Includes CMake
integration, build/test scripts, and MSVC project files.
Win64 SMP (ports/win64_smp/vs_2022): multi-core Windows simulation port.
Supports up to 4 virtual cores backed by Windows host threads.
Scheduler and timer improvements
The initial port used coarse polling and synchronous SuspendThread/ResumeThread
pairs throughout the scheduler hot path. Several rounds of optimization reduced
the SMP regression suite runtime from ~150 s to ~78 s (-48%), with no
regressions:
- Replaced scheduler polling with an event-driven wake path; switched the
simulated timer to one-shot rearming to eliminate catch-up ticks.
- Skip SuspendThread when _tx_thread_preempt_disable != 0 (new suspension
type 3) -- the primary optimization, yielding up to 7.9x speedup on
preemption-heavy tests.
- Skip SuspendThread when a thread is spinning on the Win32 critical section
(suspension type 4), and fix a stale-TLS bug in
_tx_win32_critical_section_obtain that could stamp mutex_access on the
wrong virtual core.
- Added a 2 ms scheduler event timeout (matching the Linux SMP port) to
prevent stalls on any missed SetEvent.
- Enabled high-resolution waitable timers (SetWaitableTimerEx) for accurate
100 Hz tick cadence.
- Increased TX_WIN32_CONTENTION_PAUSE_COUNT from 64 to 256 to reduce
SwitchToThread overhead under heavy CS contention.
Build and test infrastructure
- Hardened the Windows build wrapper (scripts/build_tx.ps1): invoke Ninja
directly for Ninja build trees, fix timeout detection, add a default build
timeout, and limit fallback replay to real timeout cases.
- Added -Clean support to Windows test scripts to remove stale CTest state
before each run.
- Skip Visual Studio DevShell re-entry when the active MSVC environment
already matches the requested architecture.
- Fixed scripts/build_tx.sh (Linux) regression source generation: replaced
brittle exact-string insertion with line-based matching so the interrupt
dispatcher hook is inserted reliably for both simulator ports.
Test suite updates
- Introduced test/tx/regression/threadx_test_port.h with portable macros
(TX_TEST_POINTER_WORD, TX_TEST_STORE_POINTER) for storing pointers in test
arrays on 64-bit targets where ULONG remains 32-bit.
- Adjusted pool-capacity and pointer-storage patterns in regression tests to
use ALIGN_TYPE-sized slots, making the suite correct on 64-bit hosts.
- Restored stricter event flag, sleep, and timer expectations now that
port-level fixes make prior Windows accommodations unnecessary.
- Tightened SMP watchdog and clean-build timeout defaults.
Version metadata
Updated Win32, Win64, and Win64 SMP port version strings to 6.5.1.202602.
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Codex (gpt 5.5) <[email protected]>
|
|
Summary
-------
This commit fixes the issue #424
Details
--------
- Add a the configuration option TX_QUEUE_MESSAGE_MAX_SIZE in the tx_api.h with default value
set to TX_ULONG_16 to keep backword compatibility.
- Update the txe_queue_create() to check on TX_QUEUE_MESSAGE_MAX_SIZE rather than TX_ULONG_16
as max message size.
- Add a new unitary test to cover the new change.
|
|
|
|
|