Nothing prevents an application mixing tx_thread_create() with the POSIX layer,
and a thread created that way has no POSIX control block. posix_thread2tcb()
returns NULL for it, which posix_thread2tid() then read through:
p_tcb = posix_thread2tcb(thread_ptr);
thread_ID = p_tcb->pthreadID;
pthread_self() went on to compound it, reading the signal fields of a POSIX_TCB
out of a thread that is only a TX_THREAD:
if (((POSIX_TCB *) thread_ptr) -> signals.signal_handler)
The first is a null dereference and the second runs off the end of the control
block into whatever the linker put there. Under qemu-system-riscv32 the first one
lands first: mcause=0x5, a load access fault, with mtval=0xb4 for the offset of
pthreadID.
Have posix_thread2tid() report zero for a thread with no control block, which is
what px_pth_join.c already does for the same call, and have pthread_self() skip
the signal check unless the ID says the caller really is a pthread. Zero cannot
collide with a real ID because px_pth_create.c uses the address of the control
block as the ID.
This also covers the case where there is no current thread at all, from an ISR or
before the scheduler starts: tx_thread_identify() returns NULL, and the same
zero comes back instead of a fault.
Add posix_pthread_self_test, which asks both kinds of thread for their ID: a
pthread, which has to report what pthread_create() returned, and a plain ThreadX
thread, which has to report zero. Reverting either half of the fix turns the test
into the load access fault above.
Verified with riscv64-unknown-elf and qemu-system-riscv32: 4 tests across the
default build, 4 of 4 passing.
Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
* Added the POSIX compatibility layer to the CMake build
Nothing in the repository built the POSIX layer. The FreeRTOS layer next to it
has had a target since the CMake build was introduced, so the POSIX one was the
odd one out, and 106 source files went unbuilt by any target, on any
architecture.
Add a posix-threadx target, following the FreeRTOS layer's shape: a static
library, EXCLUDE_FROM_ALL so the default build is unchanged, linking threadx and
publishing its own directory as a PUBLIC include path. The sources live in their
own CMakeLists.txt rather than the top-level file, as common/ does, because there
are 106 of them. The seven posix_*.c files in the same directory are a demo and
standalone signal tests, each with its own entry point, so they stay out of the
library.
The layer does not suit every configuration, and the target is only offered where
it can work:
- Hosted simulation ports (linux, win32, win64) build against a C library that
already provides errno.h, pthread.h and the rest. The layer replaces those.
Its pthread.h even uses _PTHREAD_H, the same include guard as glibc's, so its
declarations are skipped wholesale and the build fails on missing types.
Neutralising that guard only exposes the real problem: 69 conflicting
definitions in a single translation unit, for time_t, struct timespec,
sigset_t, pthread_t, pthread_mutex_t, sem_t and more. Both the layer and the
C library implement POSIX, and only one of them can define those names. The
linux port also emulates threads by calling the C library's pthread_create
and sem_wait, which the layer exports itself, so linking the two would divert
the port into the layer that sits on top of it.
- SMP builds. px_int.h declares _tx_thread_current_ptr as a plain pointer,
which is a per-core array under SMP, and the layer tracks no current core.
Building the layer for the first time exposed one portability defect worth
fixing rather than working around. tx_posix.h defined ssize_t as INT, with a
comment conceding it should come from <sys/types.h>. That is correct only where
the C library agrees: on AArch64 newlib makes ssize_t 64 bits, and every
translation unit that reached a library header failed to compile. Defer to the
library when it has declared the type, keyed on the _*_DECLARED guards newlib
uses, and do the same for mode_t, which had the same problem waiting. Where no
library declaration exists the previous definitions still apply, so the 32-bit
targets that did build are unaffected.
Verified by building posix-threadx for arm9, arm11, cortex_m0, cortex_m3,
cortex_m4, cortex_m7, cortex_m33, cortex_m55, cortex_m85, cortex_a7, cortex_a9,
cortex_r4, cortex_r5, cortex_a34, cortex_a53 and cortex_a55 with
arm-gnu-toolchain-14.3.rel1, and for risc-v32 and risc-v64 with
riscv64-unknown-elf: 18 of 18, 106 objects each. cortex_a78 has no non-SMP port
and fails to configure with or without this change. Linking the result against
libthreadx.a leaves only tx_application_define, _tx_initialize_low_level, the
optional execution profile hooks, and memset and strlen unresolved, all of which
the application or its C library supplies. The default build still produces
libthreadx.a and no POSIX library.
Compiling is not the same as working, and on the 64-bit targets in that list it
is not enough. The layer carries a message by putting the address of a private
buffer into the queue, and ULONG is 32 bits on every port, so that address only
fits when TX_64_BIT is defined. Without it px_mq_send.c truncates the pointer
and px_mq_receive.c casts the truncated value back, which GCC reports as nothing
worse than a -Wpointer-to-int-cast warning. Defining TX_64_BIT is not a remedy
either: tx_api.h then reaches for the extension pointer macros, which need
tx_thread_extension_ptr in the thread control block, and outside ports_smp and
ports/linux no port declares it. So the target builds everywhere, but the
message queues are only sound on the 32-bit ports. That is pre-existing, it is
not made worse here, and it is left for a change of its own.
Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
* Added regression tests for the POSIX compatibility layer
The POSIX layer had no tests. The seven posix_*.c programs shipped beside it are
demos: they print nothing, report no result and end in infinite loops, so they
tell a person watching a debugger something and an automated run nothing.
Add a suite under test/posix, laid out like the FreeRTOS one and driven the same
way, with scripts/build_posix.sh and scripts/test_posix.sh over a run.sh that
takes the same arguments as its RISC-V counterpart.
The tests run on emulated hardware because they have nowhere else to go. The
layer replaces the C library's POSIX headers and exports the same symbols the
linux port calls to emulate threads, so a host build is not available to it. The
RISC-V QEMU harness that the ThreadX suite already uses is, and this suite reuses
its BSP and testcontrol.c rather than growing copies of them.
Three tests to start:
- posix_mq_basic_test sends a message through a queue and checks the contents
and priority survive the round trip.
- posix_mq_send_abort_test covers the leak fixed in #624, by filling a queue,
blocking a sender on it, aborting the wait and watching the queue's byte
pool. Reverting the fix makes it fail on the pool check, so it measures what
it claims to.
- posix_pthread_basic_test covers pthread creation, a mutex, a semaphore
handoff, pthread_self and collecting an exit value through pthread_join.
The queue's pool is sized (mq_maxmsg + 1) * (mq_msgsize + 11), which leaves room
for about one message beyond a full queue, so the abort test uses small messages
and a shallow queue. With a larger message the first leaked buffer exhausts the
pool, tx_byte_allocate fails, and the sender disappears into the endless loop in
posix_internal_error() instead of reporting anything. Sizing it this way keeps
the failure legible as a pool measurement rather than a timeout.
riscv32 only, and the reason is the layer rather than the harness. The layer puts
the address of a message buffer into the queue, ULONG is 32 bits on every port,
and a 64-bit address only fits there when TX_64_BIT is defined. Defining it makes
tx_api.h use the extension pointer macros, which need tx_thread_extension_ptr in
the thread control block, and no port outside ports_smp and ports/linux declares
it. Configuring for risc-v64 stops with that explanation rather than building
something that would corrupt a pointer at runtime.
Verified with riscv64-unknown-elf and qemu-system-riscv32: 3 tests across
default_build, disable_notify_callbacks_build, stack_checking_build and
trace_build, 12 of 12 passing.
Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
xQueueCreateStatic() and xTaskCreateStatic() take their storage from the
caller, so neither leaks memory, but both create ThreadX objects and both
return NULL when a later step fails. The caller is left without a handle and
cannot call the matching delete function, so any object already created stays
registered in the kernel, pointing into a caller buffer that the application
is now free to reuse or discard.
Three paths were affected. xQueueCreateStatic() abandoned the read semaphore
when the write semaphore could not be created. xTaskCreateStatic() abandoned
the notification semaphore when the thread could not be created, and abandoned
both the semaphore and the thread when the thread could not be resumed.
Delete what was already created before returning on each of them. The resume
path terminates the thread before deleting it, since a thread created with
TX_DONT_START is suspended rather than terminated, which is the same order the
idle task uses when it reaps a deleted task.
Extend the regression suite to cover all three paths, and add thread resume to
the set of entry points the harness can force to fail. Each static failure case
now uses its own control block, so a future regression on one path cannot carry
damage into the next case and report misleading counts there.
Verified against the layer as it stands on dev, where the three new checks fail
with the objects left behind, and against the fixed layer, where the suite
passes.
Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
* Fixed the resource leaks on the xQueueCreate error paths
xQueueCreate() allocated the queue descriptor and its backing memory, then
created two ThreadX semaphores, and returned NULL on either semaphore failure
without releasing anything. Since no handle reached the caller, vQueueDelete()
could not be used to recover, so both allocations were lost. A failure on the
second semaphore additionally abandoned the read semaphore it had already
created, leaving a live ThreadX control block inside freed memory.
Release the backing memory and the descriptor on both paths, and delete the
read semaphore before returning when the write semaphore cannot be created.
This is the teardown order vQueueDelete() already uses, and it matches the
cleanup xTaskCreate() performs on its own error paths.
Verified with a fault injection harness that intercepts the ThreadX byte pool
and semaphore entry points to force tx_semaphore_create() to fail on a chosen
call. On a read semaphore failure the layer previously performed 2 allocations
and 0 releases, and on a write semaphore failure 2 allocations, 0 releases and
0 semaphore deletions. It now performs 2 releases in both cases and deletes
the read semaphore in the second, with the byte pool restored to its prior
state.
Fixes https://github.com/eclipse-threadx/threadx/issues/570
Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
* Added a regression suite for the FreeRTOS compatibility layer
The compatibility layer had no tests in this repository, which is awkward for
its creation functions in particular. Each of them takes one or two byte pool
allocations for its bookkeeping and then creates ThreadX kernel objects, and
each returns NULL when a kernel object cannot be created. The caller is left
without a handle, so it cannot call the matching delete function, and anything
the layer failed to release is gone until the system restarts. A leaking
version and a correct version are indistinguishable from the outside, which is
how the leak in issue 570 went unnoticed.
Add a suite that counts what the layer takes and gives back. A test asks the
harness to fail a chosen kernel creation call, then checks the number of byte
pool allocations, releases, object creations and object deletions performed.
The ThreadX entry points are intercepted with the linker's --wrap so that
tx_freertos.c is compiled exactly as it ships, with no test hooks in it. Note
that tx_api.h maps the public API onto the error checking entry points, so the
_txe_ symbols are the ones wrapped. Coverage is the creation and teardown paths
of queues, tasks, semaphores, mutexes, event groups and timers, including a
regression test for the two paths fixed for issue 570.
The suite follows the layout of the existing ThreadX and SMP suites, is
registered with ctest, and runs in CI through the shared regression template.
It is built 32 bit because the Linux port defines ULONG as unsigned int on
x86_64 while the layer passes pointers through ULONG arguments, so a 64 bit
build truncates them. It is Linux only because --wrap has no MSVC equivalent,
and the CMake configuration says so rather than failing at link time.
Validated by building the suite against the layer as it stands before the
issue 570 fix, where the two expected checks fail with the leaked counts, and
against the fixed layer, where all three tests pass.
Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
Updated the SMP execution profile aggregate getters to copy each core's total into the matching output array element. Added a focused regression test for thread, ISR, and idle total getters.
Co-authored-by: Codex <codex@openai.com>
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 <223556219+Copilot@users.noreply.github.com>
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 223556219+Copilot@users.noreply.github.com
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 <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Codex (gpt 5.5) <codex@openai.com>
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.