Conversation
The RX GCC assembly port contains an unclosed C-style comment in tx_thread_context_save.S. Because .S files are passed through the C preprocessor before assembly, the nested comment can trigger GCC warnings. Close the affected comment block without changing assembly instructions or runtime behavior. Signed-off-by: FranCDoc <fchiesadoc@gmail.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>
…#554) * Fixed Cortex-A9 SMP time source Updated Cortex-A9 SMP timestamp reads to use the global timer low counter instead of the decrementing private timer counter. Applied the change to GNU and AC5 ports. * Fixed remaining Arm SMP time sources Updated Cortex-A5, Cortex-A7, and Cortex-R8 SMP timestamp hooks so execution profiling uses incrementing time sources instead of the decrementing private timer count register. Cortex-A5 and Cortex-R8 now read the global timer count low register, matching the Cortex-A9 fix. Cortex-A7 now reads the generic timer physical count register. --------- Co-authored-by: Codex <codex@openai.com>
Follow up on the issue-541 execution profiling work. PR #553 fixed SMP execution profile total-time aggregation, and PR #554 fixed Armv7/R SMP timestamp hooks that used decrementing private timers. This change addresses the remaining related gap in ARMv8-A SMP ports: their timestamp hooks returned zero, which left execution profiling and trace timestamps without a progressing time source. Replace those stubs with the architectural generic physical counter, CNTPCT_EL0, across the ARMv8-A SMP GNU, AC6, IAR, and GHS variants. The local ARMv8 AArch64 system timer example already uses CNTPCT_EL0 for physical count reads, so this keeps the SMP timestamp hook aligned with the existing port examples while preserving the 32-bit ULONG return contract. Co-authored-by: Codex <codex@openai.com>
* Add SysTick counter reset in Cortex-M ports The SysTick counter value (SYST_CVR @0xE000E018) is indeterminate after reset. Without clearing it prior to enabling the counter, the first tick interval becomes unpredictable. * Fixed missing comment and added generated-by header in Cortex-M0/AC6 port Added the missing inline comment on the LDR r1, =0 instruction (Build value for SysTick reset) and the required AI-generated attribution comment under the copyright header. --------- Co-authored-by: Frédéric Desbiens <frederic.desbiens@eclipse-foundation.org> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use explicit immediate syntax when clearing BASEPRI in GNU and AC6 Cortex-M scheduler assembly. Co-authored-by: Codex <codex@openai.com>
Use explicit immediate syntax when testing the Thumb bit in AC6 Cortex-R4 stack build assembly. Co-authored-by: Codex <codex@openai.com>
Added a compile-time #pragma message warning to the module library source so that any module that includes or compiles this file receives an explicit deprecation notice at build time. Updated the internal documentation block in the manager-side implementation to explain that this function must not be called directly and that calling it on a live object causes a use-after-free. The Module Manager dispatch layer already releases pool memory automatically after a successful tx_*_delete() call. Module authors should remove any explicit call to txm_module_object_deallocate(). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Added #pragma message compile-time warning to the module library source and updated the DESCRIPTION blocks in both the library and manager implementations. Reason: this wrapper passes UINT_MAX as the name-buffer length to the underlying extended search. The comparison loop can therefore read past the end of a short name buffer, which is undefined behaviour. Callers should use txm_module_object_pointer_get_extended() and supply the actual buffer length. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fixed missing VFP thread extension on Cortex-R4 and R5 ports Building cortex_r4/gnu, cortex_r5/gnu or cortex_r5/ac6 with TX_ENABLE_VFP_SUPPORT corrupts memory. The assembly in tx_thread_schedule, tx_thread_system_return and tx_thread_context_restore reads and writes a per-thread VFP enable flag as [thread, #144], but every TX_THREAD_EXTENSION in these ports' tx_port.h was empty, so no such member existed. Offset 144 in the resulting TX_THREAD is tx_thread_filex_ptr, so the damage runs both ways: - tx_thread_vfp_enable() stores 1 into tx_thread_filex_ptr, after which FileX dereferences 0x1; - conversely, once FileX sets that pointer the context switch reads it as "floating point enabled" and starts pushing about 132 bytes of floating-point context onto a thread stack never sized for it, then restores unrelated data into the D registers. sizeof(TX_THREAD) is 180 on these ports, so 144 is a live member rather than padding past the end of the structure. What makes this dangerous is that it is silent. The defect was reproduced at runtime by reverting the Cortex-R52 port -- which inherited the same assembly and the same hard-coded 144 from the R5 port -- to this state and running its floating-point demo on the Armv8-R AEM FVP. Every floating-point value was still preserved exactly and no corruption was reported across interrupts, because a non-zero filex_ptr reads as "enabled" and the context switch dutifully saves and restores. The only visible damage was tx_thread_filex_ptr reading 0x00000001. In other words the feature you enabled appears to work, and what breaks is an unrelated pointer that nothing notices until FileX is introduced. With the field present the same demo reports the sentinel intact. This appears to be drift rather than a deliberate limitation. All fourteen Armv7-A port and toolchain combinations define the field and contain the VFP code paths. The R-profile family is inconsistent in both directions: these three have the code paths without the field, while cortex_r4/ac5, cortex_r4/ac6, cortex_r4/iar, cortex_r5/ghs, cortex_r5/iar, cortex_r7/ghs and cortex_r8_smp/ac5 define the field but contain no VFP code paths. The fix follows the Armv7-A ports exactly: TX_THREAD_EXTENSION_2 carries tx_thread_vfp_enable, and tx_thread_vfp_enable/disable are declared. The field is defined unconditionally rather than under TX_ENABLE_VFP_SUPPORT because the offset is hard-coded in assembly, so a conditional member would shift every following field and be correct in only one configuration. No assembly changes are needed: 144 is already correct once the member exists. Verified with GCC 14.3: on all three ports tx_thread_vfp_enable now lands at exactly offset 144, matching the assembly, with tx_thread_filex_ptr moved to 148. The library builds cleanly for cortex_r4/gnu and cortex_r5/gnu both with and without TX_ENABLE_VFP_SUPPORT, and the VFP save and restore instructions appear in the port assembly only when it is requested. cortex_r5/ac6 is verified by offset and inspection only, as that toolchain was not available here. Runtime verification was performed on Armv8-R as described above rather than on R4/R5 silicon; upstream QEMU's xlnx-zcu102 machine holds its Cortex-R5F cores in reset, so no R5 target was available. ABI note for release documentation: adding the member moves tx_thread_filex_ptr and everything after it by four bytes and grows TX_THREAD from 180 to 184 bytes. This is the layout the Armv7-A ports already have, so the change aligns R-profile with A-profile rather than introducing a new one, but kernel awareness and TraceX tooling that hard-codes offsets will need rebuilding. Follow-up worth considering separately: nothing in the toolchain ties the literal 144 in the assembly to the C structure, which is what allowed this to go unnoticed. A compile-time assertion per port turns any recurrence into a build failure -- when the experiment above reverted the field, that assertion failed the build before a corrupting binary could be produced. ports/cortex_r52/gnu/src/tx_port_offset_check.c is a working reference. * Added compile-time offset checks to the Cortex-R4 and R5 ports Nothing in the toolchain connects the structure offsets hard-coded in the port assembly to the C definition of TX_THREAD, which is what allowed the missing VFP thread extension to go unnoticed. These files assert the offsets that the context-switch path depends on, so a recurrence becomes a build failure instead of memory corruption discovered later at runtime. Three offsets are asserted: the VFP enable flag at 144, the thread stack pointer at 8 and the run counter at 4. The stack pointer and run counter precede every extension macro in TX_THREAD, so they are stable by construction. Offset 144 was checked against the build options that could plausibly move it -- stack checking, event trace, event logging, performance info and TX_NOT_INTERRUPTABLE -- and is unchanged by all of them, because the only conditional member nearby guards tx_thread_filex_ptr, which follows the extension, and TX_THREAD_USER_EXTENSION appears much later in the structure. The assertions therefore cannot misfire on a legitimate configuration. C99 has no _Static_assert, so a negative array dimension is used. Each file compiles clean under -std=c99 -pedantic -Wall -Wextra and emits zero bytes of code or data. Verified that the checks actually catch regressions rather than merely compiling: asserting a deliberately wrong offset is rejected on all three ports, and removing the VFP field again fails the build outright with a message naming the missing member. These ports have no CMake build of their own, so the files take effect only in builds that compile everything under src/. That is still worthwhile given they cost nothing and emit nothing. Extending the same technique to the remaining ports is deliberately left as separate work: it requires reading each port's assembly to attribute every hard-coded literal to the right structure, and copying assertions without that analysis would risk asserting wrong offsets. Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
Added a ThreadX port for Arm Cortex-R52 with the GNU toolchain
Adds ports/cortex_r52/gnu together with a CMake build, an example build for the
freely available Armv8-R AEM FVP, and an automated test suite.
The port is EL2-aware by construction. Cortex-R52 always implements EL2 and
resets into it, so the reset path configures EL2, installs both the EL2 and EL1
vector tables, and only then drops to EL1 to run the kernel. Keeping that
structure from the start lets partitioning work reuse the boot path rather than
replace it; TX_R52_BOOT_AT_EL1 skips the EL2 stage where a vendor monitor has
already dropped privilege.
This is also the first R-profile port in the tree with a working CMake build.
cmake/cortex_a9.cmake exists but ports/cortex_a9/gnu/CMakeLists.txt is empty, so
no A- or R-profile port could be built this way before.
Contents:
- Kernel port: 16 assembly sources plus tx_port.h, seeded from the
Cortex-R5/GNU port.
- Build: cmake/cortex_r52.cmake and the port CMakeLists.txt, with soft/hard
float, VFP, FIQ and IRQ/FIQ nesting options.
- Example BSP: EL2 to EL1 boot, GICv3, generic timer, PMSAv8-R MPU,
semihosting and PL011 consoles.
- Tests: six images registered with CTest, each judging itself and
terminating the model.
- tx_port_offset_check.c: compile-time assertions on the TX_THREAD offsets
the assembly reaches by hard-coded displacement. This is the same defect
class fixed for Cortex-R4/R5 in #578; nothing in the toolchain ties those
literals to the C structure.
Two facts were established by experiment rather than taken from documentation,
and are recorded in the code and the readme because both are traps:
- PRBAR.AP bit order is reversed relative to th
AArch64 macro set: the low bit is read-only and the high bit grants EL0
access. Programming four disjoint regions, one per encoding, and
attempting a privileged write to each gave 0b
and 0b10 allowed. Using the AArch64 ordering
as read-only and silently accept writes, because region coverage is still
enforced: an unmapped address faults while a "read-only" region does not.
- The generic timer PPI is INTID 30, recorded from ICC_IAR1 after enabling
the whole PPI range 25-31, rather than assume
Model behaviour that a silicon port must revisit: the FVP leaves CNTFRQ at zero
with the system counter stopped, so the BSP programs both; CNTHCTL.PL1PCTEN,
CNTHCTL.PL1PCEN and ICC_HSRE must be set at EL2 or EL1 accesses trap; and
tx_thread_vfp_enable() sets only a per-thread software flag, so enabling the FPU
hardware (CPACR, FPEXC.EN) is the board support package's responsibility, not
the kernel's. The FVP reports MIDR 0x410FD0F0, an architecture envelope model
rather than a Cortex-R52, so it validates architecture and not implementation.
Nothing in the port is gated on MIDR.
Changes outside ports/cortex_r52, three and all small: cmake/cortex_r52.cmake is
a new toolchain file; CMakeLists.txt gains enable_testing() at the top level,
without which add_test() in a subdirectory generat
the root never references, so ctest reports no tests from the build root; and
.gitignore covers build directories and __pycache__.
Verification: all six images build warning-free a
FVP_BaseR_AEMv8R in three configurations -- protection off, protection and
caches on, and both together with hard-float VFP. Six build-option
combinations build clean and pass the tick-and-preemption demo at run time.
The tests assert behaviour rather than configuration, which is what caught the
problems above: the cooperative demo asserts the order of execution, because
counting alone cannot distinguish working context switches from one thread
running to completion, and the MPU test provokes real faults, because reading
SCTLR back only proves a bit was set.
The 96-test regression suite is host-side and pas
configurations on the Linux port. It is not cross-run here: it validates
portable kernel logic, while the port-specific risk is the assembly, which the
FVP images exercise. Structural coverage is therefore not claimed.
Deliberate deviations: passing a string literal to tx_thread_create reports a
discarded const qualifier from common/inc/tx_api.
CHAR *; demo_threadx.c keeps the shipped sample's int main() so the demo body
stays byte-identical to it; and the floating-point test compares exactly on
purpose, since a tolerance would mask a restored
wrong.
Not included: modules support, split-mode SMP and
support. The example targets the FVP only, and NXP S32Z280 bring-up is
separate work; the readme flags what to re-verify there.
Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
The host-side module converter utilities allocated code_buffer inside the loop over the ELF code sections and never released it, so every code section in the input ELF leaked one buffer. The malloc() result was also unchecked, so a failed allocation passed a null pointer on to elf_object_read() and crashed the tool. Release the buffer at the end of each iteration and report a clean failure with exit code 5 when the allocation does not succeed. Verified with AddressSanitizer on a two-code-section input: 128 bytes leaked in 2 allocations before, none after, with byte-identical output. Fixes #571 Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
* Hardened the module converter utilities against malformed input While reviewing the code_buffer leak reported in issue 571, three further pre-existing defects turned up in the same host-side utilities. The four ELF area allocations in module_to_binary.c and module_to_c_array.c were unchecked, and every elf_object_read() return value was discarded, so a truncated or crafted ELF file was read into whatever the allocation and the reads happened to leave behind. Check each allocation, distinguishing a NULL return for an empty area from a genuine failure, and abandon the conversion with exit code 5 on an allocation failure and exit code 6 on a read failure. Validate the section string table index taken from the ELF header before it is used to subscript the section header area. AddressSanitizer confirms that an out-of-range index produced a heap buffer overflow in both tools. Correct the address format specifiers in module_to_c_array.c and module_binary_to_c_array.c, which passed an unsigned long to %08X, and close the source file on the invalid format path of module_binary_to_c_array.c. The unused current_total local is removed. All three utilities now build warning free with gcc -std=c99 -Wall -Wextra, and the code they emit is unchanged byte for byte on valid input. Refresh the version banners of all three tools, on the console and in the header written into the generated C arrays, to the 2024 Microsoft Corp and 2026 Eclipse ThreadX contributors copyrights and version v6.5.2.202603. The banners still advertised v5.8 and v5.4 with a 2018 build date. The .exe suffix is dropped from the tool names, since these tools build on Linux too. Related to #571 Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com> * Added the missing licence header to module_binary_to_c_array.c The file carried no copyright or licence header at all, unlike the two other converter utilities in the same directory. Use the same MIT header they carry, since the three tools share an origin. Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
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 #570 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 #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>
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>
_tx_thread_system_return_inline() in the Cortex-M4 AC6 tx_port.h was followed by a second, orphaned copy of its own body. The copy had no function header, so it declared interrupt_save at file scope and then placed statements there, which does not compile. It is also the older version of the body, without the dsb and isb barriers, so it was left behind rather than intended: the barriers were added by commit 33efad3 and the previous text was not removed. Delete the orphaned copy. What remains is the same body every sibling port carries: after this change ports/cortex_m4/ac6/inc/tx_port.h differs from ports/cortex_m4/iar/inc/tx_port.h and ports/cortex_m7/ac6/inc/tx_port.h only in the port name in the banner and the version string, as it should. Verified by compiling the function in isolation, which fails on the file scope statements before the change and is clean afterwards, and by a structural scan of all 208 tx_port.h files in the repository confirming this was the only occurrence. Fixes #569 Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
The Cortex-M ports under ports/ are generated. scripts/copy_armv7_m.sh copies one tx_port.h and the per tool sources to fifteen M3, M4 and M7 targets, and scripts/copy_armv8_m.sh does the same for nine M33, M55 and M85 targets. The ports_arch_check workflow runs both scripts and fails if the tree is not reproducible, so those copies are meant never to be edited directly. They were. Every Cortex-M fix since #523 was applied to the generated copies and not to the source, so the source fell behind and the check went red: running the three scripts on dev changes 35 files. The check triggers only on pull requests targeting master, which is why nothing caught it while the fixes were merged into dev. Left alone, the next run of these scripts would have reverted three separate pieces of work: the memory barriers and clobbers from #523, the correction of the IAR assembly header to use the assembler's own comment syntax, and the move of tx_initialize_low_level.S into example_build for the M33, M55 and M85 GNU ports from #514. Bring the sources up to what the ports carry today, and regenerate. Two behavioural changes come with that, both deliberate. The barriers from #523 reach the ac5 and keil variants of M3, M4 and M7, which were outside the scope of that fix and never received it. The barrier that follows restoring the interrupt posture, which #523 gave only to the GNU ports because GNU was the only toolchain that could be tested, now applies to every tool; the identical asm statement already shipped in the AC6 and IAR ports, so this adds a pipeline flush rather than any new compiler exposure. Regenerating also drops a stray #endif at the end of the Cortex-M85 IAR tx_port.h, added by #523, which left that header with one more #endif than #if and unable to compile. Every other ARMv8-M port was balanced. Verified that the scripts are idempotent afterwards, that ports_arch_check would pass, that no port loses a barrier or a clobber, that every regenerated header is preprocessor balanced, and that every Cortex-M port covered by the two scripts now carries the entry barrier. Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
…aration (#591) Three defects reached the repository through the port trees recently, and each of them is mechanically detectable without a cross compiler. Add scripts/check_ports.sh, which looks for exactly those three, and give CI and the release process the same command a contributor can run locally. The generated Cortex-M ports must be reproducible from ports_arch. Fixes were applied to the generated copies instead of the source for eight months, and the next run of the copy scripts would have reverted them. Preprocessor directives must balance. A fix left the Cortex-M85 IAR tx_port.h with one more #endif than #if, so that header could not compile. No port header may carry a statement outside a function body. A fix left a second, headerless copy of a function body in the Cortex-M4 AC6 tx_port.h, which is issue 569. The check tracks brace depth while skipping preprocessor lines, multi-line macro bodies and comments, and reports assignments, dereferences and control statements that land at file scope. Headers under example_build are excluded, since those trees vendor third party SDK code. A fourth section reports, without failing the run, on port families that have no copy script and so cannot be checked for reproducibility. It currently observes that the Cortex-M0 ac5, ac6 and keil ports lack the barriers their gnu and iar siblings have. ports_arch_check now calls the script rather than inlining a copy and diff, so CI and the command line check the same things by the same definition, and the workflow now triggers on pull requests to dev as well as master. Triggering on master alone is why the drift went unseen. prepare_release.sh runs the checks before it branches or rewrites anything, and stops if they fail, with SKIP_PORT_CHECKS=1 as the escape hatch. Each check was verified by reintroducing the defect it exists to catch and confirming that the script fails, then confirming it passes on a clean tree. Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
…source (#592) The ARMv7-A and ARMv8-A ports are generated by update.ps1, which needs PowerShell, so the cortex-a job in ports_arch_check ran on a Windows image and nobody could reproduce it locally on Linux. Add update.sh beside each update.ps1, with the same cores, compilers, copy sets and patches, and move the job to the same Linux image as everything else. The bash scripts were checked against the PowerShell ones by comparing what each reports as drifted. They agree exactly on the 63 files the Windows job last reported, and differ on 12 more, which turn out to be a defect in update.ps1 rather than in the port. Its two .cproject patterns are written as 'value=`"cortex-a7`"' with backticks that survive into the pattern, so that replacement has never matched, while the neighbouring Cortex-A7.NoFPU pattern has no backticks and always worked. The result is that the AC6 example builds for the A5, A8, A9, A12, A15 and A17 cores name cortex-a7 as their CPU while their FPU string is correct. The bash scripts do what the PowerShell ones intended, so regenerating corrects those twelve files. Restore ports_arch as the source for the rest. The implementation of _tx_thread_smp_time_get from #555 was applied to the twenty four generated SMP ports and never to ports_arch, which still held MOV x0, #0 with a FIXME comment, so regenerating would have replaced a working generic timer read with a stub. That implementation now lives in the source. The remaining differences are cosmetic and resolve in favour of the source: a trailing blank line in 38 copies of tx_thread_schedule.S and comment spacing in one tx_port.h. Note that the Cortex-A VFP fix is already present in ports_arch and was never at risk, contrary to what the description of the port consistency checks change said before this was measured. Extend scripts/check_ports.sh to run the A profile generators too, and make it fail when a generator fails or is missing rather than reporting a clean tree, which would have been a false pass. Pin every workflow to ubuntu-24.04. ubuntu-latest already resolves to that image, so nothing changes today, but a future migration becomes a deliberate commit rather than something that happens underneath the -m32 builds. Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
… that way (#593) The gnu ports are only ever built with GNU tooling, and GNU as accepts several non-canonical forms that LLVM's assembler rejects. Nothing noticed, because nothing built them with anything else. This matters beyond clang itself: Arm Toolchain for Embedded is LLVM based and is the successor to Arm Compiler 6, so these are the code paths ac6 users move onto. Seven files needed changing, none of which alters the emitted code: LDREX and STREX take no offset in A32 state; the #imm form is Thumb-2 only. GNU as drops the redundant zero, LLVM rejects it. Removed from the Cortex-A5, A7 and A9 SMP protect routines. ARMv8-M Baseline has no flag-preserving MOV immediate, so GNU as already emits MOVS. Writing MOVS in the two Cortex-M23 sources says what the assembler was doing anyway. One of them sits in a branch only compiled for the single mode secure configurations, which is why it had never surfaced. The Cortex-M0 schedule routine wrote LDR r0, =#0x10000000 with a stray hash, which its own sibling file already wrote correctly. The Cortex-M0 system return routine selected the numbered subsection .text 32, which makes LLVM place the constant pool beyond the range a Thumb-1 PC relative load can reach. Plain .text fixes it and GNU accepts either form. The reason is recorded in the file, since 32 files pair a numbered subsection with a literal pool load and the rest only escape because Thumb-2 and A32 have far more range. Add scripts/check_clang.sh, which assembles every Arm gnu port source and compiles the common C sources for one core per architecture profile, and a clang_check workflow that installs Arm Toolchain for Embedded and runs it. The toolchain version is pinned and checksum verified, for the same reason the runner image is pinned. The port directory to target mapping in that script is explicit rather than prefix matched. Prefix matching is what makes cortex_a5 also match cortex_a53 and cortex_a55, which are AArch64, and assembling those as ARM32 produces hundreds of misleading errors; that mistake cost real time while measuring this, so the reason is recorded next to the table. Verified with Arm Toolchain for Embedded 22.1.0: 711 of 711 assembly sources assemble and 185 of 185 C sources compile for all eight profiles, against 6 assembly failures before the change. arm-none-eabi-gcc still assembles all 315 ARM32 sources, so nothing regressed for GNU. The check was confirmed to fail when any one of the fixes is reverted. Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
…en on Linux (#594) * Made the example builds work with LLVM, and fixed four that were broken on Linux The gnu example builds are the natural LLVM path: Arm Toolchain for Embedded is LLVM based, consumes GNU ld linker scripts, and needs none of the scatter files or Arm DS projects the ac6 examples carry. So rather than port the ac6 examples, teach the gnu scripts to drive either toolchain. Each script now selects the toolchain from a TOOLCHAIN variable that defaults to gnu, so every existing invocation behaves exactly as before. TOOLCHAIN=atfe switches the compiler, adds the target triple, passes the entry symbol through to the linker rather than to the driver, and links the toolchain's own semihosting library in place of --specs=nosys.specs, which is a GCC spec file mechanism with no LLVM equivalent. That last choice avoids adding a syscall stub source to every example. The scripts are parameterised rather than duplicated. Copies of build scripts would drift the first time one side was edited, which is the failure this repository has just spent several changes recovering from. Four sample scripts could not run on a case-sensitive filesystem at all. They compiled MP_PrivateTimer.s and V7.s while the files on disk are MP_PrivateTimer.S and v7.s, and the Cortex-A5 and A9 scripts named MP_GIC.s where the file is MP_GIC.S. The link lines named V7.o accordingly. Corrected to match the files, which is why the Cortex-A5, A7, A8 and A9 examples now build on Linux where before they could not. Extend scripts/check_clang.sh to link the example builds as well as compile the sources, since compiling proves the sources parse while only linking exercises entry symbols, linker scripts and the C library together. Eight examples are listed as not expected to link, each with its reason, so the gaps stay visible rather than being silently skipped. Five of them fail with the GNU toolchain too and are therefore not LLVM problems: the Cortex-M0 example's crt0 references __text_load_start__, __text_start__ and __text_end__, which its linker script never defines, while the Cortex-M4 script defines the equivalents; the arm9, arm11, Cortex-R4 and Cortex-R5 examples need newlib multilib variants that are not present in every GNU toolchain packaging. The Cortex-A12, A15 and A17 examples fail only with LLVM, because their link line omits -nostartfiles so the toolchain's own crt0 is linked and wants picolibc's __data_start, __data_source, __data_size and __bss_size, which their linker script does not define. Verified by building every example with both toolchains. Seven link with both: Cortex-A5, A7, A8, A9, M3, M4 and M7. The GNU results are unchanged where they worked before, and now also succeed for the four scripts with the case bug. Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com> * Resolved the compiler path before running the example builds The example stage runs each build script from inside its own directory, so a relative path passed with --clang stopped resolving there and every example build failed instantly. Local runs passed an absolute path and did not show it; the CI job passes a path relative to the workspace root, which did. Resolve the compiler to an absolute path once, before any directory change, and print it so the toolchain in use is visible in the log. Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com> * Parameterised the archiver as well, and stopped the check hiding failures The toolchain selection covered the compiler but not arm-none-eabi-ar, which the scripts invoke 628 times to assemble the library archive. A machine with an LLVM toolchain and no GNU one therefore could not build any example, which is exactly the situation in CI: the runner has no Arm GNU toolchain installed. Local runs had one, so this only appeared once the job ran. Select the archiver alongside the compiler, taking llvm-ar from beside clang in the toolchain. The four scripts that call arm-none-eabi-ld directly are left alone: they are arm9, arm11, Cortex-R4 and Cortex-R5, all already listed as not expected to link, and their invocations are specific to GNU ld in ways that parameterising would not resolve. The check reported "example build produced no image" and then filtered the log for lines containing "error", which hid the actual cause, since a missing tool reports "command not found" or "No such file or directory". It now prints the tail of the log. That filtering cost two CI round trips to diagnose something the first run already knew. Verified by shadowing arm-none-eabi-gcc, arm-none-eabi-ar, arm-none-eabi-ld and the aarch64 equivalents with stubs that fail loudly, then running the whole check: all 711 sources assemble, all eight profiles compile and all seven example builds link without any GNU tool being invoked. The GNU default path still produces an identical image. The diagnostics were confirmed by pointing the archiver at a name that does not exist and checking that the reason appears. Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
… toolchains (#595) Two unrelated Cortex-M0 gaps, both left over from earlier work. The memory barriers from #523 reached the Cortex-M0 gnu port but not its ac6 or iar siblings, in either the inline system return in tx_port.h or the assembly routine. Both take the same GNU or IAR code path, and iar already carried the entry barrier, so the missing pieces were the entry pair for ac6 and the barrier after restoring the interrupt posture for both. All three tools now match. The Cortex-M0 example could not link with any toolchain. cortexm0_crt0.S references 23 linker script symbols and the script defined only 12 of them, so __text_start__, __text_end__, __text_load_start__, the rodata and fast section symbols, and the ctors and dtors load addresses were all unresolved. The Cortex-M4 script defines all 23, including a .fast section with no content whose symbols exist so that the startup copy is a no-op, and its comment says as much. The Cortex-M0 script is brought to that same shape. That left the example failing under LLVM only, on instructions that ARMv6-M can encode just one way. The file declared .code 16 but no syntax mode, so GNU as used the legacy divided syntax in which a plain add or sub sets the flags implicitly, while LLVM implements unified syntax only and rejected the non-flag-setting spelling. Declaring .syntax unified and writing movs, adds and subs makes both assemblers agree, and the encodings GNU produces are byte identical before and after, verified by disassembling both objects. The Cortex-M0 example now links with GNU at 22,520 bytes of text and with Arm Toolchain for Embedded at 22,866, so it comes off the list of examples not expected to link and scripts/check_clang.sh now links eight of eight. Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
…#596) Those three sample scripts compiled crt0.S and reset.S but named neither on the link line, and omitted -nostartfiles, so the toolchain was also free to link its own startup. Under GNU that produced a working image; under an LLVM toolchain picolibc's crt0 was pulled in and wanted __data_start, __data_source, __data_size, __bss_size, __stack, __tls_base and __arm32_tls_tcb_offset, none of which the linker script defines. Defining picolibc's contract in the shared linker script was tried first and abandoned: it reaches into ABI constants that cannot be verified here, and it is unnecessary, because the in-tree crt0.S is already a complete startup for this script. It sets up the stack and zeroes BSS between __bss_start__ and __bss_end__, and .data carries no AT() so there is nothing to copy. So the three scripts now pass -nostartfiles and name crt0.o and reset.o, which is what the four sibling A profile cores already do and what these scripts were evidently compiling those files for. Both the old and the new GNU images contain the in-tree startup, __vectors from reset.S and _mainCRTStartup from crt0.S, so the startup was already being used through implicit startup file resolution rather than an explicit operand. How that resolution happened without the objects being named is not accounted for here, which is itself the argument for naming them: the same implicit behaviour does not hold across toolchains, and that is why the LLVM link failed. Add a readme to each of the three example directories. Their tx_initialize_low_level.S is the generic ARMv7-A skeleton, 300 lines and identical across all three, with no interrupt controller programming, no timer and no vector table installation, where the A5, A7, A8 and A9 examples have all three and ship the matching Versatile Express support files. These three therefore demonstrate that the port builds; they will not receive a timer tick. Nothing in the tree said so, which invites the assumption that they are equivalent. Verified with both toolchains for all three cores. GNU links at 41,276 bytes of text, down from 41,768 because the unused toolchain startup is no longer included, and Arm Toolchain for Embedded links at 37,982 where it previously could not link at all. The resulting image is structurally sound: entry at _start, __vectors at address zero, and a BSS range in RAM. It has not been executed. scripts/check_clang.sh now links eleven of eleven example builds. Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
The AArch64 examples could only be built through the Arm Development Studio project files beside them. There was no script, so nothing in CI or on a developer's machine could build them, and the sources they need were never named anywhere: vectors.S, v8_aarch64.S and v8_utils.S were present but unreferenced, which is why a hand written link failed on GetCPUID, GetAffinity, InvalidateUDCaches and ZeroBlock. Those four are defined in v8_aarch64.S and v8_utils.S, in the tree all along. Add one pair of scripts, in ports_arch so every AArch64 port receives them. Both derive the -mcpu value and the kernel source directory from the port directory they sit in, so a single pair serves the twelve ThreadX ports and the twelve SMP ports, the latter building against common_smp and picking up the C sources those ports carry alongside the assembly. TOOLCHAIN=atfe selects Arm Toolchain for Embedded in place of the GNU toolchain, as it does for the ARMv7 example scripts. One symbol genuinely had no definition. startup.S calls initialise_monitor_handles to open the standard file handles over a debugger connection; the GNU toolchain provides it in libgloss through --specs=rdimon.specs, while picolibc has no equivalent and neither does the LLVM toolchain's semihosting library. semihost_stub.S supplies a weak no-op, linked for that toolchain only, so a real definition always wins. Extend scripts/check_clang.sh to cover ports_smp as well as ports, since the example builds now exist there too. Verified by building every AArch64 example with Arm Toolchain for Embedded: twelve ThreadX ports at 314,744 to 315,256 bytes of text and twelve SMP ports at 325,304 to 325,880. scripts/check_clang.sh now reports 35 of 35 example builds linking with none failing, the twenty-four new ones plus the eleven that already built. The images have not been executed: the sample targets the Base platform peripheral addresses, which the available emulator does not provide. Cortex-A34, and the Cortex-A34 and A78 SMP ports, are left out. They are not in the generator's core list, so they receive nothing from ports_arch and cannot be checked for reproducibility either. Bringing them in rewrites 114 files in those three directories, which deserves its own change. Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
The comment block describing what the script does was left half-rewritten when the example link stage was added to it: one sentence ends mid-clause and the word "profile" appears twice, once as the tail of a sentence that was meant to be replaced. Say the three stages plainly instead, and keep the point the truncated sentence was making, that only the linking stage needs a target C library. This block is what --help prints, so the damage was user visible. Comment only; no behaviour change. Verified that --help still frames the intended lines, since it selects them by number. Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
…599) ports/cortex_a34, ports_smp/cortex_a34_smp and ports_smp/cortex_a78_smp were absent from the ARMv8-A generator's core list, so ports_arch never reached them and scripts/check_ports.sh could not detect that they had drifted. They had. The two Cortex-A34 ports sat two releases behind the shared sources, at 6.1.10 against 6.3.0. The difference is not only banners: tx_initialize_low_level.S lacked the SUB x1, x1, #15 that precedes the BIC when the system stack pointer is recorded, so the value stored was the incoming SP rather than the first 16-byte boundary below it. The Arm Development Studio example was missing GICv3_aliases.h, which every other port's GICv3_gicc.h includes to reach the interrupt controller registers through the stringify indirection. The Cortex-A78 SMP port had never had its debug launch configuration patched at all. It still named the A35 platform and model, so Debug Cortex-A35, Base_A35x4 and FVP_Base_Cortex-A35x4 would have started an A35 model for an A78 port. Add cortex_a34 to the core list. Cortex-A78 cannot go in the same list, because the generator walks cores against port sets and would then create a ports/cortex_a78 that the tree has never had; give it a separate SMP-only list consulted only for the tx_smp port set. Both changes are mirrored in update.ps1, which stays equivalent to update.sh. Verified by regenerating: exactly these three port directories change, 116 files modified and 13 added, and no already-covered core moves, so the core list was the only thing holding them back. scripts/check_ports.sh passes, which now means these three are checked for reproducibility for the first time. scripts/check_clang.sh reports 38 of 38 example builds linked, the three new ports included. readme_threadx.txt is not added here. Only the two Cortex-A35 template ports carry it; the other 23 AArch64 ports do not, so its absence from Cortex-A34 is the tree's norm rather than drift, and supplying it everywhere is separate work. Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
…ped examples (#600) Two gaps in check_clang.sh, both of which made the Cortex-R52 port look better covered than it was. C_CORES gains cortex_r52. That list is described as one core per architecture profile, and Armv8-R AArch32 is a profile rather than a variant of Armv7-R: the port is written by hand instead of generated from ports_arch, so cortex_r5 does not stand in for its tx_port.h. The assembly was already covered, since #593 added the PORT_TARGET entry, but the common C sources had never been compiled against that header. The example stage skipped any directory lacking both driver scripts, and did so in silence. A port absent from the count reads as covered, which is how the two CMake based example builds under ports/cortex_r52 looked like part of the total while never being built. The stage now reports what it passed over, in the two distinct cases that exist. The comment above EXAMPLES_EXPECTED_TO_FAIL already asks for gaps to stay visible; this makes the code agree with it. Four Cortex-M ports turn out to carry build_threadx.sh with no build_threadx_sample.sh, so they were skipped despite having a driver: cortex_m23, cortex_m33, cortex_m55 and cortex_m85. Reported, not fixed. Whether those want a sample script is a separate question. The expected-to-fail test now runs before the Arm test rather than after. arm9 and arm11 are on that list but carry no PORT_TARGET entry, so testing for Arm first dropped them from the report entirely, reintroducing the same silence for two ports that were already being named. Verified with Arm Toolchain for Embedded 22.1.0, the version CI pins: 711 of 711 assembly sources, 185 of 185 common C sources for each of the nine cores including cortex_r52, and 38 of 38 example builds linking, so the built set is unchanged by this commit. The remaining driverless ports report as cortex_r52, cortex_a5_smp, cortex_a7_smp and cortex_a9_smp, the three A profile SMP ports having no example scripts and the R52 examples being driven by CMake. --help still frames the intended lines, since the additions sit below the range it selects by number. Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
…em (#601) The Cortex-M23, M33, M55 and M85 examples each shipped a build_threadx.sh with no build_threadx_sample.sh, so scripts/check_clang.sh skipped all four at its linking stage: the whole Armv8-M family had no example-link coverage. Adding the missing script surfaced five separate reasons why none of them could have linked. ports/cortex_m23/gnu/src/tx_initialize_low_level.S referenced Image$$ARM_LIB_STACK$$ZI$$Limit and __Vectors, which are Arm toolchain scatter-load names that no GNU linker defines. This is a GNU port, so it now uses __RAM_segment_used_end__ and _vectors, as the Cortex-M33 version already does. The Cortex-A ports mention Image$$ZI$$Limit only inside a comment; this was a live relocation. The Cortex-M23 crt0 needed unified assembler syntax. The Armv7-M file it derives from carries no .syntax directive, so GNU as reads it in the legacy divided syntax, where a Thumb data-processing instruction sets the flags whether or not the mnemonic says so, and "mov r2, #0" assembles to 2200, MOVS. LLVM implements unified syntax only, where those mnemonics mean the non-flag-setting forms that Armv8-M Baseline does not have. Disassembling the Cortex-M4 object confirms GNU already emits 2200 movs, 1a52 subs and 3001 adds, so spelling them out changes no encoding. The flags carry meaning: crt0_memory_copy branches on the result of "subs r2, r2, r1" and again on "subs r2, #1". Cortex-M23 has no SVC_Handler to name in its vector table. Its library is built -DTX_SINGLE_MODE_NON_SECURE, and tx_thread_schedule.S defines that handler only when neither TX_SINGLE_MODE_SECURE nor TX_SINGLE_MODE_NON_SECURE is set, so the SVCall slot takes __tx_BadHandler. The table also uses the CMSIS handler names throughout, because that is what the Armv8-M ports export: the Armv7-M table references __tx_SVCallHandler and __tx_SysTickHandler, which resolve to nothing here. The other three needed a C library. tx_thread_secure_stack.c calls malloc and free, which pulls the allocator in, and the sample scripts already defined SYSCALL_LIB for exactly that without ever passing it to the linker. Their linker scripts now also provide end and _end, which newlib's libnosys _sbrk wants, and __heap_start and __heap_end, which picolibc wants instead. Cortex-M55 and M85 build with the hard-float ABI now, in both the library and the sample. Arm Toolchain for Embedded ships no soft-float MVE multilib and says so plainly: "No library available for MVE with soft-float ABI." The library and the sample have to agree, and -mfloat-abi=hard is what check_clang.sh already uses for these two cores in PORT_TARGET. Verified with GNU 13.2.1 and Arm Toolchain for Embedded 22.1.0. Every port links under both: Cortex-M23 at 206,136 and 312,636 bytes, M33 at 305,856 and 320,696, M55 at 306,228 and 320,664, M85 at 306,240 and 320,668. check_clang.sh now reports 42 of 42 example builds linking, up from 38, and no longer lists any port as carrying build_threadx.sh without build_threadx_sample.sh. The images are link-verified only and have not been executed, as with the AArch64 examples. Only .sh drivers are added. Cortex-M23 has a build_threadx.bat with no sample counterpart and the other three have no .bat at all; adding untested Windows scripts belongs in its own change. Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
* Fixed the Cortex-A7 module data check to validate whole ranges The Cortex-A7 module port received a module instance, a start address and a byte size from the common Module Manager, then discarded the instance and the size and asked the MMU to translate the start address alone, for reading only. A range was therefore accepted whenever its first byte happened to be readable by the module, so privileged dispatch code could read or write past the end of the module's mapping, or use read-only module code as a write destination. The check now takes the size and an access intent. The module's own data region is answered from the manager's records, which name it exactly and cost no translations; everything else is answered by translating every page the range touches with the requested unprivileged access. Empty ranges, ranges whose last byte would wrap, and ranges that leave the recorded data region partway through are all refused, and the walk is bounded by TXM_MODULE_MANAGER_DATA_CHECK_MAX_PAGES so one module request cannot impose unbounded work on the kernel. Translation is only believed while the requesting module's own context is loaded. The outside direction gets its own check rather than negating the inside one: a range that reaches into the module only partway is inside neither answer, and negating a whole-range check would have let it pass as a kernel object. Other ports keep their single data check through backward-compatible fallbacks in the common header; their preprocessed dispatch output is byte-identical. Regression coverage runs on the host by standing a simulated page map behind the one architecture primitive, and reaches 100% line, branch and call coverage of the new logic. Twenty-four of its expectations fail against the previous implementation. Assisted-by: Claude Code (Opus 5) * Drove the Cortex-A7 range check through a live MMU The host test for this check stands a simulated page map behind the port's CP15 primitive, so it never executes an address translation. That leaves the part most easily got wrong unexercised: an encoding naming the wrong operation, or a PAR fault bit read the wrong way round, passes it without complaint. This adds a bare-metal image that builds a short-descriptor translation table, enables the MMU, and drives the real check through the real translations on a real Cortex-A7 translation regime, plus a script that builds and runs it under an emulator. Sections are mapped for unprivileged read/write, unprivileged read only, and privileged only, with an unmapped section behind the read-only one so that a range can be made to leave its mapping partway through. That last case is the one the replaced check accepted, and the test asserts both answers against the same live MMU: the current check rejects the range, and translating only its first address accepts it. It also confirms what the simulated map could only assume, that ATS1CUW denies a write to a read-only mapping while ATS1CUR allows the read. The write intent is the reason the port asks for two translations rather than one. The script skips with a notice when the cross toolchain or the emulator is absent, so a machine without them does not fail the build. Assisted-by: Claude Code (Opus 5)
…#753) The SMP Linux port suspends a thread with a signal whose handler calls sigsuspend and does not return until the thread is resumed. That signal can arrive while the thread is parked in pthread_mutex_lock on _tx_linux_mutex; the port knows it can, because _tx_linux_mutex_obtain sets tx_thread_linux_mutex_access around the lock call for exactly this case, and nothing anywhere reads that flag. glibc waits for a contended mutex in a loop that re-arms the futex wait after a signal, and this handler never returns to it. The next release hands its wake-up to that thread, which will not act on it, and any other thread parked on the mutex is never woken, leaving the mutex free with waiters on it. That deadlocks the process: the only thread that can resume the suspended one is the scheduler, and the scheduler takes this mutex on every pass. _tx_linux_mutex_obtain now waits with pthread_mutex_timedlock and retries, so the wait is re-armed every TX_LINUX_MUTEX_RETRY_NSEC and a lost wake-up costs one retry period instead of the process. The period is one millisecond, half the scheduler's own idle period. Nothing else changes. Four hung processes captured untraced, across three tests, show the same state: a thread in sigsuspend on top of pthread_mutex_lock, the scheduler blocked in pthread_mutex_lock, and the mutex reading free. Standalone, threadx_smp_random_resume_suspend_exclusion_test hung 3 times in 100 runs and 2 in 55 before the change and 0 in 400 after it. Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
This is the ThreadX half of the defect fixed for ThreadX SMP in an earlier pull request. The Linux port suspends a thread with a signal whose handler calls sigsuspend and does not return until the thread is resumed, and it takes its critical section with a bare pthread_mutex_lock through tx_linux_mutex_lock. A thread can therefore be signalled while it is parked on _tx_linux_mutex. glibc waits for a contended mutex in a loop that re-arms the futex wait after a signal, and this handler never returns to it. The next unlock hands its wake-up to that thread, which will not act on it, and any other thread parked on the mutex is never woken, leaving the mutex free with waiters on it. tx_linux_mutex_lock now calls a helper that waits with pthread_mutex_timedlock and retries, so the wait is re-armed every TX_LINUX_MUTEX_RETRY_NSEC and a lost wake-up costs one retry period instead of the process. The period is one millisecond. pthread_mutex_timedlock needs _GNU_SOURCE under -std=c99, which both of this port's build systems already define. These are the only two ports affected: a sigsuspend-based suspend handler exists in the Linux and SMP Linux ports and nowhere else, and those two are also the only ports taking a critical section with pthread_mutex_lock. Unlike the SMP port, this one has never been observed to deadlock, which is consistent with one emulated core and far less suspend and resume traffic. The fix is by inspection, and verified as breaking nothing: all seven configurations pass with retries disabled, 105/105 in five and 100/100 in two. Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
_tx_thread_relinquish walks the ready list at the relinquishing thread's priority for a thread to hand its core to. When the walk reaches one it cannot place, carrying preemption-threshold or excluded from that thread's mapped core, it sets the rebalance flag and breaks. That exit falls into the block concluding no other thread is ready, which is not what the walk found, and that block sets the finished flag. The rebalance at the end of the function is guarded on that flag, so it never runs. Every rebalance requested from inside the walk is discarded, because that exit always reaches the block first. A ready thread behind the obstacle then never reaches a core while the others hold them and relinquish to each other. The tick keeps arriving, so it is a livelock rather than a deadlock. The early return now applies only when no rebalance was requested, which lets control reach the rebalance path the other three request sites already use. threadx_smp_relinquish_rebalance_test fills the cores with threads of one priority relinquishing in a loop, places a thread excluded from every core behind them and a runnable thread behind that, and fails if the runnable one has not run within 100 ticks. It fails 3 of 3 before this change and passes 5 of 5 after. Instrumenting a passing run of threadx_thread_relinquish_test counted 12 rebalance requests from the walk against 4 performed, all 4 from sites outside it. 109/109 in all eight configurations, twice from clean. Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
) _tx_thread_smp_unprotect takes the Linux mutex on entry and releases it twice when the protection structure names this core, but only once when it does not, while the matching _tx_thread_smp_protect took it once either way. _tx_thread_system_return clears the protection outright, so an unprotect can find it no longer naming its core and return having released one level fewer than were taken. A thread that later reaches _tx_linux_mutex_release_all drains that level. The timer interrupt thread has none on its tick path unless a thread was preempted on core 0, so a level leaked there while core 0 is idle is never recovered: the nesting count never reaches zero, the mutex is never handed back to Linux, and every other thread waits on it for the life of the process while the tick keeps arriving. The release of the level the matching protect took now happens whether or not the protection still names this core. Protection bookkeeping and scheduling are unchanged, and releasing beyond what a thread holds was already a no-op. Two hung processes captured untraced, in different tests, show the timer thread owning the mutex with a nesting count of one while parked in its tick wait, the scheduler blocked in pthread_mutex_timedlock, and the clock advancing 99 ticks a second across a ten second sample while nothing else changes. On a healthy run that path is taken 0 times in 26,546 unprotect calls. Forcing it on the timer thread leaks one level before this change and balances after it. Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
The randomised phase of threadx_smp_random_resume_suspend_exclusion_pt_test guards its preemption-threshold setup with tx_thread_priority > 40, but the test creates its 1024 source threads with priority = i reset whenever it reaches TX_MAX_PRIORITIES, which is 32 on the Linux port the regression suite runs. The guard is therefore never true. The one thing that distinguishes this test from its non-pt twin in that phase never executes, so the randomised phase exercises no preemption threshold at all and the test duplicates its twin there. The threshold the earlier rebalance section sets is unaffected. Both the guard and the threshold gap are now derived from TX_MAX_PRIORITIES, so the phase sets a threshold whatever the port's priority count is. On a 32-priority port that is priority > 16 with a threshold eight levels better, which keeps the original shape of a threshold set on the worse half of the priority range. Verified under gdb that the block is now reached, at priority 26 with a threshold of 18, and that thread_entry's preemption-threshold invariant is entered for a randomised-phase thread; neither happened before the change. 40/40 passes, 20 runs each in disable_notify_callbacks_build and stack_checking_build. Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
The Cortex-R5 assembly guarded its execution-profile hooks with only the legacy TX_ENABLE_EXECUTION_CHANGE_NOTIFY symbol. The documented TX_EXECUTION_PROFILE_ENABLE configuration initialized profiling without recording thread or interrupt transitions. I made all AC5, AC6, GNU, Green Hills, and IAR hooks accept both symbols. I also extended the port consistency and GNU/LLVM feature checks to cover the current configuration. All 849 base assembly files and all 219 TX_EXECUTION_PROFILE_ENABLE files passed with GCC 14.2.1 and clang 22.1.0. A CMake/Ninja Cortex-R5 profile build emitted all seven expected hook relocations. Proprietary toolchains were not run. Assisted-by: Codex (GPT-5) <noreply@openai.com>
#763) Running git blame on a file header returns a tree-wide copyright, disclosure or version-constant pass rather than the commit that last touched the code. There have been 11 such passes in this repository since 2024, and every header line in the tree now points at one of them. Added .git-blame-ignore-revs listing those commits. GitHub applies the file to its blame view automatically; locally it takes one git config command, which the file documents in its own header. Additive. No source file is touched, and every listed commit was verified to be an ancestor of dev. Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
The ThreadX README sent readers to archived Markdown documentation. I linked the overview, Modules, and SMP guides to published HTML pages. The general documentation entry now follows the latest release. All replacement URLs returned HTTP 200; git diff --check passed. Assisted-by: Codex (gpt-6-astra) <noreply@openai.com>
Git reads trailers only from a block at the very end of a commit message. A squash merge concatenates the branch's messages, so every trailer but the last ends up mid-message, and an indented trailer is skipped as well. Both forms are real attributions and both are invisible to the trailer parser, which is what the project has been using to answer which agents touched a file. The script reads whole commit bodies instead. It groups by value, or lists one line per commit with --list, and takes a revision and a path the way git log does. On dev it reports 163 attributions where the trailer parser reports 122, so a quarter of the record was unreachable. The difference is entirely squashed and indented trailers, not new commits. Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
common_smp/src/tx_misra.c performs four implicit conversions that its monoprocessor twin makes explicit, and ignores a parameter the twin casts to void. Built with the warning set common_smp uses, the file reports five diagnostics that common/src/tx_misra.c does not: tx_misra.c:49 conversion to 'int' from 'UINT' may change the sign of the result tx_misra.c:93 conversion to 'ULONG' from 'int' may change the sign of the result tx_misra.c:151 the same tx_misra.c:221 the same tx_misra.c:624 unused parameter 'status' The two files are otherwise the same shim, so this is drift rather than a deliberate difference: common/src already carries (INT) on the memset value, (ULONG) on the three pointer differences, and (VOID)status. That the shim is the file reporting them is the reason to fix it. It exists to route the kernel's pointer conversions through functions a MISRA analysis can account for, and an implicit signed-to-unsigned conversion inside it is the class of construct it was written to remove. The test trees hide it. test/tx builds the kernel with -Werror, so the monoprocessor copy could never have regressed this way; test/smp has -Werror commented out, so the SMP copy warns and builds. Applying the monoprocessor form to all five sites. Object code is byte identical before and after, compared with --strip-debug at -m32 -std=c99 with TX_MISRA_ENABLE defined, so this changes diagnostics and nothing else. Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
* Updated ThreadX contribution guide for current workflows The existing guide left contributors without current build, test, and submission guidance. The guide now covers the shared project process and ThreadX-specific C99, port, simulator, CI, attribution, and release practices. Documentation links resolve and git diff --check passed. Build tests were not run for this documentation change. Assisted-by: Codex (gpt-6-sol) <noreply@openai.com> * Added visible spacing between contributor setup steps The ECA and Git setup items had no visible gap in GitHub Markdown. An indented line break now separates them while preserving list numbering. GitHub Markdown rendered the items in one list with the visible gap, and git diff --check passed. Assisted-by: Codex (gpt-6-sol) <noreply@openai.com>
…so (#762) * Normalized the AI disclosure line across every tracked file type The repository-wide pass covered source files only, so build files, CMake toolchain files, shell scripts and the GDB and manifest files kept the older per-edit form of the disclosure comment, which names a product and a model version. The Cortex-R52 module manager port then merged after that pass and brought the old form back into the sources as well. Replaced it with the fixed text in all of them, using the comment character each file already uses. Comment-only. 143 files, one line each. The repository now holds 601 files carrying exactly one disclosure line, none carrying the old form, and none carrying more than one. Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com> * Added a check that keeps the AI disclosure comment in its one accepted form Nothing enforced the disclosure convention, so the drift it exists to prevent returned twice: once when a port merged after the normalisation pass carrying the older per-edit form, and once because that pass had covered source files only, leaving build files and scripts untouched for months. Added scripts/check_ai_disclosure.sh, which rejects the superseded per-edit form, a doubled comment marker, more than one disclosure line in a file, and any spelling of the line that is not exact. It runs from repo_checks.yml, a workflow with no path filter, because a source-path filter is what hid the build files the first time. The check passes on this branch. Each of its four rules was confirmed to fail on a tree with that defect reintroduced, and to pass once it was removed. Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com> * Normalised the disclosure lines that landed after this branch Thirteen files reached dev after this branch was written, each carrying the superseded per-edit form. txm_module_manager_dispatch.h reached it with six stacked copies, naming the same product and the same model every time -- the accumulation the fixed text exists to prevent. Each of those files now carries one disclosure line in the accepted form. Where the accepted line was already present, the superseded ones are deleted rather than converted, so no file gains a second. check_ai_disclosure.sh reported eighteen hits across thirteen files before the pass and passes after it. Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com> * Exempted Markdown from the near-miss disclosure check The near-miss rule flags any line carrying the phrase "AI assistance" that is not the accepted text, which is right for source but wrong for documentation. The contribution guide has to quote the accepted line and say when it applies, so the check reports two paragraphs of prose as drift and fails the build. Markdown is now exempt from that rule alone. The three rules that matter for a documentation file -- superseded form, doubled comment marker, duplicate line -- still scan it, so a stale disclosure in a Markdown file is still caught. The check passes against a tree carrying the rewritten contribution guide, and still fails when a near miss is planted in a source file. Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com> * Corrected the trailer command named in the disclosure check The header comment points a reader at git's own trailer parser to find out which agents have touched a file. That parser reads trailers only from a block at the very end of a message, so a squash merge -- which concatenates a branch's messages -- buries every trailer but the last one mid-message, and an indented trailer is skipped outright. On dev it finds 122 attributions where 163 exist. The comment now names count_assisted_by.sh, which reads whole bodies. Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
Fixes #61 Object names are exposed as writable pointers throughout the kernel API, which rejects string literals in C++ and lets a caller modify a name an object still holds. Information services return those names through writable double pointers. Create services, control blocks, information services, the module manager and trace registration now preserve const qualification, behind TX_ENABLE_CONST_NAMES. The option defaults to off, so a build that says nothing gets exactly the types it got before. It is opt-in rather than opt-out because it changes the type of a public struct field: application code that copies a name into a writable CHAR * stops compiling, which is a reasonable thing to ask of a minor release and not of a patch one. Issue #780 tracks making it the default in 6.6. Two things the option reaches that its own call sites do not. TX_CHAR_TO_UCHAR_POINTER_CONVERT has exactly two users, both of them reading an object name in _tx_trace_object_register, and every form of that macro but the MISRA one casts the qualifier away without saying so; the conversion is now const in and const out, so nothing launders const to make the build pass. The FreeRTOS adapter holds the name pcTaskGetName retrieves in a TX_NAME_CONST pointer so that it tracks whichever declaration tx_thread_info_get has, and keeps its writable return type through an explicit MISRA C:2012 Rule 11.8 cast, because that signature is part of the FreeRTOS API. Default build: all seven host configurations and all five SMP configurations build with zero warnings and pass -- 113/113 on five host configurations, 100/100 on the two MISRA builds, 118/118 on SMP, 3/3 FreeRTOS. With TX_ENABLE_CONST_NAMES set, the host default and both MISRA configurations, the SMP trace configuration and the FreeRTOS adapter build with zero warnings and pass. Co-authored-by: Tilen Majerle <tilen@majerle.eu> Assisted-by: Codex (gpt-6-astra) <noreply@openai.com> Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
) ThreadX still declares 6.5.1.202602 with hotfix 'a', which is the previous release rather than the one being cut. prepare_release.sh updated the five version constants in tx_api.h and the version string in 211 port headers across ports, ports_smp, ports_arch and ports_module. The hotfix letter is cleared, since the target has none. Nothing else changed: every line in the port commit carries a version, and no file outside an inc/tx_port.h was touched. The port consistency checks passed before the branch was cut, which is what the script gates on. Host regression 113/113 with zero warnings, and the AI disclosure check passes. Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
Two commits on dev are mechanical and repository-wide, and neither is in the list. The disclosure normalisation is the second half of a pass whose first half, #740, is already listed, so blame behaves differently either side of it. The version pass is the same shape as the two release preparations above it. Both are now listed with their file counts. The const object names change is deliberately absent: it alters behaviour, and size is not the criterion. All thirteen revisions in the file resolve, and git accepts it as an ignore file. Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
Test Results FreeRTOS3 tests 3 ✅ 0s ⏱️ Results for commit 729959f. ♻️ This comment has been updated with latest results. |
Test Results RISC-V1 158 tests 1 158 ✅ 6m 3s ⏱️ Results for commit 729959f. ♻️ This comment has been updated with latest results. |
The port stage matched a version only where "Version" was followed immediately by four dotted numbers. A port writing three numbers, or a stray letter before the first, was passed over and kept its old release, and the pass reported success either way. The RISC-V32/IAR port is one of them. It reads "Version G6.5.0.202601", and the letter in front of the number has kept it out of every pass since, so it has advertised 6.5.0.202601 across two releases it was not part of. Its string now reads the current release. The pattern accepts three or four numbers and tolerates a leading letter. A check follows it: any port header that names a release other than the one being prepared is listed and the pass stops, so a port the substitution cannot reach fails the release instead of shipping a version that misreports itself. Verified: every ThreadX port header now advertises 6.5.2.202603. Against a FileX tree, a port the substitution cannot reach makes the pass name the file and exit non-zero, and correcting that string lets the pass complete. Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
ThreadX had no board support for Erbium, the OpenHW Foundation CORE-ET RISC-V platform that Zephyr and NuttX support. Its hart implements only part of the F extension, needs a 4 KiB aligned mtvec and uses the original Shakti UART. The example runs the standard demo on hart 0 in machine mode. It builds the library and the demo for rv64imc_zicsr_zifencei with the soft-float lp64 ABI and links without libc or libgcc, so the pinned riscv64-unknown-elf toolchain can build it. It programs the machine timer, a polling UART console and the PLIC, writing the source priorities and threshold that silicon hardwires because the simulator resets them to 0. No shared file changes. The demo built without warnings and ran on erbium_emu from et-platform 836a4ab, with all eight threads reporting and five thread 0 wakeups in 100M cycles, which matches the 2 MHz tick. Separate test images took five PLIC UART interrupts through the ThreadX ISR path and halted with mcause 2 on an illegal instruction. check_ai_disclosure.sh and check_ports.sh passed. Assisted-by: Claude Code (Opus 5.5) <noreply@anthropic.com>
Long polling UART writes can mask timer interrupts across several tick periods. PLIC enable-word updates can overwrite a change made by an interrupt handler. The board example had no simulator tests for either case. Added two simulator images and a runner. One compares hardware timer progress with ThreadX ticks after a long print. The other toggles one PLIC source in timer context while a thread changes another. Both tests fail on the PR code and pass with the corresponding local fixes. GCC 15.2.0 and Ninja built both images. erbium_emu reproduced both failures; control runs passed both tests. Port consistency checks passed. No silicon run. Assisted-by: Codex (GPT-6-Sol) <noreply@openai.com>
Polling UART output held machine interrupts off for an entire string, which could lose ThreadX ticks. PLIC enable-word changes could overwrite an update made by an interrupt handler. The UART now polls with interrupts enabled and protects only the final status check and byte write. PLIC enable and disable changes are protected across their read-modify-write. The README explains the new simulator tests. Both simulator tests failed on the original code and passed with these fixes. The 100-million-cycle demo run reported all eight threads and five thread 0 wakeups. AI disclosure and port consistency checks passed. No silicon run. Assisted-by: Codex (GPT-6-Sol) <noreply@openai.com>
The install step is capped at ten minutes for every consumer. That suits a consumer that only installs packages, which is all of them bar one: measured install steps run 0.2 to 0.5 minutes, and NetX Duo's secure interoperability job, which builds OpenSSL and wolfSSL from source, takes 2.1 to 2.8. GUIX does not fit. Its regression suite is staged across the install, build and test steps to keep each inside its limit, so the install step also builds a configuration, and on a slow mirror the packages and the ThreadX checkout leave too little of the ten minutes for it. Three runs on dev were lost that way. The cap is now an input defaulting to ten, so a consumer that needs longer asks for it and every other consumer keeps the timeout it has today. Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
The build step is capped at fifteen minutes for every consumer, on the same shape of assumption the install step carried: that a consumer builds once. Measured build steps are 0.5 minutes for LevelX, 1.1 for FileX and 6.4 for USBX, so fifteen is right for them. GUIX builds five configurations in that step, because its suite is staged across the three steps, and reaches 11.8 minutes. That is 79 per cent of the cap, and it grows with every regression test added -- six advisories landed tests this week. The cap is now an input defaulting to fifteen, matching install_timeout_minutes above. No consumer changes unless it asks to. Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.