mirror of
https://github.com/eclipse-threadx/threadx.git
synced 2026-10-06 06:59:08 +08:00
7212b366aeb0e02eae4cc1cdc6f9787b19bfd79e
516
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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) |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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>
|
||
|
|
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> |
||
|
|
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>
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
83a3e62c28 |
Added a Module Manager host test harness and released a module thread's kernel stack only when it has one (#738)
* Released a module thread's kernel stack only when it has one, so deleting a thread without a manager-allocated stack no longer asks for that memory back The thread delete dispatcher decided whether to release a kernel stack from the calling module's property flags. A user-mode module can be given the address of a thread that carries no kernel stack the manager allocated, and deleting it passed that thread's null stack pointer to _txm_module_manager_object_deallocate(). The pointer is now tested instead of the module's properties. Both thread create paths clear the whole control block before filling it in, so a thread with no manager-allocated kernel stack holds TX_NULL in the field, which makes the test exact rather than an inference from how the module was loaded. Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com> * Added a Module Manager host test harness and covered the user-mode thread kernel stack lifetime The Module Manager is not in the ThreadX library the test tree links, and its control blocks come from a module port rather than a base port, so nothing in the suite could reach it. The thread_transition directory already describes this technique as the one "the module manager tests in this tree use", but no such tests existed. This adds the directory that comment refers to: a port shim that replaces the three interrupt primitives with host equivalents that count, and a CMake target that compiles the manager sources under test directly against one module port's headers. The dispatch layer is one header of static functions and an unoptimised build emits all of them, so a test that includes it to reach one dispatcher would pull in references to every service the manager can dispatch. The header guards each dispatcher with its own TXM_<SERVICE>_CALL_NOT_USED macro, so the build reads that list out of the header and defines every guard except the ones the test needs. Reading it rather than writing it down means a service added later is excluded without anyone having to remember. The first test asserts the invariant a user-mode module thread has to hold: one logical thread costs the object pool two allocations, the control block the module asks for and the kernel stack the manager takes on its behalf, and deleting the thread must return the pool's available bytes and the module's allocation-list count to exactly what they were. It asserts an equality rather than a bound, because a bound would pass while one of the two was left behind on every cycle, and then runs enough cycles that a leak of one stack per cycle exhausts the pool several times over. Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com> |
||
|
|
dde43b8ab2 |
Replaced the stale system stack switch pseudo-code in the ARMv7-A ports with comments that describe what the code actually does (#735)
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 context save, vectored context save and system return routines in the
ARMv7-A ports carried pseudo-code comments claiming that they saved the
thread stack pointer and then switched to _tx_thread_system_stack_ptr.
Neither of those things happens, and none of these ports references that
variable outside an unused IMPORT in their example builds.
On ARMv7-A each processor mode has its own banked stack pointer. The IRQ
handler branches to _tx_thread_context_save while still in IRQ mode, so the
core's banked IRQ stack already serves as the system stack, and the thread
stack pointer is stored in the control block by _tx_thread_context_restore,
and only when the interrupt results in preemption. The scheduler runs on the
banked SVC mode stack that the startup code sets up. There is nothing for a
software stack switch to do.
The comments were therefore misleading rather than merely redundant, and had
led at least one user to try to restore the code they described. They are now
replaced by a description of the actual mechanism.
The AArch64 SMP ports keep their comments unchanged, because ARMv8-A does not
bank a stack pointer per processor mode and those ports do reload
_tx_thread_system_stack_ptr[core] explicitly.
This is a comment-only change. Every changed line is a comment, and all
twenty-five GNU variants still assemble cleanly for their target core.
The fourteen files under ports/cortex_a{5,7,8,9,12,15,17} were regenerated
from ports_arch/ARMv7-A/threadx/common/src/tx_thread_system_return.S with
ports_arch/ARMv7-A/update.sh. The ARMv7-A SMP ports have no generator, so
those files were edited directly.
Fixes #734
Assisted-by: Copilot (Opus 5) <noreply@github.com>
|
||
|
|
c0aa4dbe29 |
Initialized the suspend status before the non-interruptable suspend in tx_thread_sleep, so a sleep no longer returns a stale error left over from an earlier timed-out suspension (#725)
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
When TX_NOT_INTERRUPTABLE is defined, _tx_thread_sleep did not set tx_thread_suspend_status to TX_SUCCESS before calling _tx_thread_system_ni_suspend. The interruptable path always did. Nothing else writes that field on behalf of a sleeping thread: _tx_thread_timeout resumes a TX_SLEEP thread directly and there is no suspend cleanup routine for sleep. The value therefore survived from whatever suspension the thread performed last, and tx_thread_sleep returned it. A tx_semaphore_get that timed out with TX_NO_INSTANCE would be followed by a tx_thread_sleep that also returned TX_NO_INSTANCE after sleeping correctly. The same change is applied to the SMP copy, which is identical. Assisted-by: Copilot (Opus 5) <noreply@github.com> |
||
|
|
70741198e9 |
Refused thread delete and reset while an exit transition is in progress (#724)
_tx_thread_shell_entry and _tx_thread_terminate both publish a thread's
terminal state -- TX_COMPLETED or TX_TERMINATED -- and then call that
thread's exit notification callback, before the thread has been detached
from the ready list and before either service has finished with the pointer
it holds to the control block. That terminal state is exactly the state
_tx_thread_delete and _tx_thread_reset accept as authorization to
invalidate or rebuild the control block, and neither service tested whether
the transition producing it had finished.
A callback could therefore delete the thread it was called for -- and then
lawfully recreate it over the same memory, since delete exists to permit
that -- while the scheduler was still linked to the old incarnation.
tx_thread_create zeroes the whole control block and can auto-start the new
one, so the old priority list is left heading at a block whose own priority
field names a different list, with the old priority's map bit set behind
nothing. Alternatively a callback could reset a terminated thread, which
moves it out of the terminal state, and then resume it: the interrupted-
suspension logic in _tx_thread_system_resume refuses to void a suspension
only while the state is still terminal, so with the reset allowed first the
resume clears the suspending flag and restores TX_READY, the outer service
then finds the flag clear and skips the removal, and tx_thread_terminate
returns TX_SUCCESS for a thread that is runnable again.
Interrupt masking does not close the window, because the kernel restores
the prior posture before invoking the callback deliberately. On SMP
TX_RESTORE also releases the global protection, so the target can be
executing on another core while its callback runs -- and a reset there
memsets the stack a live core is running on. On the Linux, Win32 and Win64
host simulation ports the consequence is more immediate than corruption:
TX_THREAD_DELETE_PORT_COMPLETION cancels and joins the host thread backing
the deleted thread, so a callback-side delete of the completing thread
destroys the host thread the callback is running on.
The fix marks the transition and has the two services refuse a marked
target, returning the errors they already document, TX_DELETE_ERROR and
TX_NOT_DONE. The refusal is transient and the same call succeeds once the
transition has completed, so no documented lifecycle is lost; and it is in
the core services rather than the _txe_ wrappers, so disabling error
checking cannot disable it. Refusing the reset is also what closes the
resume path, without touching _tx_thread_system_resume: its existing
terminal-state test is sufficient once nothing can turn the terminal state
into TX_SUSPENDED from inside the window.
tx_thread_suspending is the marker, rather than a new control-block field.
It already means "a suspension is in progress" and is already true across
the callback in the two interruptable paths, so no field is added, the
public structure is unchanged, and sizeof(TX_THREAD) is unchanged --
which matters, because the Module Manager's object handling depends on the
sizes of the control blocks. Widening its lifetime was checked against
every reader rather than assumed. There are four: two in
_tx_thread_system_suspend and two in _tx_thread_system_resume. In every
window this change widens, the state is TX_COMPLETED or TX_TERMINATED, and
both resume readers already refuse to void a suspension for exactly those
two states, so their behaviour is unchanged; and no suspension routine is
called on the target in those windows, so the suspend readers never see
them. No suspension-initiating service can set the marker again inside a
window either: every one of them acts on a thread that is ready or
suspended.
Three sites needed changing beyond the two refusals, and the shape of each
was decided by where the marker can safely be cleared:
- The non-ready branch of _tx_thread_terminate cleared the marker before
the terminated extension and the callback, which is what left them free
to act on a control block the service still had mutex-release
processing to do against. The clear moves to the common tail, after the
last dereference of the target, and becomes the single clear site for
the whole service. In the interruptable ready branch the flag is
already false there, because _tx_thread_system_suspend cleared it when
it detached the thread, so the tail store is a second store of a value
the flag already holds -- cheaper than testing for it, and it keeps one
clear site.
- Under TX_NOT_INTERRUPTABLE neither path set the marker at all, because
that configuration does not use the interruptable suspension path that
sets it. Both now set it before the callback. Interrupts being disabled
there does not help: the callback is reached by a direct call.
- In the TX_NOT_INTERRUPTABLE completion path the marker is cleared
before _tx_thread_system_ni_suspend rather than after it. That call
returns to the scheduler for a thread that is the current thread, which
a completing thread is, and does not come back; clearing afterwards
would leave a normally completed thread marked for ever and therefore
permanently undeletable. Nothing is lost by clearing early there,
because everything from that point to the detachment runs with
interrupts disabled and calls no application code.
The change is the same change twice. All four files are byte-for-byte
identical between common and common_smp at this commit and stay so after
it, so common_smp was written by copying rather than by repeating the
edits. tx_thread_system_suspend.c and tx_thread_system_resume.c, which do
differ between the kernels, are deliberately untouched.
Tests. The in-tree regression test goes to both trees and is byte-for-byte
identical between them. It drives seven scenarios: terminating a ready
non-current target with two peers ready at the same priority, with the
callback attempting the delete and recreating the block if it succeeded;
the same with the callback attempting the reset and then the resume;
terminating a target suspended on a semaphore while owning a mutex, which
is the non-ready branch; natural completion alone at its priority,
including the safe post-completion reset, terminate, delete and recreate at
another priority; self termination; a benign callback, whose notification
count and ordering are unchanged; and the state and boundary cases, where
the new refusal must not fire.
It measures rather than describes. The callback records the published
state, the marker, and the status of every lifecycle service it can reach,
calling the core service as well as the wrapper wherever a refusal is
expected. A snapshot taken under interrupt lockout -- which is the global
SMP protection on an SMP port -- checks that every ready list agrees with
the control blocks it heads, that the priority map agrees with the lists,
and that each execute pointer is a member of the list its own priority
field names. Every walk is bounded, so a corrupted ring costs an assertion
and not a hang, and no test in the suite can hang. Expectations are counted
inside a scenario and gated between scenarios, so a failing kernel reports
how much it failed by without being driven further into its own
corruption.
The consequences are demonstrated from the terminator's context rather than
the completing thread's, which is what makes the pre-fix behaviour an
assertion instead of a wedged simulator. Compiled against the unfixed
sources the test fails 9 of the 24 expectations it reaches in the
uniprocessor tree and 10 of 24 in the SMP tree, and the failures are the
finding: the callback-side delete succeeds, the recreate succeeds, the
consistency snapshot disagrees, the target is neither terminal nor detached
when the service returns, and a peer has left the ready ring the recreated
block hijacked.
The SMP tree gets a second test for the case that needs concurrency. The
victim is excluded to core 1 and spins there without relinquishing while
the controller, excluded to core 0, terminates it, so the callback runs on
one core while the target executes on another. The callback-side reset and
delete must both be refused, and a sentinel written into the unused low end
of the victim's stack must survive -- a reset would have memset the whole
stack before rebuilding the frame. Every wait is bounded, and if the remote
precondition cannot be established the test says so and drops only the
assertions that depend on it rather than reporting a pass it did not earn;
measured over twenty consecutive runs it established the precondition every
time.
TX_NOT_INTERRUPTABLE and TX_DISABLE_ERROR_CHECKING are not among the five
build configurations either tree compiles, and each tree builds the whole
library once per configuration, so neither can be reached from inside the
suites. The lines this change adds under TX_NOT_INTERRUPTABLE are therefore
in no configuration the trees build, and they are where the permanent-
undeletability failure mode lives, so they get their own harness rather
than a compile check: the four sources plus the two error wrappers are
compiled directly into a test executable, once per combination, with
recorders standing behind the scheduler services they call. That is what
makes the marker's value at the moment of detachment directly observable.
It runs 66 expectations under TX_NOT_INTERRUPTABLE, 66 under that with
error checking disabled, 45 under that with notification disabled, and 63
under error checking disabled alone; against the unfixed sources those fail
25, 22, 6 and 20 respectively. The harness lives in the uniprocessor tree
only, because the four sources are identical between the kernels and the
shim replaces the very primitive the SMP port differs in, so a second copy
would compile the same text under the same macros. It is deliberately left
out of the coverage instrumentation, since the same source under different
feature macros has a different line set and merging those would confuse the
union rather than add to it.
Results. Both suites pass in all five configurations with GCC 14: 103 of
103 in the uniprocessor tree, up from 98, and 116 of 116 in the SMP tree,
up from 114. Merged line coverage is 100% in the uniprocessor tree and
5172 of 5183 in the SMP tree, whose eleven uncovered lines are the same
eleven that were uncovered before this change and are in tx_byte_pool_search
and tx_thread_smp_utilities; all four changed files are at 100% line
coverage in both trees, and SMP branch coverage rises from 2819 of 3548 to
2831 of 3556. Cross-compiled with arm-none-eabi-gcc at -Wall -Wextra for
Cortex-M4 against common and for Cortex-A7 SMP against common_smp, all four
files produce no diagnostics at all and an identical warning set to before
the change, under -std=gnu99 and -std=c99 alike -- unlike the module ports,
-std=c99 does not fail on these base ports, and even -Wconversion is clean.
No MISRA deviation is required: explicit comparisons to TX_TRUE, existing
ThreadX types, single-entry and single-exit control flow, no goto, and two
added constant-time tests that change no real-time complexity.
Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
|
||
|
|
9ee198a2d6 |
Made the stack analyze binary search require several consecutive fill words, so unwritten holes in a used stack no longer cause the highest stack pointer to be under-reported (#720)
The binary search in _tx_thread_stack_analyze() accepted a probe location as unused as soon as a single word still held TX_STACK_FILL. A word inside an otherwise used region that simply was never written - the padding of a partially initialized local array, for example - therefore made the search move away from the real boundary and report far less stack usage than the thread had actually consumed, which in turn kept the stack guard from firing. The probe now walks down from the candidate location and requires TX_THREAD_STACK_ANALYZE_FILL_WORDS consecutive fill words before it treats the location as unused, stopping early at the lowest location already known to hold the fill pattern. The new macro defaults to eight words and can be overridden in tx_port.h; setting it to one restores the previous behavior. Holes shorter than the configured run no longer mislead the search, and the result is unchanged for stacks that contain no such holes. Assisted-by: Copilot (Opus 5) <noreply@github.com> |
||
|
|
d56f96f37f |
Refreshed the reason gcc_check leaves RISC-V out (#722)
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 header explained the exclusion by saying RISC-V "is not regressing" and that adding it would widen the toolchain download. The second half is still true; the first read as though nothing in CI exercised the family at all, which stopped being the case with #717. RISC-V is now the best-covered of the four excluded families rather than the least: regression_test.yml builds both ports and runs 955 tests on them under QEMU -- 475 on RV32 and 480 on RV64, across five build configurations each -- which is more than a compile-and-link check could establish. That is a stronger argument for leaving it out of this workflow than the original, so the sentence now makes it. The download figure is kept and quantified: the two bare-metal toolchains this workflow would have to fetch are about 500 MB apiece. Comment only; no behaviour change. scripts/check_gcc.sh names the same four families but states the exclusion without giving a reason for it, so it needs no matching edit. Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com> |