522 Commits
Author SHA1 Message Date
Frédéric Desbiens 93387b0a60 Merge pull request #783 from eclipse-threadx/dev
Merged changes ahead of the v6.5.2.202603 release
v6.5.2.202603_rel
2026-10-02 10:42:44 -04:00
Frédéric Desbiens 28281cd564 Stopped the RISC-V install pulling a desktop media stack (#789)
The RISC-V job installs qemu-system-misc, ninja-build and cmake under a
two-minute per-attempt budget. With recommends enabled that install fetches 67
packages and 87 MB of archives, 388 MB unpacked, because qemu-system-misc
recommends gstreamer, pulseaudio, v4l and a set of codec libraries. Twenty-six
of the 67 are that media stack, and the job runs qemu-system-riscv64 headless.
On a slow mirror the download does not finish inside the budget, and all three
attempts time out.

The install now passes --no-install-recommends. Raising the budget was the
alternative and is not available: TIMEOUT_LONG is already sized so that three
attempts fit inside the ten-minute install step, as its own comment records.

Observed on the run for #783, where all three attempts timed out while a second
RISC-V run at the same minute succeeded, which is the mirror-speed dependence
this removes.

Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
2026-10-02 10:26:18 -04:00
Frédéric Desbiens 729959fed0 Made the build step timeout a template input (#791)
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>
2026-09-30 12:42:59 -04:00
Frédéric Desbiens f09846d75a Made the install step timeout a template input (#790)
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>
2026-09-30 12:00:52 -04:00
Frédéric Desbiens fb4a8761fc Fixed Erbium interrupt masking and PLIC update races
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>
2026-09-30 10:49:55 -04:00
Frédéric Desbiens f19d8c267d Added simulator tests for Erbium interrupt regressions
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>
2026-09-30 10:49:55 -04:00
Afonso Oliveira 7212b366ae Added an Erbium example to the RISC-V64 GNU port
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>
2026-09-30 10:49:55 -04:00
Frédéric Desbiens 0a52df8f87 Stopped the version pass skipping ports in silence (#786)
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>
2026-09-28 19:56:37 -04:00
Frédéric Desbiens 80dfbe7797 Added the release and disclosure passes to the blame ignore list (#782)
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>
2026-09-28 14:50:27 -04:00
Frédéric Desbiens a7ded6a3b9 Updated ThreadX version constants and port strings to 6.5.2.202603 (#781)
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>
2026-09-28 14:36:10 -04:00
Frédéric DesbiensandTilen Majerle cf577c7137 Made ThreadX object names const-qualifiable behind an option (#761)
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>
2026-09-28 14:08:13 -04:00
Frédéric Desbiens 83361990cb Normalized the AI disclosure comment and added a check that keeps it 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>
2026-09-28 14:03:53 -04:00
Frédéric Desbiens a752784c65 Updated ThreadX contribution guide for current workflows (#776)
* 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>
2026-09-28 14:03:25 -04:00
Frédéric Desbiens 9bf9978e95 Silenced the SMP MISRA shim's implicit conversions (#751)
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>
2026-09-28 14:02:49 -04:00
Frédéric Desbiens f65727f585 Added a script that counts Assisted-by attributions completely (#775)
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>
2026-09-28 12:47:25 -04:00
Frédéric Desbiens c805d9d3df Linked ThreadX readers to published documentation (#774)
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>
2026-09-28 12:46:55 -04:00
Frédéric Desbiens 6098ce67d9 Added a blame ignore list for the mechanical header and version passes (#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>
2026-09-28 12:46:17 -04:00
Frédéric Desbiens 990faf670d Enabled execution profiling for Cortex-R5 (#766)
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>
2026-09-28 12:45:09 -04:00
Frédéric Desbiens c3bf5e6db5 Made the SMP pt test's randomised phase set preemption thresholds (#760)
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>
2026-09-28 12:44:18 -04:00
Frédéric Desbiens 2886bdff6f Stopped the SMP Linux port leaking a critical section on unprotect (#759)
_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>
2026-09-28 12:43:42 -04:00
Frédéric Desbiens e3ed79dbab Stopped tx_thread_relinquish discarding the rebalance it requests (#758)
_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>
2026-09-28 12:43:18 -04:00
Frédéric Desbiens b567428f2a Stopped the Linux port losing a mutex wake-up to a suspend signal (#754)
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>
2026-09-28 12:42:48 -04:00
Frédéric Desbiens 47a5259002 Stopped the SMP Linux port losing a mutex wake-up to a suspend signal (#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>
2026-09-28 12:42:20 -04:00
Frédéric Desbiens a9bd72de21 Merge commit from fork
* 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)
2026-09-28 11:31:41 -04:00
Frédéric Desbiens 5f9ebde847 Merge commit from fork
A memory-protected module's queue request reaches the Module Manager's dispatch
layer, which decides how much of the module's buffer the privileged queue copy
may touch and hands that extent to the module port's range check. A queue's
tx_queue_message_size is a count of ULONGs and the copy moves that many words, so
the extent is message_size * sizeof(ULONG) bytes. Front-send passed the word count
itself. Send and receive, either side of it in the same header, have always
multiplied.

So a source buffer ending message_size bytes into memory the module may reach was
accepted, and _txe_queue_front_send then read message_size * sizeof(ULONG) bytes
from it in privileged mode: 3 * message_size bytes past the end of what the module
is allowed to read, which is 48 bytes at ThreadX's default message size limit and
96 at the limit this test tree builds with. The words land in the queue, and a
module that can receive from that queue reads them back, so the over-read is
disclosed rather than merely performed. Where the memory past the region is not
mapped for the requesting module, the privileged read faults instead.

The buffer cannot be aimed: it has to pass the same check to be accepted at all,
so it is anchored to the end of a region the module can already read and the
overrun is the fixed window immediately after it. That bounds what this reaches;
it does not make it the module's business.

The fix is the multiplication the other two services do, in the units the port
check has always expected. It changes nothing for a module loaded without memory
protection, because the check it corrects is inside the dispatcher's memory
protection block, and the tests hold that.

The regression test drives the three queue dispatchers directly, which nothing in
the tree did before, and measures the extent each one validates rather than
sampling either side of it: for a given message size it walks the room left
between the buffer and the end of the module's region and finds the smallest
amount the dispatcher accepts. That number is the extent. It has to be
message_size * sizeof(ULONG) for all three services, in the module's data, in a
shared region registered to it, and -- for the two services that read the buffer
rather than write it -- in the module's read-only image, which is where a module
sending a constant message sends it from. A receive destination in the read-only
image is refused at every extent.

Two things had to be arranged for a dispatcher to be testable on the host at all.
The dispatch table is a header of static functions wrapped one per service in
#ifndef TXM_<SERVICE>_CALL_NOT_USED, and at -O0 a compiler emits them all, so
linking the whole table would mean standing up every kernel entry point it
reaches. Defining the 93 guards the test does not exercise compiles the header
down to the three queue services, which leaves four symbols to stub and exercises
that conditional compilation, which nothing else in the tree does. The extent also
has to reach a check that honours it, so the test takes its module port headers
from a Cortex-M port, whose inline data check is the shape the memory-protected
module ports share. The Cortex-A7 port's data check takes the pointer alone and
translates a single address, so on that port no extent reaches anything and a test
built on it would pass equally with and without this change.

That is also the affected-port claim: the defect is in the portable dispatch layer
and was live on every memory-protected module port that honours the size it is
given, which is all of them except Cortex-A7, where the port's own check discarded
the size before this one's error could matter.

Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
2026-09-28 11:07:09 -04:00
Frédéric Desbiens ee4c2fb6c7 Merge commit from fork
A memory-protected module's object lookup arrives at the Module Manager's dispatch
layer, which decides how much of the module's name buffer the privileged comparison
may touch. It checked one byte.

_txm_module_manager_object_name_compare reads a character from each name at the top
of every iteration and tests the remaining search length at the bottom, so a declared
length N permits reads at offsets 0 through N: N+1 bytes, the extra one being the
terminator a length excludes. The extended service receives that length in its extra
parameter array, validates the array correctly, and then never uses the length to
bound the buffer it describes. The deprecated service carries no length at all; its
manager wrapper calls the same implementation with the largest value a UINT can hold,
so it removes the bound rather than lacking one.

The comparison stops at the first differing character, so a walk past the end of the
module's memory looks as though it needs the bytes there to match a name the module
chose. It does not. A create service stores the name pointer a module supplies in the
control block rather than copying the string, so a module can register an object whose
name is the very buffer it then searches for. Both sides of the comparison are then
the same address, every character matches by construction, and the walk continues
until it meets a terminator in memory the module does not own and cannot see. That
walk runs in privileged mode with _tx_thread_preempt_disable raised, and none of the
27 memory fault handlers lowers it again, so a fault on it costs more than the
requesting thread.

The fix validates the range the comparison may reach. The extended dispatcher now
checks the name over name_length + 1 bytes, refusing a length whose range cannot be
expressed, and the extra parameter array is checked first because the length comes out
of it. The deprecated dispatcher refuses a memory-protected module outright, because
no range can be derived from a pointer alone; a module without protection is unchanged,
as it is for every other check in this layer.

_txm_module_manager_object_name_compare is left as it is. Reading N+1 bytes for a
declared length of N is the documented contract, and the defect is that the contract
was never checked.

The regression test drives both lookup dispatchers and measures two extents rather than
sampling either side of a boundary, because the finding is the relationship between
them. It anchors the name buffer to the end of one of the module's regions and walks it
backwards to find the smallest room the dispatcher accepts, which is the extent
validated; and it fills memory with a filler character, plants the only terminator at a
chosen depth, and asks for an object named with exactly the bytes up to it, so that the
deepest depth the lookup can be made to return from is the extent read. Against dev's
copy of the two headers the test exits 1 with 408 failed expectations of 1130: a
31-character name with one byte of room is accepted and read 31 bytes past the region,
and the aliased walk reaches every depth offered. With the fix it exits 0 with 1490
expectations and none failed.

Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
2026-09-28 10:58:58 -04:00
Frédéric Desbiens a967d005f6 Merge commit from fork
_txm_module_manager_tx_block_pool_create_dispatch proved that two
ALIGN_TYPE words of the extra-parameter array a module supplies lay
inside the module's data, and then used indices 0 through 3. The two
words it had not proved were read twice each in privileged context:
once by the dispatcher's own buffer check, which takes index 1 as the
pool start and index 2 as the length to range-check it over, and once
when the argument list for _txe_block_pool_create was built. So the
out-of-bounds read was performed by the validation code itself, and
the size the caller's pool start was then validated against was a
word the manager had not proved the module owned.

A module reaches this by calling its kernel dispatcher directly with
an array that ends two words before the boundary of its data or of a
shared region, which the module library's own four-word array never
does. The read window is eight bytes on every module port and there
is nothing in it the module can lengthen.

The extent becomes sizeof(ALIGN_TYPE[4]), which is what the two
siblings of the same shape have always had: queue create validates
four words for four, and byte-pool create three for three. Both
checks on this path are data-only, with no fallback to the portable
code-region check, so the four Cortex-A35 and Cortex-A35 SMP module
port combinations whose data check is the constant (TX_SUCCESS) refuse
these create requests from a memory-protected module before and after
this change alike.

The regression test drives all three create dispatchers and measures
the extent each one validates rather than sampling either side of it:
it anchors the array at the end of each region a module owns and walks
it backwards until the dispatcher accepts, and the smallest room
accepted is the extent. It measures the highest index each dispatcher
uses through what the stubbed service records, so the invariant the
three are checked against is measured on both sides. It also measures
what a module can learn from the answers, since the buffer check is a
comparison against a threshold the module chooses and the dispatcher's
refusal is distinguishable from every status these services return.
Compiled against the previous dispatch header the test reports the
extent as two words and the service reached carrying a word planted
past the end of the region; against this one it reports four.

Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
2026-09-28 10:52:12 -04:00
Frédéric Desbiens 35d4137126 Merge commit from fork
A module calls txm_module_object_allocate and chooses object_size. The manager
adds sizeof(TXM_MODULE_ALLOCATED_OBJECT) to it and asked the object pool for the
result without checking the addition, so a size near the top of a ULONG wrapped:
object_size 0xFFFFFFF8 asked for eight bytes, the pool served them, and the
manager then wrote its sixteen-byte header -- owner, list links and size -- into
that eight-byte block and linked it into the module's allocation list. Eight
bytes of the next block's contents or header are gone by the time the call
returns, and the shared object pool that every module allocates from is the thing
that was corrupted.

The addition is now made with the repository's own overflow-checked helper, which
was already used on both load paths and simply never reached this one, and it is
made before the protection mutex is taken so a refused request leaves the pool,
the allocation list and its count exactly as they were. Sizes that survive the
addition need no further bound here: _txe_byte_allocate refuses a request larger
than the pool, so the alignment round-up in _tx_byte_allocate is never reached
with a value that could wrap in its turn.

Regression coverage runs on the host by modelling the byte pool behind the
manager. The model applies the two bounds _txe_byte_allocate applies and hands
out a block of precisely the requested length with a guard band immediately
after it, so an undersized allocation is caught as the out-of-bounds write it is
rather than inferred from arithmetic. Thirty-two of its expectations fail against
the previous implementation, twenty reporting state a refused request must not
have touched and two reporting the eight and twelve bytes written past the end of
the block. It reaches 100% line, branch and call coverage of the changed
function; the lines it leaves uncovered are its own failure reporting, plus the
modelled pool's zero-size refusal, which only an unfixed build reaches.

Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
2026-09-28 10:37:13 -04:00
Frédéric Desbiens ff4be30656 Covered the control block ID cleared when module memory is given back (#779)
The control block ID that _txm_module_manager_object_deallocate clears on its way
out is read by the size-based object check, which is the one kept for dispatchers
outside this repository. Nothing exercised that path. The expectations covering
the clearing all went through object authentication, which consults the kernel's
created list and would refuse a recycled address whatever its ID said, so they
would have passed with the clearing removed.

A section drives the size-based check directly. A module plants the value of an
ID into memory it owns, gives the allocation back, and is handed the same address
again; the check accepts the planted ID before the deallocation and refuses it
afterwards, which is the difference the clearing makes.

The authentication test holds 125 expectations and passes. Removing the ID
clearing fails it. The full suite passes 107/107 in default_build_coverage with
no warnings.

Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
2026-09-28 10:22:07 -04:00
Frédéric Desbiens 2930618cfe Merge commit from fork
* Refused to give back the memory of a live kernel object

A module allocates the control blocks of its kernel objects from the Module
Manager's object pool and can ask for that memory back by address. The manager
released it whatever was in it, including a control block the kernel was still
using. Deallocation is not deletion: the object stays on the created list for its
type, a thread stays wherever it was on the ready or suspension lists and stays
schedulable, and an active timer stays on the timer list. Nothing on those paths
consults a control block ID, so nothing about the memory having been freed stops
the kernel from using it -- a created list walk reads a name pointer out of it and
follows that pointer, a create or delete of another object of the same type writes
through the created links in it, the scheduler switches the stack pointer to the
word at offset 8 of it and pops a saved processor state, and timer expiration
calls the function pointer in it. Meanwhile the byte pool is free to hand those
same bytes to the next allocation, so what the kernel goes on reading as a control
block becomes whatever the next owner of the memory puts there, through ordinary
create services.

The manager now refuses to release memory that holds an object which is still
created, and returns TX_DELETE_ERROR without touching the allocation, the object
or the memory. Deleting the object first is what makes its memory releasable,
which is the sequence the delete dispatchers already follow and the one module
authors are told to follow.

The question is answered from the kernel's created lists, not from the control
block. A control block ID is not evidence that an object is there: an allocation
that was never created can be carrying the value of an ID, and refusing on that
would strand memory a module is entitled to have back. Deallocation is also given
an address and nothing else, so unlike a typed service it cannot be told which
list to search, and each of the eight lists is searched in turn. The search is not
narrowed by the size of the allocation, because that would be sound only if every
object had been created through a size-checked path, and a module running without
memory protection creates objects through no such path -- a queue at the start of a
thread-sized allocation is a case the tests here cover. Each list is searched in
its own interrupts-disabled window, bounded by the count the kernel keeps beside
it, so the longest window is the length of one type's list and a list whose links
have been damaged cannot make the search run on. The whole search is one pass over
the objects the system has created, paid once per object deallocation.

Storage that is not an object is released exactly as before, which is what keeps
cleanup after a create that failed or was abandoned working, and what makes the
release each delete dispatcher performs after a successful delete go through.

The address the request arrives with is now checked before the manager's private
header in front of it is read, rather than partly after. The size of the
allocation comes from that header, so there is nothing to validate a size against
until the header has been read, and the previous order established only where the
header started: a header that began inside the pool and ended past it had its size
word read from outside the pool. That check has moved out of the dispatcher into
_txm_module_manager_param_check_object_for_deallocation, alongside the other
parameter checks the dispatch table uses and where a test can reach it, and it now
also refuses a size that would carry the end of the allocation past the top of the
address space instead of wrapping it, since a wrapped end compares as though the
allocation were inside the pool.

The 301 expectations in the new test drive all eight object types through allocate,
create, a refused deallocation, delete, and a deallocation that succeeds, and
assert after the refusal that nothing reached the pool, that the allocation is
still on the module's list at the head of it, that the control block still carries
its ID and that the object is still live. They cover storage that was never
created, storage carrying nothing but a plausible ID for each of the eight types,
an object at the start of an oversized allocation, every aligned interior offset
of a live object with that object's own ID planted at it, an application-owned
object outside the pool, another module's allocations both live and raw, releasing
the head of a list of several and releasing the same address twice, a type that has
no created list, the bounds on the search against a list longer than its count and
against a count larger than its list for every type, the boundary addresses at both
ends of the pool, a crafted size, and an object pool that was never created.
Removing the refusal fails 43 of them; removing the size wrap guard fails one.

Line and branch coverage of the three new functions and of the changed
_txm_module_manager_object_deallocate is 100%, with one exception that is test
scaffolding rather than product code: the host shim's stand-in for TX_RESTORE has
an underflow guard, and the test asserts that branch is never taken. All 99 tests
pass in each of the five configurations the tree builds with GCC 14.

Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>

* Removed the object deallocation bounds check superseded by the search

_txm_module_manager_param_check_object_for_deallocation() bounded the private
header in front of a caller's address before the deallocator read it. The
deallocator no longer reads that header: it finds the allocation by searching
the module's own allocation list, which never dereferences the address, so the
bounds check now guards a read that does not happen.

The function, its prototype, its macro and its one call site in the
txm_module_object_deallocate dispatcher are removed, along with the twelve
expectations that covered it and three declarations left unused by their
removal. The search proves more than the check did: the bounds test established
only that the header lay inside the object pool, while the search establishes
that the address is the exact start of one of this module's allocations.

The live object deallocation test holds 289 expectations and passes. Reverting
the live-object guard still fails 51 of them, the same 51 as before the
deletion, so nothing the removed expectations covered was load-bearing. The
full suite passes 108/108 in default_build_coverage, with no warnings.

Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
2026-09-28 10:13:22 -04:00
Frédéric Desbiens 3276b0efd5 Merge commit from fork
A module reaches a kernel object's delete service through the Module Manager's
dispatch layer, which asked one question about the pointer: does the object lie
outside the module's own code and data. That is the policy for using an object,
and using an object a module does not own is supported and intended --
txm_module_object_pointer_get_extended searches the system's created objects by
name and hands back objects the application created and objects other modules
created, so that a module can send to a shared queue, take a shared semaphore or
get a shared mutex. Destroying one of those is not sharing it. A module could
name any object it had a pointer to and have the privileged dispatcher delete it:
a host service lost its queue, waiters were resumed with TX_DELETED, an owned
mutex's priority inheritance was unwound, and an active timer stopped.

Nothing in the manager intended that. The deallocation the delete path performs
afterwards has always required the memory to belong to the calling module, so a
delete of anything else could only ever end in TX_PTR_ERROR -- the ownership
requirement was already there and was enforced one step too late, after the
kernel object had been irreversibly destroyed and its waiters woken. An error
returned at that point describes a cleanup failure and restores nothing.

The eight delete dispatchers now ask the question before the delete rather than
after it. A memory-protected module may delete an object only if the object is
one it allocated from the manager's object pool, at the exact address the manager
returned to it, for an allocation made for that type's control block size -- the
same conditions the create dispatchers already apply, since deletion is the
inverse of creation. Anything else returns TXM_MODULE_INVALID_MEMORY, which is
what every other parameter rejection in the dispatch table returns, so no new
value enters the module ABI and a module cannot use a delete request to tell
"not yours" apart from "not a usable address". The object stays created, nothing
waiting on it is resumed, and no created count moves, because the kernel was
never asked.

The ownership question is deliberately separate from whether an object is there
at all and of the expected type. It establishes nothing about liveness or type,
and it is composed with the checks that do rather than replacing them.

_txm_module_manager_object_deallocate reached the manager's private header by
subtracting from whatever address the caller supplied, and read it before
deciding whether it was a header. All eight delete dispatchers call that function
directly, so an object the module does not own arrived there as a matter of
course rather than exceptionally: for an object the application allocated
statically, or for any address outside the object pool, the words in front of it
were unrelated memory read in privileged mode -- and acted on, since an address
whose preceding words happened to name the calling module was unlinked from that
module's allocation list and handed to the byte pool.

It now finds the allocation by searching the module's own allocation list. The
caller's address is compared and never dereferenced, so an address that names
none of this module's allocations is refused without a privileged read of
anything in front of it, and the search establishes what the header read could
not: that the address is the exact start of an allocation rather than somewhere
inside one. The search is bounded by the count the manager keeps beside the list
and runs with interrupts disabled, so a damaged list cannot make it run on and it
cannot observe the list being changed under it. The fix is in the deallocation
itself rather than at a call site, so it covers all eight delete dispatchers, the
deallocation request a module can make directly, protected and unprotected
modules alike.

txm_module_manager_stop needs no exemption and was not given one. It deletes the
objects a module created by calling the internal _tx_*_delete services directly,
and identifies them with _txm_module_manager_created_object_check rather than
through a module request, so no caller-facing check stands in its way.

The 209 expectations in the new test drive all eight object types through
allocation, an ownership check by the owning module and by another, a check
against a larger and a smaller expected size, and a deallocation that succeeds.
They cover an object the application owns, deliberately preceded by a header
naming the requesting module so that reading it can be seen to have happened;
another module's object, checked and refused from both sides; every aligned
interior offset of an allocation and the addresses of its header, its end, the
pool's ends and one past the pool; a null pointer and an address with no room for
a header in front of it; a request from no module at all; memory that has changed
hands, where a stale pointer names an address that now belongs to another module;
the bounds on the search, against a count smaller than the list and against a
count larger than a list whose links are broken; releasing the head, the middle
and the last of a list of three; and an object pool that was never created. Every
call asserts that the interrupt lock came back balanced, and that the search ran
with interrupts disabled exactly one deep.

Restoring the previous deallocation fails four of them and then segmentation
faults, on the null-pointer case, where the header in front of address zero is
read; removing the ownership check fails 74.

Line and branch coverage of the two new functions and of the changed
_txm_module_manager_object_deallocate is 100%, with two branch outcomes excepted
that are test scaffolding rather than product code: the host shim's stand-ins for
TX_DISABLE and TX_RESTORE carry a maximum-depth test and an underflow guard, and
the real ports' primitives are inline assembly with no branch at all. All 99
tests pass in each of the five configurations the tree builds with GCC 14.

Nothing in the tree compiles the dispatch header, so the eight changed
dispatchers were cross-compiled by hand: the two changed C files and a
translation unit that includes the header build at -Werror with arm-none-eabi-gcc
13.2.1 for every GNU 32-bit module port -- Cortex-A7, M0+, M23, M3, M33, M4 and
M7 -- and produce the same -Wall -Wextra warning counts as before the change, 117
on Cortex-A7 and 116 on each of the others. Cortex-R4 and RXv2 have no GNU module
port, and the two AArch64 module ports have no toolchain available here; the new
arithmetic is a comparison of two addresses of the same type, so a wider
ALIGN_TYPE changes nothing about it.

MISRA: all if bodies are braced, the address comparison goes through ALIGN_TYPE,
no pointer the caller supplied is dereferenced, and no goto appears. No deviation
is required.

Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
2026-09-28 09:23:49 -04:00
Frédéric Desbiens 7495b02374 Merge commit from fork
A memory-protected module names the kernel objects it wants operated on by
address, and the Module Manager decided whether a privileged service could
dereference that address by asking only whether it fell outside the module. The
object pool is outside every module, so that test was satisfied by an address
shifted into the interior of one of the module's own privileged allocations,
which denotes no object at all. The bytes such an address presents as a control
block are bytes the module put there through ordinary create and set services,
so the control block ID at the front of them could be made to read as any type
the module chose, and the _txe_ layer's ID test then agreed. A module could
therefore have the kernel read and write fields of an object that does not
exist, at an address it picked; the reported chain reaches a privileged memset
across an attacker-chosen range that way.

Authentication now answers the question the location test could not: is this the
exact address of a live kernel object of the type this service expects. It is
answered from the kernel's created list for the type, which the create and delete
services maintain, so membership establishes at once that the address is an
object start rather than an address inside an object, that the object is of this
type, and that it has not been deleted. That list is also what makes an object
the application created and shared with a module authenticable at all, since the
manager allocated no such object and has no record of its own to consult, so
sharing keeps working and needs no new registration API.

Nothing a module can influence is used to establish a type. An earlier form of
this fix accepted an address that was the exact start of one of the calling
module's own allocations of the right size and then took the type from the
control block ID, which is cheaper -- a module's allocation list is much shorter
than the system's created list for a type. The tests here refused it: a byte
pool, a mutex and a timer are all the same size on a 32-bit target, so an
allocation created as one of them and presented as another passed both the
address and the size test, leaving the ID as the only thing between the module
and a type confusion. The ID is still checked, after the created list has settled
the question, because it is the test the _txe_ services make and it is compiled
away with them under TX_DISABLE_ERROR_CHECKING. An address a module named is not
read at all until a kernel record says there is an object there.

All 58 object-using dispatchers now pass the object type rather than a control
block size, since a size cannot establish a type. Both scans are bounded by the
counts the kernel and the manager maintain beside their lists, so the cost of one
check is bounded and a list whose links have been damaged cannot make a scan run
on, and both run with interrupts disabled, which is the protection those lists
are maintained under and which adds no blocking point or priority inversion to a
kernel request. The cost is a walk of the created list for the type, paid only by
memory-protected modules; the size-based check is kept for dispatchers outside
this repository and hardened to refuse object pool interiors, which is the part
of the attack it can see without a type.

Two further changes close paths the authentication alone would leave open. Thread
reset now refuses a thread that carries no module instance: such a thread is
authentic, can be found by name and satisfies reset's own state test, and reset
reads the shell entry function out of that instance, so a null one was followed
in privileged mode. Object deallocation now clears the control block ID as it
gives memory back, so an object freed without being deleted first stops being
vouched for at the moment the manager stops owning its memory, rather than
returning to the pool still carrying a valid ID for the next allocation placed
there to present.

The 120 expectations in the new test offer every aligned interior offset of a
legitimate object as a foreign type, with that type's own ID planted at the
offset, and assert that none is accepted; they cover all eight object types
against each other, uncreated allocations, deleted objects, freed addresses that
have been handed out again, objects the application owns and memory that merely
carries a plausible ID, objects another module allocated, and the bounds on both
scans. Reverting the object checks to the permissive policy fails 89 of them;
removing the thread reset guard makes the test die with SIGSEGV inside the reset
path; removing the ID clearing in deallocation fails 2. All 99 tests pass in each
of the five configurations the tree builds with GCC 14.

Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
2026-09-28 09:08:55 -04:00
Frédéric Desbiens d55fe1ef2f Stopped the release script adding a Co-authored-by trailer (#777)
prepare_release.sh committed the version passes with a Co-authored-by trailer naming an
AI. That trailer asserts authorship an AI cannot hold: the human contributor signs the
ECA and is solely responsible for the contribution. It is already in the published
history, on the 6.5.1.202602 and 6.5.1.202602a preparations.

The commits now carry their subject alone. A version pass is mechanical sed output, so
no agent produces it at run time; an agent that runs the script records its own
Assisted-by trailer on that run instead. The script also gains the AI disclosure line it
was missing.

Ran the patched script against a scratch clone targeting 6.5.2.202603. Both commits come
out with an empty trailer block, the version constants and 211 port version strings
update as before, and check_ports.sh passes.

Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
2026-09-28 07:28:23 -04:00
Frédéric Desbiens b37cd4a81a Fixed RISC-V regression portability failures (#773)
cortex_m / Cortex M0 build (push) Canceled after 0s
cortex_m / Cortex M3 build (push) Canceled after 0s
cortex_m / Cortex M4 build (push) Canceled after 0s
cortex_m / Cortex M7 build (push) Canceled after 0s
regression_test / tx (push) Canceled after 0s
regression_test / smp (push) Canceled after 0s
regression_test / freertos (push) Canceled after 0s
regression_test / riscv (push) Canceled after 0s
regression_test / deploy (push) Canceled after 0s
regression_template / run_tests (push) Canceled after 0s
regression_template / deploy_code_coverage (push) Canceled after 0s
RISC-V regression builds ran only at -O0, leaving a timer callback counter that
spins forever at -O2. The trace regression was excluded because it referenced
a port-specific interrupt-save variable, and its adjacent pools could start
misaligned on RV64.

Made the timer counter volatile, gave the trace test aligned pool storage and a
portable saved-interrupt value, and enabled it on RISC-V. Added an -O2 QEMU
configuration to keep the optimized failure covered.

CMake/Ninja/QEMU: RV64 default, optimized and trace suites passed 97/97 each;
RV32 passed 96/96 each. Two ISR event tests passed 30 repeats each at -O2.
The Linux/GCC 14 trace test passed. The reported timing resonance did not recur.

Assisted-by: Codex (gpt-6-sol) <noreply@openai.com>
2026-09-23 16:52:44 -04:00
Frédéric Desbiens 70a5300977 Added a ThreadX module manager port for the Cortex-R52 (#639)
cortex_m / Cortex M0 build (push) Canceled after 0s
cortex_m / Cortex M3 build (push) Canceled after 0s
cortex_m / Cortex M4 build (push) Canceled after 0s
cortex_m / Cortex M7 build (push) Canceled after 0s
gcc_check / gnu (push) Canceled after 0s
r52_fvp / r52 (push) Canceled after 0s
regression_test / tx (push) Canceled after 0s
regression_test / smp (push) Canceled after 0s
regression_test / freertos (push) Canceled after 0s
regression_test / riscv (push) Canceled after 0s
regression_test / deploy (push) Canceled after 0s
regression_template / run_tests (push) Canceled after 0s
regression_template / deploy_code_coverage (push) Canceled after 0s
Added the first GNU ThreadX module port for an Arm R-profile core.

  The port combines the PMSAv8-R MPU model from Cortex-M33 with the AArch32
  privilege and processor-mode handling from Cortex-R4. It provides the complete
  module path: headers, manager sources, scheduler integration, user-mode entry,
  SVC dispatch, data- and prefetch-abort capture, fault notification, relocatable
  module loading, and shared-memory regions.

  Module isolation uses eight MPU regions (8–15) with a 64-byte granule. The
  scheduler replaces those regions on each module switch and manages a separate
  privileged loading window in region 16. Assembly-visible structure offsets and
  region-layout assumptions are checked at build time.

  Added independent module demonstrations for the S32Z280-594EVB and Armv8-R AEM
  FVP. The automated FVP regressions cover:

  - Loading the same position-independent module at different addresses
  - User-mode data and instruction access violations
  - Fault capture and notification for both abort types
  - Shared-region access, alignment, exhaustion, empty-size, and overflow handling
  - Required module-property combinations
  - The GCC CLZ-based priority search

  The port requires user mode and memory protection together, at least 17 EL1 MPU
  regions, and currently validates A32 modules; Thumb module execution remains
  unvalidated.

  Validated with GNU Arm 14.3.1. The FVP suites pass 8/8 in the default
  configuration and 11/11 with hard-float, FIQ, and interrupt nesting enabled.
  The S32Z280 images build cleanly, and the module isolation and relocation paths
  were exercised on S32Z280 silicon during development.

  Matching user documentation is provided by rtos-docs-asciidoc PR #41.

  Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
2026-09-17 15:39:30 -04:00
Frédéric Desbiens ad558a7b1f Fixed the zero trace time stamps in the Linux ports' MISRA builds (#749)
cortex_m / Cortex M0 build (push) Canceled after 0s
cortex_m / Cortex M3 build (push) Canceled after 0s
cortex_m / Cortex M4 build (push) Canceled after 0s
cortex_m / Cortex M7 build (push) Canceled after 0s
gcc_check / gnu (push) Canceled after 0s
regression_test / tx (push) Canceled after 0s
regression_test / smp (push) Canceled after 0s
regression_test / freertos (push) Canceled after 0s
regression_test / riscv (push) Canceled after 0s
regression_test / deploy (push) Canceled after 0s
regression_template / run_tests (push) Canceled after 0s
regression_template / deploy_code_coverage (push) Canceled after 0s
Both Linux ports define TX_TRACE_TIME_SOURCE as _tx_misra_time_stamp_get() when
TX_MISRA_ENABLE is set, and neither implements that function, so both inherit
the generic `return(0);` from tx_misra.c. Every trace event is stamped zero.
The buffer carries no timing, and the kernel's own check for an entry having
been overwritten -- time_stamp against the entry's own stamp, in the block and
byte allocates and in the system suspend and resume -- compares zero with zero,
so it never fires and a service patches whatever now occupies the slot.

Both ports now read in MISRA builds the clock they already read otherwise,
_tx_linux_time_stamp.tv_nsec, which TX_TRACE_PORT_EXTENSION refreshes on every
recorded event in both forms of the insert. The non-SMP port's non-MISRA macro
carried a trailing semicolon, which made it a statement and is why the MISRA
insert -- which takes the time source as a function argument -- could not use
it; that is dropped and the two branches become one definition. Both headers
keep the _tx_misra_time_stamp_get declaration, because tx_misra.c still defines
it and is compiled for these ports.

The MISRA insert evaluates its time source before the callee refreshes the
clock, so each entry carries the reading taken at the previous recorded event.
Stamps are real, distinct and ordered, which is what the overwrite check needs.

The trace entry update test gains an assertion that the buffer holds an entry
the port actually stamped. It fails on dev with ERROR #13 under
misra_trace_build and passes with this change. Suites green: 7/7 ThreadX
configurations, 5/5 SMP.

Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
2026-09-16 14:01:07 -04:00
Frédéric Desbiens e9b5aec65e Added MISRA build configurations to the ThreadX regression matrix (#747)
TX_MISRA_ENABLE routes the kernel's pointer conversions, its memset and its
stack check through the shim in common/src/tx_misra.c. No build configuration
defined it, so that file was never compiled or tested, and the source the MISRA
analysis runs against was not the source the suite exercises. Adding event
tracing alongside it reaches a further region of the shim that neither macro
alone compiles. Both defects found in this area were invisible for the same
reason: #741 broke under TX_MISRA_ENABLE and #745 under both macros together,
and neither combination was built anywhere.

misra_build and misra_trace_build fill that gap. Two things had to give way for
them. TX_POINTER_TO_ALIGN_TYPE_CONVERT exists only when TX_MISRA_ENABLE is
absent, so threadx_test_port.h writes the conversion out rather than taking it
from the API; it is test scaffolding storing a pointer in a word wide enough to
hold one, not kernel source. And thread_transition and module_manager compile
hand-picked common/src sources directly instead of linking the library, which is
what lets them reach feature macros the library-per-configuration model cannot;
under TX_MISRA_ENABLE those sources call into the shim, and pulling the shim in
pulls its own callees after it. Neither directory exists to test the shim, so the
MISRA configurations skip them.

100/100 tests pass in each of the two new configurations, against 105/105 in the
existing five - the five not run are the four thread transition tests and the
module manager test, exactly the two directories skipped. Build is 4 to 5
seconds and the suites 13 and 15 seconds, against 4 seconds and 9 to 17 for the
configurations already there, so the tx job grows by roughly forty seconds.

Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
2026-09-16 09:55:24 -04:00
Frédéric Desbiens 24d1f278c2 Decorrelated the wait abort ISR test's interrupt source from the thread it samples (#743)
threadx_thread_wait_abort_and_isr_test waits for the periodic timer interrupt to
land while the preempt disable flag is set. The same interrupt also puts the
semaphore that wakes the thread being sampled, so the two run in lockstep: the tick
wakes thread 0, thread 0 does a fixed amount of work and suspends, and the next tick
arrives a fixed interval later with thread 0 in the same place every time. That is
the resonance the handler's own comment describes, and perturbing the handler's
duration only shifts the phase rather than breaking the correlation. The window
itself is a few instructions wide, so a sampler locked to the thread's own cycle can
miss it indefinitely, which is what #644 and #649 measured and worked around.

The simulator's timer thread waits on _tx_linux_timer_semaphore with a one-tick
deadline and delivers an interrupt early when the semaphore is posted, which the port
already relies on in _tx_thread_schedule. The test now runs a plain POSIX thread that
posts it, injecting interrupts at moments unrelated to the tick grid and sampling
thread 0 at arbitrary points in its cycle rather than the same one. The injector posts
only when nothing is outstanding, so interrupts can never be queued faster than they
are serviced. It is confined to the Linux simulation port and no port file changes.

The count of windows asked for goes back to ten, on the same reasoning that lowered
it to three: ask for what a run can actually reach. Measured over six build
configurations of a branch carrying this change, every one reaches ten of ten in under
a second, against one to three of three previously with three of the six pinned at the
180 second budget. The budget and the zero window ceiling are untouched, so a run that
somehow still falls behind behaves exactly as it does today.

The SMP copy deliberately does not get the injector, and its count stays at twenty. It
has never been in the slow mode, and injecting interrupts there measurably hurts:
8.9 seconds against 0 to 1 for the same twenty windows, which is the extra interrupt
traffic contending across the simulated cores. Only the tx copy has the problem this
solves, so only the tx copy changes; the budget and ceiling logic stays identical
between them.

Verified on this branch: the tx suite passes 105 of 105 with the test at 0.44 seconds,
and four direct runs reach ten of ten in 0 to 1 seconds. The SMP suite passes 117 of
117 unmodified, with its own test at 0 to 1 seconds over four runs.

Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
2026-09-16 09:18:41 -04:00
Frédéric Desbiens b93d1ee92f Fixed the simulator ports and thread create paths so they compile when TX_MISRA_ENABLE is defined (#742)
cortex_m / Cortex M0 build (push) Canceled after 0s
cortex_m / Cortex M3 build (push) Canceled after 0s
cortex_m / Cortex M4 build (push) Canceled after 0s
cortex_m / Cortex M7 build (push) Canceled after 0s
gcc_check / gnu (push) Canceled after 0s
r52_fvp / r52 (push) Canceled after 0s
regression_test / tx (push) Canceled after 0s
regression_test / smp (push) Canceled after 0s
regression_test / freertos (push) Canceled after 0s
regression_test / riscv (push) Canceled after 0s
regression_test / deploy (push) Canceled after 0s
regression_template / run_tests (push) Canceled after 0s
regression_template / deploy_code_coverage (push) Canceled after 0s
* Fixed the simulator ports so they compile when TX_MISRA_ENABLE is defined

_tx_thread_stack_build() in the four simulator ports converts the fake stack
pointer through TX_POINTER_TO_ALIGN_TYPE_CONVERT and
TX_ALIGN_TYPE_TO_POINTER_CONVERT. tx_api.h defines both macros only in the
non-MISRA branch of its #ifdef TX_MISRA_ENABLE, so with that macro defined the two
names are undeclared and none of the four files compiles.

Reproduced with:

    gcc -m32 -c -DTX_MISRA_ENABLE -I common/inc -I ports/linux/gnu/inc \
        ports/linux/gnu/src/tx_thread_stack_build.c -o /dev/null

which reports both names as implicit declarations and then an int to pointer
assignment. The same command without the define compiles cleanly.

No build configuration under test/tx/cmake or test/smp/cmake defines
TX_MISRA_ENABLE, so CI never compiles these files in that mode. It surfaced on a
branch that carries such a configuration.

The conversions are now written inline, which is what the non-MISRA macros expand
to and what the surrounding port code already does, including the line this
replaced.

The alternative would be the idiom common/src/tx_thread_create.c uses for the same
conversion: an explicit #ifdef selecting the ULONG pair under MISRA. That is not
equivalent here. On __x86_64__ this port defines ULONG as unsigned int and
ALIGN_TYPE as unsigned long long, so a pointer round-tripped through the ULONG pair
loses its top 32 bits. Writing the conversion inline keeps one form that is correct
in both modes and on both widths.

Verified by compiling the Linux port with and without TX_MISRA_ENABLE, both clean.

Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>

* Fixed the same MISRA build break in the SMP and module manager thread create

_tx_thread_create() in common_smp and _txm_module_manager_thread_create() convert the
thread's stack start through TX_POINTER_TO_ALIGN_TYPE_CONVERT and
TX_ALIGN_TYPE_TO_POINTER_CONVERT with no conditional at all. tx_api.h defines both
macros only in the non-MISRA branch, so with TX_MISRA_ENABLE and
TX_ENABLE_STACK_CHECKING both defined neither file compiles. It is the same defect as
the simulator ports in the previous commit, in two more files.

Reproduced with:

    gcc -c -DTX_MISRA_ENABLE -DTX_ENABLE_STACK_CHECKING \
        -I common_smp/inc -I ports_smp/linux/gnu/inc \
        common_smp/src/tx_thread_create.c -o /dev/null

which reports both names as implicit declarations. Both files now compile with and
without TX_MISRA_ENABLE.

The conversions are written inline for the same reason as the ports: the #ifdef idiom
that common/src/tx_thread_create.c uses selects the ULONG pair under MISRA, which
truncates a pointer wherever ALIGN_TYPE is wider than ULONG. That is reported
separately.

Verified: the SMP regression suite passes 117 of 117, and the ThreadX suite still
builds.

Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
2026-09-15 23:19:32 -04:00
Frédéric Desbiens 26e4aa182c Fixed the build of tx_misra.c with MISRA and event trace enabled (#746)
tx_misra.c defines five pointer conversions for the trace component, and their
prototypes sit in tx_trace.h behind TX_SOURCE_CODE. tx_misra.c is the one kernel
source that does not define that macro, so it defines all five with no previous
declaration. Under -Wmissing-declarations -Werror, which the kernel target is
built with, the file does not compile at all, so TX_MISRA_ENABLE and
TX_ENABLE_EVENT_TRACE cannot be enabled together. No build configuration defines
both, which is why this has never surfaced.

The five prototypes move out of the TX_SOURCE_CODE guard into their own block,
next to where tx_api.h already declares _tx_misra_trace_event_insert - the sixth
function of the same region, which escapes the problem for exactly that reason.
Declarations only; no definition moves and no macro changes meaning.

All 185 files in common/src now compile clean under all four combinations of the
two macros with -Wall -Wextra -Wmissing-declarations -Werror, against five errors
in tx_misra.c for the both-enabled case before. Object code is byte identical for
the three combinations that already built: 0 of 185 files differ in each.

Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
2026-09-15 23:08:23 -04:00
Frédéric Desbiens 9e4c57138d Normalized the AI disclosure comment to one fixed line per file (#740)
cortex_m / Cortex M0 build (push) Canceled after 0s
cortex_m / Cortex M3 build (push) Canceled after 0s
cortex_m / Cortex M4 build (push) Canceled after 0s
cortex_m / Cortex M7 build (push) Canceled after 0s
gcc_check / gnu (push) Canceled after 0s
r52_fvp / r52 (push) Canceled after 0s
regression_test / tx (push) Canceled after 0s
regression_test / smp (push) Canceled after 0s
regression_test / freertos (push) Canceled after 0s
regression_test / riscv (push) Canceled after 0s
regression_test / deploy (push) Canceled after 0s
regression_template / run_tests (push) Canceled after 0s
regression_template / deploy_code_coverage (push) Canceled after 0s
The per-edit disclosure named the product and model, so every agent and every
model version appended another line. 74 files carried two to four of them, and
the same five products had accumulated 13 spellings -- Copilot against GitHub
Copilot, Claude Sonnet 4.6 against claude-sonnet-4.6, four spellings of Codex.
Twenty assembly lines carried a doubled comment marker, `; //` or `@ //`.

Every file now carries exactly one line, fixed text naming no product:

    Portions of this file were generated with AI assistance.

It is written with the comment character that file already uses, so the `;`
and `@` assembly files keep theirs and the doubled markers are gone. Precise
attribution stays on the commit, where the Assisted-by trailer is per-change,
dated and attached to the diff it describes. A header line cannot hold that
record honestly, because the code it names gets rewritten and the line stays.
A file-level flag answers whether; the history answers who.

Comment-only. 455 files, 455 insertions and 574 deletions: every removed line
was a disclosure line, every added line is the fixed text, and no file is left
with zero or with more than one. `scripts/check_ports.sh` passes, including the
reproducibility check that would catch a ports_arch master and its generated
copies drifting apart. Recompiled against dev, every file that builds without a
vendor toolchain gives a byte-identical object: 19 of 19 C files under common,
100 of 100 GNU assembly files, and all 16 assemblable files whose comment
marker changed. The 10 remaining marker changes are ac5 and IAR sources where
`;` already started the comment and only the redundant `//` was removed.

Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
2026-09-15 17:17:26 -04:00
Frédéric Desbiens f5e59a1dd3 Completed Windows simulator support and regression coverage (#736)
Completed and stabilized the Win32 and Win64 MSVC simulator ports.

- Replaced high-latency host synchronization with bounded scheduler handoffs and
  critical sections.
- Added a high-resolution timer, tick batching, idle fast-forward, shutdown
  coordination and Win64 extension-pointer support.
- Brought the Windows regression tooling and the new thread-transition tests up
  on CMake, Ninja and the Visual Studio Build Tools.
- Extended the SMP teardown diagnostics and corrected 64-bit trace-test handling.

This supersedes the historical `win64`, `win32-perf`, `windows-sim-ports` and
`windows-sim-ports-completion` branches; no unmerged change from them is missing
here. The original Win64 port landed in #529.

1,610 of 1,610 tests pass, across five configurations each: Win32 515, Win64 515,
Win64 SMP 580. No external dependency was added, and the existing MSVC warnings
in the trace configuration are unchanged. Hardware validation does not apply to
host simulator ports.

Assisted-by: Codex (gpt-5.6-sol) <codex@openai.com>
2026-09-15 16:47:08 -04:00
Frédéric Desbiens 30b3d22adc Restored the random stack fill value cleared during thread creation (#732)
Fixes #723

With `TX_ENABLE_STACK_CHECKING` and `TX_ENABLE_RANDOM_NUMBER_STACK_FILLING` both
enabled, `_tx_thread_create` picked a random byte, stored it in
`tx_thread_stack_fill_value`, filled the stack with it -- and then cleared the
whole control block with `TX_MEMSET`. The stack held the pattern while the
control block claimed zero, so `TX_THREAD_STACK_CHECK` and
`_tx_thread_stack_analyze` compared against the wrong value for the entire life
of the thread.

The value is now computed into a local and written back after the clear. The
clear stays where it is, because the module manager's error checking walks the
created list before the control block may be touched.

Fixed in `common/src/tx_thread_create.c`, `common_smp/src/tx_thread_create.c`
and `txm_module_manager_thread_create.c`, where it additionally left a user-mode
module thread's kernel stack filled with zeros.

`threadx_thread_stack_fill_value_test` creates sixteen unstarted threads and
checks each control block against the pattern in its stack, tolerating a random
zero byte without letting that hide the defect. Added to both suites: `ERROR #3`
on `dev` in `stack_checking_rand_fill_build`, green with the fix. Five tx
configurations at 104 tests, five SMP at 117, and all three sources clean under
`-Wall -Wextra` across every combination of the four stack-filling switches.

Assisted-by: Copilot (Opus 5) <noreply@github.com>
2026-09-15 16:24:58 -04:00
Frédéric Desbiens 201cc04609 Applied R_ARM_RELATIVE relocations at GNU ThreadX module startup (#731)
Fixes #230

`gcc_setup.s` rebases the GOT and copies initialized data into the module data
area, but never applies the relocations the linker records for words inside that
data. A global initialized with a link-time constant -- `char *pBuffer = buffer;`,
a function pointer, a struct member holding a string literal's address -- kept
its link address and pointed outside the loaded module.

`gcc_setup.s` now walks `.rel.dyn` after the data copy and adjusts every
`R_ARM_RELATIVE` entry targeting the module data area, reusing the GOT loop's
base arithmetic and skip rules, and reloading the flash base first because
`crt0_memory_copy` clobbers `r3`.

The table is only emitted with `-pie`, so the example build scripts now pass
`-pie --no-dynamic-linker` and the example linker scripts discard `.dynamic`,
which `-pie` would otherwise place at address zero and inflate the image to
nearly 200 KB. Without `-pie` the table is empty and the loop is a no-op.

Covers Cortex-M3, M4, M7, M33 and M0+. Cortex-M23 ships no module linker or
build script and AArch64 uses a different relocation format; both left for a
follow-up.

Verified on `qemu-system-arm` with a module linked at one address and loaded at
another: char, function, string and struct-member pointers all resolve to the
loaded addresses, and the same module built without `-pie` still runs.

Assisted-by: Copilot (Opus 5) <noreply@github.com>
2026-09-15 16:24:54 -04:00
Frédéric Desbiens 3e5a16c95b Allowed tx_timer_change to be called from tx_application_define (#730)
Fixes #224

`_txe_timer_change` returned `TX_CALLER_ERROR` for any call at or above
`TX_INITIALIZE_IN_PROGRESS`, making `tx_timer_change` the only timer service
that could not be called from `tx_application_define` -- while still being
allowed from an ISR.

The check has no technical basis: `_tx_timer_change` only writes the expiration
fields of a timer that is not on an active list, with interrupts disabled.
Removed it from both the `common` and `common_smp` copies of
`txe_timer_change.c`, with the now-unused includes and the `TX_CALLER_ERROR`
line in the header comment. Relaxing an error check is backward compatible.

`testcontrol.c` in both suites now calls `tx_timer_change` at initialization, so
the timer simple test covers it: `ERROR #30` on `dev`, green with the fix.
116/116 SMP, 103/103 non-SMP.

Assisted-by: Copilot (Opus 5) <noreply@github.com>
2026-09-15 16:24:50 -04:00
Frédéric Desbiens 9b2979e6b0 Replaced the GNU-only dsb/isb 0xF operands with the UAL sy form (#729)
Fixes #551

`_tx_thread_system_return_inline()` in the Cortex-M `tx_port.h` headers spells
its barriers `dsb 0xF` and `isb 0xF`. A bare hexadecimal operand is a GNU
assembler extension, and IAR rejects it with `operand syntax error`, so the
header cannot be included at all. The block is guarded for GCC, armclang and IAR
together, so every IAR user of an affected port hits it -- four independent
reports on Cortex-M33 and M7 with EWARM 9.50 and 9.70.

Both operands become `sy`, the Arm UAL name for exactly what `0xF` encodes. The
generated instruction is unchanged. Applied to the two `ports_arch` masters and
all 32 copies under `ports`, covering M0, M23, M3, M33, M4, M52, M55, M7 and M85
across ac5, ac6, gnu, iar and keil, plus the `scripts/check_ports.sh` probes that
matched the old spelling.

`check_ports.sh` passes, the copy scripts still reproduce every generated port
byte for byte, and `arm-none-eabi-gcc -O2` compiles a caller for every patched
header, emitting `dsb sy` and `isb sy`. Three headers that need toolchain
intrinsics GCC does not ship fail identically on `dev`.

Assisted-by: Copilot (Opus 5) <noreply@github.com>
2026-09-15 16:24:45 -04:00
Frédéric Desbiens d15f28ab9f Masked the SMP remap core maps to silence a false -O2 array bounds error (#728)
Fixes #469

`_tx_thread_smp_remap_solution_find` indexes `_tx_thread_smp_schedule_list` with
the lowest set bit of the core maps it is given. Every caller already masks
those maps with `TX_THREAD_SMP_CORE_MASK`, but the compiler cannot see it, so
when the function is inlined into `_tx_thread_system_suspend` at `-O2` GCC
assumes a bit number as high as 31 and reports an out-of-bounds subscript.
`-Werror` turns that into a build failure.

Masked the three incoming maps at the top of the function, in both the inline
version in `common_smp/inc/tx_thread.h` and the twin in
`tx_thread_smp_utilities.c`. The masks are semantic no-ops, so scheduling is
unchanged, but the range is now visible to the optimizer. The first core queue
entry is also initialized, because the narrowed range lets GCC consider an empty
queue and warn about that instead.

Reproduced on the Cortex-A9 SMP port with arm-none-eabi-gcc 13.2.1, and clean
afterwards across -O2, -O3 and -Os and 2, 4 and 8 core configurations. SMP suite
116/116.

Assisted-by: Copilot (Opus 5) <noreply@github.com>
2026-09-15 16:24:40 -04:00
Frédéric Desbiens 1d4a4aeec8 Guarded _tx_thread_stack_analyze against inverted stack pointers (#727)
Fixes #460

`TX_ULONG_POINTER_DIF` casts the pointer difference to `ULONG`, so when
`tx_thread_stack_highest_ptr` sits below `tx_thread_stack_start` the midpoint
wraps to a huge value, the probe lands outside the stack, and the search never
converges: the caller hangs or faults.

`_tx_thread_stack_analyze` now requires the highest pointer to be strictly above
the start of the stack, and bounds the final scan by it. #464 covered the
`TX_THREAD_STACK_CHECK` path; this covers direct callers too, as
@billlamiework suggested on the issue.

Inconsistent pointers still mean a real overflow or a corrupted control block,
which remains the application's problem. What changes is that ThreadX reports it
through the stack error handler instead of hanging.

New cases for an inverted and an equal pointer pair crash the suite without the
fix and pass with it.

Assisted-by: Copilot (Opus 5) <noreply@github.com>
2026-09-15 16:24:35 -04:00
Frédéric Desbiens 5d235a534c Fixed the garbage _tx_initialize_unused_memory in the GNU Cortex-A ports (#726)
Fixes #435

`LDR x1, =__top_of_ram` already loads the top of RAM, so the `LDR x1, [x1]`
that followed read whatever sat at that address and left
`_tx_initialize_unused_memory` holding garbage.

Dropped that instruction from the 13 non-SMP GNU Cortex-A ports, the ARMv8-A
source they are generated from, and the two Cortex-A35 module examples. The SMP
GNU ports already had the correct form, and the Arm Compiler ports are
unaffected -- their symbol really does need the dereference.

`scripts/check_ports.sh` passes and the `gnu` CI job build-verifies the AArch64
ports. Not run on hardware.

Assisted-by: Copilot (Opus 5) <noreply@github.com>
2026-09-15 16:24:09 -04:00
Frédéric Desbiens cd6a2d9034 Put the module manager test's thread on the kernel's created list, so the stand-in kernel matches the one the manager reads (#739)
cortex_m / Cortex M0 build (push) Canceled after 0s
cortex_m / Cortex M3 build (push) Canceled after 0s
cortex_m / Cortex M4 build (push) Canceled after 0s
cortex_m / Cortex M7 build (push) Canceled after 0s
gcc_check / gnu (push) Canceled after 0s
regression_test / tx (push) Canceled after 0s
regression_test / smp (push) Canceled after 0s
regression_test / freertos (push) Canceled after 0s
regression_test / riscv (push) Canceled after 0s
regression_test / deploy (push) Canceled after 0s
regression_template / run_tests (push) Canceled after 0s
regression_template / deploy_code_coverage (push) Canceled after 0s
The host test built a TX_THREAD, gave it an ID and a module instance, and
left it off the kernel's created list. A created thread lives on that list,
so the thread the test created was not one the kernel would recognise.

The created list head and count are now defined for each object type, the
thread the test creates goes onto the thread list the way thread create puts
it there, and a successful delete takes it off again the way thread delete
does. Only the thread list is populated; the others stay empty because
nothing here creates an object of those types.

No expectation changed.

Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
2026-09-14 17:09:00 -04:00