summaryrefslogtreecommitdiff
AgeCommit message (Collapse)Author
4 daysBump EnricoMi/publish-unit-test-result-actiondependabot/github_actions/dev/actions-minor-and-patch-5fda5cb54adependabot[bot]
Bumps the actions-minor-and-patch group with 1 update: [EnricoMi/publish-unit-test-result-action](https://github.com/enricomi/publish-unit-test-result-action). Updates `EnricoMi/publish-unit-test-result-action` from 2.24.0 to 2.25.0 - [Release notes](https://github.com/enricomi/publish-unit-test-result-action/releases) - [Commits](https://github.com/enricomi/publish-unit-test-result-action/compare/d0a4676d0e0b938bc201470d88276b7c74c712b3...7622c5bf3d112a72ab4fa3951c2aa5723d8ab92d) --- updated-dependencies: - dependency-name: EnricoMi/publish-unit-test-result-action dependency-version: 2.25.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: actions-minor-and-patch ... Signed-off-by: dependabot[bot] <[email protected]>
10 daysAdded the R52 module and S32Z280 examples to the FVP workflow (#804)Frédéric Desbiens
The FVP module manager example and the S32Z280 kernel and module examples are each behind a CMake option that defaults to OFF, and no CI lane turned any of them on. The module isolation tests were therefore never linked, and nothing built the S32Z280 images. Both r52_fvp configurations now enable all four example options. The FVP module images join the CTest suite whenever the model is available; the S32Z280 images are built and never run, since CTest registers only FVP images. The header no longer describes these examples as pending. On the Armv8-R AEM FVP 11.32 with GCC 14.3, the default configuration builds 14 images and passes 8/8, and the feature configuration builds 20 and passes 11/11, with zero warnings. CI still builds without executing until FVP_AEMV8R_URL is set. Assisted-by: Claude Code (Opus 5.5) <[email protected]>
10 daysEnabled FVP execution in the Cortex-R52 workflow (#806)Frédéric Desbiens
Fixes #805 r52_fvp only ran the FVP when a repository variable pointed it at the model, and none was set, so every run built the images and executed nothing. The workflow also expected an unpacked model, but Arm ships the FVP as an installer script. The workflow now downloads the 11.32_19 archive from Arm's public permalink, checks it against a pinned SHA-256, and runs Arm's installer with its EULA accepted. The cache is keyed on the checksum. A missing model now fails the job instead of skipping execution. CONTRIBUTING.md no longer describes the repository variable. The workflow's install, locate, and build-and-run steps, run locally as written, download and verify the archive, install 11.32+19, and pass 6/6 default and 9/9 feature on the FVP. The installed binary is identical to the one used for local validation of the port. Assisted-by: Claude Code (Opus 5.5) <[email protected]>
10 daysRemoved internal milestone labels from the Cortex-R52 port (#803)Frédéric Desbiens
Comments, CMake messages and demo banners in the Cortex-R52 port and its module example referred to development milestones by internal labels. The labels mean nothing to a reader of the port, and three FVP demo images were named after them. Replaced each label with a description of what the code or image does, and renamed demo_m2, demo_m3 and demo_m5 to demo_coop, demo_tick and demo_vfp, with their run targets to match. The banners the demos print change accordingly; the FVP test runner matches only result lines, so no test depends on them. Default, feature and module configurations pass 6/6, 9/9 and 8/8 on the Armv8-R AEM FVP, as on dev, with zero warnings; the S32Z280 images build cleanly. Not run on S32Z280 hardware. Assisted-by: Claude Code (Opus 5.5) <[email protected]>
10 daysAligned the banner comments in the Cortex-R52 ports (#785)Frédéric Desbiens
The header and function banner comments in the Cortex-R52 port and its module port do not close on the right border. 990 of 4,228 banner lines sat one to three columns off, and in the module port that was 42 per cent of them. Measured against the ports the convention came from, Cortex-A7 and Cortex-M33 are at zero and Cortex-R5 at 2 per cent, so this is the R52 tree rather than the house style. Lines whose text already fitted are re-padded. Where the text itself ran past the border the trailing words move down to the next line, which pushes the paragraph along until every line fits, so no wording is shortened and no sentence is recut by hand. The bar widths were already correct in every file and are untouched. One line is left over and is a real exception: _txm_module_manager_thread_stack_build is long enough that its name and the release column cannot share a line at this width. Verified: the banner text is word-for-word identical before and after in all 45 files, the port builds at 203 targets with zero warnings, and all eleven changed assembly files assemble. Assisted-by: Claude Code (Opus 5) <[email protected]>
10 daysUpdated artifact and Pages actions to current patch releasesdependabot[bot]
The artifact and Pages workflows pin actions behind their current patch releases. Updated upload-artifact to v7.0.2, download-artifact to v8.0.2 and deploy-pages to v5.0.1, retaining immutable SHA pins. Component sources are unchanged. The ThreadX, SMP, FreeRTOS and RISC-V regression jobs and repository checks passed. Pages deployment was not exercised by the pull request. Assisted-by: Copilot (gpt-6.1-sol) <[email protected]>
10 daysRemoved the Cortex-M asides from the Cortex-R52 module port (#784)Frédéric Desbiens
Four comments in the Cortex-R52 module port explained what the Cortex-M ports do instead. A reader working on this port cannot act on any of it: the behaviour described belongs to a different architecture, and the surrounding comments already say what this port does and why. The remaining text is unchanged. The abort vector still explains the three things it owes the fault handler and what omitting the first costs, the fault handler still explains why its two statements are ordered as they are, and the preamble still says that every GNU module port writes the entry-point words the same way. Comment-only. The port builds clean, 203 targets, zero warnings. Assisted-by: Claude Code (Opus 5) <[email protected]>
10 daysFixed MISRA stack alignment on ports with 64-bit pointers (#799)Jefferson Nascimento
Fixes #744 MISRA stack checking converted the stack address through 32-bit ULONG. Preserved ALIGN_TYPE width in both stack-alignment conversions. Added a native-width behavioral regression that fails on the original code. The regression fails on the original code and passes with the fix. tx, smp, riscv and freertos suites are green in CI, along with the Cortex-M/A/R52, gnu and atfe builds. Assisted-by: Codex (gpt-6.1-sol) <[email protected]>
2026-10-06Merge pull request #797 from fdesbiens/fix/ctest-timeout-headroomFrédéric Desbiens
Gave the per-test ctest timeout headroom over the slowest test
2026-10-06Merge pull request #796 from fdesbiens/fix/deploy-gate-on-needs-resultFrédéric Desbiens
Stopped the coverage deploy running after a cancelled test job
2026-10-06Gave the per-test ctest timeout headroom over the slowest testFrédéric Desbiens
The ceiling was 1000 seconds and GUIX's guix_bidi_text_draw_32bpp needs most of it. Measured across six passing runs in October 2026 it took between 831 and 957 seconds, and it timed out four times in the same period. Nothing about the test changed between the passes and the timeouts; runner load decided it, because the suite builds and tests five configurations at once. Each timeout also costs twice the ceiling, since until-pass:2 runs a failing test again, and the failures were indistinguishable from a real hang in the logs. Raised the default to 1500 and made it overridable through CTEST_TIMEOUT, matching how CTEST_PARALLEL_LEVEL and CTEST_REPEAT_FAIL are already handled. A per-test TIMEOUT property on the one slow test was considered and rejected because the ceiling is shared by every suite this script serves and the next slow test should not need a second fix. bash -n parses. The default and the override both resolve as intended. A test that times out twice now occupies 50 minutes of the 60 minute job timeout, so a genuine hang still fails the test rather than the step. This repository's own suites call the template by relative path and so ran the new ceiling: tx, smp, riscv and freertos all green. Components referencing the template at @master keep the old ceiling until this reaches master. Assisted-by: Claude Code (Opus 5) <[email protected]>
2026-10-06Stopped the coverage deploy running after a cancelled test jobFrédéric Desbiens
deploy_code_coverage gated on !failure() && !cancelled(). Both report the status of the workflow, not of the job named in needs, so a run_tests that is cancelled leaves both false and the deploy proceeds. On the GUIX run of 5 October 2026 run_tests was never assigned a runner, sat fifteen minutes and was cancelled; the deploy ran anyway and failed with "No artifacts named github-pages were found for this workflow run". A run that executed no test at all reported itself red for a missing artifact. Gated on needs.run_tests.result == 'success' instead, so the deploy runs only when the job that uploads the artifact actually succeeded. A caller passing skip_test gains the same protection: run_tests is skipped, and the deploy no longer attempts a download that cannot succeed. yaml.safe_load parses the file. Against the four run_tests outcomes seen on that GUIX run, the new condition deploys on success and skips on failure, cancelled and skipped; the old one deployed on three of the four. This repository's own suites call the template by relative path and are green on tx, smp, riscv and freertos -- but not the changed line itself: deploy_code_coverage is push-only, so a pull request skips it before the new condition decides anything. Components referencing the template at @master are unaffected until this reaches master. Assisted-by: Claude Code (Opus 5) <[email protected]>
2026-10-02Merge pull request #783 from eclipse-threadx/devv6.5.2.202603_relFrédéric Desbiens
Merged changes ahead of the v6.5.2.202603 release
2026-10-02Stopped the RISC-V install pulling a desktop media stack (#789)Frédéric Desbiens
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) <[email protected]>
2026-09-30Made the build step timeout a template input (#791)Frédéric Desbiens
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) <[email protected]>
2026-09-30Made the install step timeout a template input (#790)Frédéric Desbiens
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) <[email protected]>
2026-09-30Fixed Erbium interrupt masking and PLIC update racesFrédéric Desbiens
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) <[email protected]>
2026-09-30Added simulator tests for Erbium interrupt regressionsFrédéric Desbiens
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) <[email protected]>
2026-09-30Added an Erbium example to the RISC-V64 GNU portAfonso Oliveira
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) <[email protected]>
2026-09-28Stopped the version pass skipping ports in silence (#786)Frédéric Desbiens
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) <[email protected]>
2026-09-28Added the release and disclosure passes to the blame ignore list (#782)Frédéric Desbiens
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) <[email protected]>
2026-09-28Updated ThreadX version constants and port strings to 6.5.2.202603 (#781)Frédéric Desbiens
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) <[email protected]>
2026-09-28Made ThreadX object names const-qualifiable behind an option (#761)Frédéric Desbiens
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 <[email protected]> Assisted-by: Codex (gpt-6-astra) <[email protected]> Assisted-by: Claude Code (Opus 5) <[email protected]>
2026-09-28Normalized the AI disclosure comment and added a check that keeps it so (#762)Frédéric Desbiens
* 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) <[email protected]> * 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) <[email protected]> * 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) <[email protected]> * 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) <[email protected]> * 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) <[email protected]>
2026-09-28Updated ThreadX contribution guide for current workflows (#776)Frédéric Desbiens
* 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) <[email protected]> * 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) <[email protected]>
2026-09-28Silenced the SMP MISRA shim's implicit conversions (#751)Frédéric Desbiens
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) <[email protected]>
2026-09-28Added a script that counts Assisted-by attributions completely (#775)Frédéric Desbiens
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) <[email protected]>
2026-09-28Linked ThreadX readers to published documentation (#774)Frédéric Desbiens
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) <[email protected]>
2026-09-28Added a blame ignore list for the mechanical header and version passes (#763)Frédéric Desbiens
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) <[email protected]>
2026-09-28Enabled execution profiling for Cortex-R5 (#766)Frédéric Desbiens
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) <[email protected]>
2026-09-28Made the SMP pt test's randomised phase set preemption thresholds (#760)Frédéric Desbiens
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) <[email protected]>
2026-09-28Stopped the SMP Linux port leaking a critical section on unprotect (#759)Frédéric Desbiens
_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) <[email protected]>
2026-09-28Stopped tx_thread_relinquish discarding the rebalance it requests (#758)Frédéric Desbiens
_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) <[email protected]>
2026-09-28Stopped the Linux port losing a mutex wake-up to a suspend signal (#754)Frédéric Desbiens
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) <[email protected]>
2026-09-28Stopped the SMP Linux port losing a mutex wake-up to a suspend signal (#753)Frédéric Desbiens
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) <[email protected]>
2026-09-28Merge commit from forkFrédéric Desbiens
* 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-28Merge commit from forkFrédéric Desbiens
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) <[email protected]>
2026-09-28Merge commit from forkFrédéric Desbiens
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) <[email protected]>
2026-09-28Merge commit from forkFrédéric Desbiens
_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) <[email protected]>
2026-09-28Merge commit from forkFrédéric Desbiens
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) <[email protected]>
2026-09-28Covered the control block ID cleared when module memory is given back (#779)Frédéric Desbiens
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) <[email protected]>
2026-09-28Merge commit from forkFrédéric Desbiens
* 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) <[email protected]> * 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) <[email protected]>
2026-09-28Merge commit from forkFrédéric Desbiens
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) <[email protected]>
2026-09-28Merge commit from forkFrédéric Desbiens
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) <[email protected]>
2026-09-28Stopped the release script adding a Co-authored-by trailer (#777)Frédéric Desbiens
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) <[email protected]>
2026-09-23Fixed RISC-V regression portability failures (#773)Frédéric Desbiens
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) <[email protected]>
2026-09-17Added a ThreadX module manager port for the Cortex-R52 (#639)Frédéric Desbiens
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) <[email protected]>
2026-09-16Fixed the zero trace time stamps in the Linux ports' MISRA builds (#749)Frédéric Desbiens
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) <[email protected]>
2026-09-16Added MISRA build configurations to the ThreadX regression matrix (#747)Frédéric Desbiens
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) <[email protected]>
2026-09-16Decorrelated the wait abort ISR test's interrupt source from the thread it ↵Frédéric Desbiens
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) <[email protected]>