# Research note: Eclipse ThreadX **Kind:** Read-only source analysis, 2026-10-07, on a sparse shallow clone of eclipse-threadx/threadx at commit `93387b0a6038` (common/, ports/cortex_m4/gnu, ports/cortex_m33/gnu, docs/). Version string "ThreadX Cortex-M4/GNU 6.5.2.202603" (ports/cortex_m4/gnu/inc/tx_port.h:706). Abbreviations: C = common/src, I = common/inc, M4 = ports/cortex_m4/gnu, M33 = ports/cortex_m33/gnu. The clone has no common_modules/, utility/, or tests. Line numbers drift; paths and symbols are the durable reference. Synthesized in `R-001`. --- ## Q1 Scheduler - Priority 0 is highest; TX_MAX_PRIORITIES defaults to 32 and must be a multiple of 32 (M4/inc/tx_port.h:114; I/tx_api.h:2111). State: `_tx_thread_priority_maps[TX_MAX_PRIORITIES/32]` (I/tx_thread.h:333), `_tx_thread_priority_map_active` only above 32 (:341), the per-priority head array `_tx_thread_priority_list[]` (:377), cached `_tx_thread_highest_priority` (:369), `_tx_thread_execute_ptr` versus `_tx_thread_current_ptr` (:299, 306). - Highest ready is the lowest set bit: generic `TX_LOWEST_SET_BIT_CALCULATE` isolates `m & (~m+1)` then shifts (I/tx_thread.h:141-200); M4 overrides with RBIT plus CLZ (M4/inc/tx_port.h:518). - The ready list per priority is a self-linked circular doubly linked list: the first thread becomes the head pointing to itself (C/tx_thread_system_resume.c:189-196), later ones append at the tail (:383-387), the bit is OR'd (:209-210), highest and execute updated (:213-230). Suspend unlinks and clears the bit when empty, then recomputes highest from the maps, NULL execute when none (C/tx_thread_system_suspend.c:198-263, 311-331). - Round robin: per-thread `tx_thread_time_slice` and `new_time_slice` (I/tx_api.h:436-437) mirrored into the global `_tx_timer_time_slice` on switch (M4/src/tx_thread_schedule.S:173-184, 214-221); on expiry `_tx_thread_time_slice` rotates the head to `ready_next` only if priority equals threshold ("Preemption-threshold overrides time-slicing", C/tx_thread_time_slice.c:116-128); relinquish does the same (C/tx_thread_relinquish.c:106-113). - Preemption threshold: `tx_thread_preempt_threshold` is at most the priority (I/tx_api.h:455; C/txe_thread_create.c:252). A newly ready thread displaces the execute thread only if `priority < execute_ptr->tx_thread_preempt_threshold` (resume.c:238). If a thread whose threshold differs from its priority is preempted, its priority bit is recorded in `_tx_thread_preempted_maps` (resume.c:244-259; I/tx_thread.h:353). When the running thread suspends, suspend.c:403-477 finds the lowest set preempted priority and, if `_tx_thread_highest_priority >= that thread's threshold`, re-selects the preempted thread instead of the highest ready one; interrupts are briefly re-enabled there "to keep the interrupt lockout time deterministic" (:412-424). Bits are cleared when the thread leaves the ready list (:226-231) or raises its threshold (C/tx_thread_preemption_change.c:167-212). Net effect: threads in [threshold, priority) cannot preempt, so fewer context switches; threshold 0 is a non-preemptible section. With TX_DISABLE_PREEMPTION_THRESHOLD any threshold collapses to 0 (:88-98). - `_tx_thread_preempt_disable` (I/tx_thread.h:384) is an internal counter; `TX_THREAD_SYSTEM_RETURN_CHECK` ORs it with system_state (:117); the Cortex-M port drops system_state from the check (M4/inc/tx_port.h:499). Non-zero also forbids blocking inside services (C/tx_queue_send.c:282 returns TX_QUEUE_FULL; C/tx_event_flags_get.c:269). - No idle thread: PendSV `__tx_ts_wait` spins with interrupts masked, optional WFI under TX_ENABLE_WFI and TX_LOW_POWER hooks (M4/src/tx_thread_schedule.S:251-296). - `_tx_thread_system_state` (I/tx_thread.h:325): 0xF0F0F0F0 during init (I/tx_initialize.h:48; C/tx_initialize_kernel_enter.c:99, 137), 0 is thread level; Cortex-M ORs IPSR for ISR detection (M4/inc/tx_port.h:478). TX_DISABLE and TX_RESTORE save PRIMASK and `CPSID i`, or BASEPRI with TX_PORT_USE_BASEPRI (M4/inc/tx_port.h:139, 564-600). ## Q2 Context switch and interrupts - Cortex-M ISRs are ordinary handlers; `_tx_thread_context_save` and `_restore` are stubs that only call execution-profile hooks ("Context is already saved - just return", M4/src/tx_thread_context_save.S:66-75; "only needed for legacy applications", tx_thread_context_restore.S:46). - Rescheduling is setting ICSR.PENDSVSET: inline `_tx_thread_system_return_inline` (M4/inc/tx_port.h:578-600, 681); the assembly variant checks IPSR to skip the interrupt toggle when called from an ISR (M4/src/tx_thread_system_return.S:70-80). PendSV is lowest priority (M4/example_build/tx_initialize_low_level.S:127), so ISR-driven preemption is deferred until nesting unwinds. - PendSV body (schedule.S:121-245): save r4-r11, s16-s31 if EXC_RETURN bit 4 is clear, LR; PSP stored at TCB offset 8 (I/tx_api.h:432, assembly-fixed layout :425-442); run_count++ at offset 4 (:213-217); if execute is NULL go wait; `__tx_ts_ready` clears a redundant PendSV (:301-304). - Decision: `_tx_thread_system_preempt_check` (C/tx_thread_system_preempt_check.c:65-118): combined flags 0 and current differs from execute leads to system_return; services inline the same (resume.c:356-368). The tick ISR pends PendSV itself after a time slice if execute differs from current and preempt_disable is 0 (M4/src/tx_timer_interrupt.S:221-233). - Misuse rejection via `TX_THREAD_GET_SYSTEM_STATE()`: TX_WAIT_ERROR for blocking from an ISR, TX_CALLER_ERROR for ISR callers; the timer thread may not block (C/txe_mutex_get.c:111, 122-126, 146); tx_thread_sleep refuses when preempt is disabled (C/tx_thread_sleep.c:134). - `tx_interrupt_control` swaps PRIMASK or BASEPRI and returns the old posture (M4/src/tx_thread_interrupt_control.S:66-73); the trace build substitutes `_tx_trace_interrupt_control` (I/tx_api.h:1338-1341). - Low power and execution profile exist only as hooks (`tx_low_power_enter` and `_exit`, `_tx_execution_*`: schedule.S:33-35, 129-146, 223-228; C/tx_initialize_kernel_enter.c:144; TCB fields I/tx_api.h:578-581); implementations live in upstream utility/, absent here. - Initial frame: a 68-byte fake exception frame, EXC_RETURN 0xFFFFFFFD, xPSR T bit, poisoned LR (M4/src/tx_thread_stack_build.S:96-122). M33 uses 0xFFFFFFBC for the NS return (M33/src/tx_thread_stack_build.S:99-101). ## Q3 Time - Timer wheel: `_tx_timer_list[TX_TIMER_ENTRIES=32]` of circular doubly linked `TX_TIMER_INTERNAL` lists (I/tx_timer.h:49, 95; struct I/tx_api.h:339-364 with remaining and reinit ticks, callback plus ULONG parameter, list_head back-pointer). `_tx_timer_current_ptr` advances one slot per tick with wrap (I/tx_timer.h:108; M4/src/tx_timer_interrupt.S:151-161). - Insertion at current + min(remaining, 32) - 1 (C/tx_timer_system_activate.c:100-119). Timers beyond 32 ticks are parked in the farthest slot; on "expiry" remaining -= 32 and they are re-queued without callback (C/tx_timer_thread_entry.c:184-215; "expiration adjust" performance counter). - The ISR does only: clock++, slice--, set `_tx_timer_expired` if the current slot is non-empty (tx_timer_interrupt.S:88-140); then `_tx_timer_expiration_process` resumes the system timer thread (C/tx_timer_expiration_process.c:120). With TX_TIMER_PROCESS_IN_ISR callbacks run inside the ISR guarded by `_tx_timer_processing_active` (:125-347, 146). - Timer thread "System Timer Thread", priority TX_TIMER_THREAD_PRIORITY default 0, static stack (C/tx_timer_initialize.c:251-255; M4/inc/tx_port.h:134). Loop: detach the slot list, run callbacks with interrupts enabled (thread_entry.c:299), reload periodic timers from re_initialize_ticks (:264) and re-insert relative to the current slot (:408), self-suspend when nothing expired (:454). Because reinsertion is relative to the slot pointer at processing time, a late timer thread shifts subsequent periods (drift), not an absolute deadline. - Sleep: TX_SLEEP plus remaining_ticks (C/tx_thread_sleep.c:155, 176); system_suspend activates the TCB-embedded `tx_thread_timer` (I/tx_api.h:473; suspend.c:118-134) whose callback `_tx_thread_timeout` was bound at create (I/tx_thread.h:124-125); the timeout resumes a sleeper or calls the object cleanup (C/tx_thread_timeout.c:88, 149). - The tick is a 32-bit ULONG (I/tx_timer.h:77); `tx_time_get` reads it (C/tx_time_get.c:80); TX_WAIT_FOREVER is 0xFFFFFFFF and never enters the wheel (system_activate.c:88); 100 ticks per second by default (I/tx_api.h:272). ## Q4 Blocking and waiting - The waiter list per object is a circular doubly linked list of TCBs (`tx_thread_suspended_next` and `_previous`, I/tx_api.h:481-483; for example `tx_queue_suspension_list` :952-954). A service stores `tx_thread_suspend_cleanup`, `tx_thread_suspend_control_block`, extra info, bumps `tx_thread_suspension_sequence` (I/tx_api.h:479-487, 585; C/tx_queue_send.c:302-314), sets the state code (I/tx_api.h:139-152), sets `tx_thread_suspending`, preempt_disable++, re-enables interrupts, then calls `_tx_thread_system_suspend`, which proceeds only if `tx_thread_suspending` is still TRUE (suspend.c:156). A resume that lands in that window clears the flag and the suspension evaporates (resume.c:401-431). - Timeout and wakeup race: cleanup functions re-validate cleanup pointer identity, sequence number, object id, and state before touching lists (C/tx_queue_cleanup.c:93-199; C/tx_mutex_cleanup.c:93, 176). Every waker nulls `tx_thread_suspend_cleanup` before resuming (tx_queue_send.c:221). Wait-abort reuses cleanup with TX_WAIT_ABORTED (C/tx_thread_wait_abort.c:113, 163). - Ordering is FIFO: new waiters append at the tail (tx_queue_send.c body; C/tx_mutex_get.c:240 region). Exceptions: `tx_queue_front_send` suspends at the head (C/tx_queue_front_send.c:317-342); explicit `tx_*_prioritize` moves the highest-priority waiter to the front (C/tx_mutex_prioritize.c:105-233, restarts if the list changed :167-177); a mutex put with TX_INHERIT calls prioritize automatically (C/tx_mutex_put.c:332-335, 587). - Delete with waiters resumes all with TX_DELETED (C/tx_queue_delete.c:153-154). - Event flags: AND or OR and clear options (C/tx_event_flags_get.c:121-245); `tx_event_flags_group_delayed_clear` defers clears while a set is walking the list (:245; I/tx_api.h:790); set nulls the head "to thwart manipulation" by ISRs, an ISR set marks `reset_search` to restart the walk (C/tx_event_flags_set.c:314, 583), satisfied threads are collected then resumed (:486). Event chaining is the `tx_event_flags_set_notify` callback (C/tx_event_flags_set_notify.c:92) invoked after every set (set.c:597); queue and semaphore have analogous `*_send_notify` and `*_put_notify` (I/tx_api.h:987, 1042). - No multi-object wait: the TCB holds exactly one cleanup and control-block pair (I/tx_api.h:479-480); notify callbacks forwarding into one event-flags group are the documented substitute. ## Q5 Synchronization and priority inheritance - Mutex fields: recursive `tx_mutex_ownership_count`, owner, inherit flag, `tx_mutex_highest_priority_waiting`, owned_next and prev threaded into the owner's `tx_thread_owned_mutex_list` (I/tx_api.h:857-888, 517-518); the TCB keeps `tx_thread_user_priority`, `tx_thread_user_preempt_threshold`, `tx_thread_inherit_priority` (:511-513). - Get: free leads to own and link (C/tx_mutex_get.c:159); re-get increments the count (:175). Contended with TX_INHERIT: record the highest waiter (:352), lower the owner's inherit_priority (:360), raise the owner via `_tx_mutex_priority_change` (:369), which rewrites priority and threshold for a non-ready owner or suspend-plus-resumes a ready one to relocate it (C/tx_mutex_priority_change.c:97-105, 186, 215) and fixes the preempted map and execute pointer (:274). - Not transitive: only the direct owner is raised; if that owner is itself blocked on another mutex, nothing walks the chain (no loop in tx_mutex_get.c). - Put (C/tx_mutex_put.c): owner check skipped when preempt_disable is set (:139); when the count reaches 0 and inherit, scan all owned mutexes for the maximum highest_priority_waiting to get the new inherit_priority (:253-295), restore priority (:382 no waiters; :552 and 619 after prioritize :332-335 and 587 hands ownership to the highest waiter). - Owner death: `_tx_thread_mutex_release` (I/tx_thread.h:390) installed by C/tx_mutex_create.c:93 leads to `_tx_mutex_thread_release`, which forces count to 1 and puts every owned mutex (C/tx_mutex_cleanup.c:251-280); called on completion (C/tx_thread_shell_entry.c:118) and terminate (C/tx_thread_terminate.c:281). - Semaphore ceiling put returns TX_CEILING_EXCEEDED when the count is at or above the ceiling and nobody waits (C/tx_semaphore_ceiling_put.c:121). ## Q6 IPC and memory - Queues: 1 to 16 ULONG messages (I/tx_api.h:109-113, 279), unrolled word copy `TX_QUEUE_MESSAGE_COPY` (I/tx_queue.h:83-94), ring start, end, read, write (I/tx_api.h:942-948); if a receiver waits, the message is copied straight into its buffer (C/tx_queue_send.c:222-227); front_send steps the read pointer backwards; a send-notify callback (:170). - Block pool: a singly linked free list, header word rewritten to point at the pool when allocated (C/tx_block_allocate.c:160), suspends with cleanup when empty (:238). - Byte pool: first-fit (C/tx_byte_pool_search.c:162) over an implicit list of [next][FREE 0xFFFFEEEE or pool pointer] headers (I/tx_byte_pool.h:51; minimum split 20 bytes :55); a rotating search pointer set to the first free block seen (:145), adjacent free blocks coalesced lazily during search (:182), split when the remainder is at least the minimum (:272), early reject by theoretical availability (:99). The search runs with interrupts periodically enabled; `tx_byte_pool_owner` detects preemption and restarts (:253; allocate retry loop C/tx_byte_allocate.c:150-171). Release moves the search pointer back (C/tx_byte_release.c:200). Fragmentation is possible; no size classes. - Static model: every control block and stack is caller memory; public structs expose the full layout (I/tx_api.h:423-599); the kernel allocates nothing, only passes `_tx_initialize_unused_memory` to `tx_application_define` (C/tx_initialize_kernel_enter.c:133). The only exception: M33 secure stacks use malloc (M33/src/tx_thread_secure_stack.c:253). ## Q7 Isolation - Modules: sources absent; README.md:15, 28 lists common_modules and ports_modules and :105-106 describes dynamically loaded modules in one paragraph. No MPU code in the base kernel. - M33 TrustZone: the NS kernel calls secure CMSE entry functions (M33/src/tx_thread_secure_stack.c:103, 200, 347, 452, 545) that manage a fixed table of TX_MAX_SECURE_CONTEXTS=32 (:49); secure stacks of 256 to 1024 bytes are malloc'd with seal 0xFEF5EDA5 (:36, 40, 45, 253); index+1 kept in the TCB extension `tx_thread_secure_stack_context` (M33/inc/tx_port.h:240, 251), read by assembly at offset 0x90 (M33/src/tx_thread_schedule.S:162); PendSV calls save and restore which load and zero PSP_S and PSPLIM_S (:166, 235; secure_stack.c:126, 495). NS requests go through SVC 1, 2, 3 (M33/src/tx_thread_secure_stack_allocate.S:73; SVC_Handler schedule.S:334-358). The timer thread gets a 256-byte secure stack (tx_port.h:310-311); freed at delete (:302). - PSPLIM: the scheduler loads PSPLIM from `tx_thread_stack_start` before restoring (schedule.S:242); the UsageFault handler checks STKOF and calls `_tx_thread_stack_error_handler` (M33/example_build/tx_initialize_low_level.S:199-221; TX_PORT_THREAD_STACK_ERROR_HANDLING tx_port.h:134). M4 has no hardware limit. - Software stack checking: fill 0xEFEFEFEF or random (I/tx_api.h:131-133; C/tx_thread_create.c:106, 123), guard words at both ends checked on every suspend, resume, and slice (I/tx_thread.h:210-236), binary-search high-water (C/tx_thread_stack_analyze.c:116-132), application handler (C/tx_thread_stack_error_handler.c:80); minimum stack 200 (M4/inc/tx_port.h:122). ## Q8 Observability - TraceX: one caller buffer with header, object registry, and a circular event ring (I/tx_trace.h:30-40; header :74; registry entry with 32-character name :93, 106-115; event: thread pointer, priority with threshold<<16, id, timestamp, 4 info words :242-257). `TX_TRACE_IN_LINE_INSERT` is inlined into every service (:295-329); wrap-around invokes the buffer-full callback (:493-499; C/tx_trace_buffer_full_notify.c:75); class filter bits (C/tx_trace_event_filter.c:72); registry slots reused by object pointer (C/tx_trace_object_register.c:123-125); enable registers all live objects (C/tx_trace_enable.c:235). Scheduling code patches the "next thread" field after the decision if the timestamp is unchanged (resume.c:325-338). The M4 timestamp is DWT CYCCNT at 0xE0001004 (M4/inc/tx_port.h:161). ISR enter and exit events are application-inserted. - Performance info: global and per-TCB counters (I/tx_thread.h:439-515 including an 8-entry execute log; I/tx_api.h:520-555), read via C/tx_thread_performance_info_get.c:79+; `tx_thread_info_get` returns user priority and threshold, run count (incremented in PendSV schedule.S:213-217), created and suspended-list neighbours (C/tx_thread_info_get.c:122-150). - Error layer: 68 `txe_*` shells; tx_api.h maps names to `_txe_` by default, `_tx_` under TX_DISABLE_ERROR_CHECKING, `_txr_` under TX_ENABLE_MULTI_ERROR_CHECKING (I/tx_api.h:1057-1065, 1191). Shells check NULL, id magic (C/txe_mutex_get.c:92), caller context (:111, 146), control-block size via a sizeof macro (I/tx_api.h:1266, 1377; C/txe_thread_create.c:116), stack overlap against all created threads (:166-182), minimum stack (:232). Internals never call txe. ## Q9 Configuration, portability, quality - tx_user.h via TX_INCLUDE_USER_DEFINE_FILE; I/tx_user_sample.h:89-302 enumerates options, :55-76 gives "max speed" and "min size" recipes (TX_NOT_INTERRUPTABLE selects `_ni_` variants, resume.c:533-615; TX_INLINE_THREAD_RESUME_SUSPEND, TX_TIMER_PROCESS_IN_ISR, TX_REACTIVATE_INLINE, TX_NO_TIMER, TX_DISABLE_NOTIFY_CALLBACKS). `_tx_build_options` records the build (I/tx_thread.h:393-426). TX_SAFETY_CRITICAL forbids disabling error checks, ISR timers, inline reactivation, stack checking (I/tx_api.h:2103-2140) and raises an exception if the scheduler ever returns (:2221-2222; C/tx_initialize_kernel_enter.c:150-153). - Port contract: tx_port.h supplies types, TX_MAX_PRIORITIES, TX_MINIMUM_STACK, timer-thread defaults, TX_DISABLE, TX_RESTORE, TX_SAVE_AREA, TX_THREAD_GET_SYSTEM_STATE, optional TX_LOWEST_SET_BIT_CALCULATE, object `*_EXTENSION` and create, delete, completed, terminated hooks (M4/inc/tx_port.h:216-400), TX_PORT_SPECIFIC_* init hooks (I/tx_initialize.h); assembly: schedule, stack_build, system_return, context_save and restore, interrupt_control, disable, restore, timer_interrupt, plus the example low_level (M4/src file list). TCB words 0 to 9 are assembly-fixed (I/tx_api.h:425-442). - MISRA: TX_MISRA_ENABLE routes casts, memset, pointer math, and message copy through 70 assembly shims (M4/src/tx_misra.S:131-237; C fallback C/tx_misra.c:50-121); the revision history records "modified code for MISRA compliance" across files (docs/revision_history.txt:39-270). File headers carry component, release, author only (C/tx_mutex_get.c:35-44). - Certification: no IEC 61508 or ISO 26262 text in the repository; only CONTRIBUTING.md:7 "safety-certified OS" and "safety critical exception logic" (revision_history.txt:350-460). Supporting artifacts: the TX_SAFETY_CRITICAL gate, the MISRA shims, the txe layer. - Tests: none in the clone; README.md:31 points to utility/ "Test cases and utilities"; CMakeLists.txt:27-32 enables ctest for port example builds; build-all.log and ctest-all.log are artifacts only. ## Ideas to borrow 1. Preemption threshold with the preempted-map bookkeeping (resume.c:238-259; suspend.c:403-477): a cheap bounded-preemption band that doubles as a non-preemptible section. 2. Suspension sequence number plus cleanup-pointer identity checks to make timeout, wakeup, and abort races idempotent (I/tx_api.h:585; tx_queue_cleanup.c:93-199). 3. A timer wheel with lazy "more than 32 ticks" re-queue plus processing in a dedicated thread with interrupts enabled around callbacks (thread_entry.c:184-299). 4. The txe, tx, txr name mapping: a zero-cost removable validation layer, with sizeof(control block) passed by macro to catch ABI mismatches (I/tx_api.h:1057-1065, 1266). 5. Event chaining via per-object notify callbacks as a lightweight multi-source wait (set.c:597). 6. An inline trace macro with post-hoc "next thread" patching and per-class filters (I/tx_trace.h:295-329; resume.c:325-338). 7. The byte-pool "owner token" to allow interrupt windows inside a long search (tx_byte_pool_search.c:253). 8. The TX_SAFETY_CRITICAL compile-time gate rejecting unsafe option combinations (I/tx_api.h:2103-2140). ## Weaknesses 1. Public control-block layouts are the ABI; assembly hard-codes TCB offsets (I/tx_api.h:425-442; schedule.S:174, 213; M33 schedule.S:162 offset 0x90), and `tx_thread_create` must pass sizeof(TX_THREAD) to detect drift (txe_thread_create.c:116). 2. FIFO-only waiter order by default; priority order requires explicit `*_prioritize` calls or TX_INHERIT mutexes (tx_mutex_prioritize.c; tx_queue_send.c tail append). 3. Priority inheritance is single-level, not transitive (tx_mutex_get.c:360-369 touches only the direct owner). 4. No multi-object wait; notify callbacks run in the setter's context and are one per object (I/tx_api.h:479-480, 987). 5. The timer wheel is a 32-slot circular array: long timers are re-queued every 32 ticks and periodic reload is relative to processing time, so a delayed timer thread drifts (thread_entry.c:184-215, 408). 6. The timer thread runs at priority 0 and all callbacks share one stack and priority; TX_TIMER_PROCESS_IN_ISR moves callbacks into the ISR (expiration_process.c:125-347). 7. Isolation is thin in the base kernel: no MPU, Modules absent here, M33 secure stacks rely on malloc and a fixed 32-context table (secure_stack.c:49, 253). 8. The byte pool is first-fit with lazy coalescing and no free list; worst-case search is O(fragments) under interrupt windows (tx_byte_pool_search.c:99-272). 9. Idle is a busy loop unless TX_ENABLE_WFI or TX_LOW_POWER is defined (schedule.S:251-296); low-power and execution-profile code are external hooks only. 10. Certification and MISRA claims are not substantiated inside the repository (no test suite, no certificate artifacts; CONTRIBUTING.md:7 only).