') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); Relocate NUCLEO_F401RE to targets and adopt generic BSP framework by AmmarOkla12772 · Pull Request #50 · eclipse-threadx/samplex · GitHub
Skip to content

Relocate NUCLEO_F401RE to targets and adopt generic BSP framework - #50

Merged
fdesbiens merged 4 commits into
eclipse-threadx:devfrom
AmmarOkla12772:feat/nucleo-bsp-migration
Aug 31, 2026
Merged

Relocate NUCLEO_F401RE to targets and adopt generic BSP framework#50
fdesbiens merged 4 commits into
eclipse-threadx:devfrom
AmmarOkla12772:feat/nucleo-bsp-migration

Conversation

@AmmarOkla12772

@AmmarOkla12772AmmarOkla12772 commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

feat(nucleo): Relocate NUCLEO_F401RE to targets and adopt generic BSP framework

Summary

Follow-up PR relocating the existing STMicroelectronics NUCLEO_F401RE target into
targets/ and migrating it to the generic BSP framework introduced in #49.

Changes

  • Git History Preservation: Relocated NUCLEO_F401RE using git mv, with the
    STM32CubeF4 vendor tree detected as R100 renames without any changes to the vendor files.
  • Generic BSP Adoption: Replaced the legacy nucleo_bsp with lib/bsp/ implementing the
    generic board, LED, and console interfaces. The CMake target is named nucleo_bsp,
    matching polarfire_bsp.
  • Heap Safety: _sbrk() bounds the heap against _heap_limit, a new linker-script
    symbol at the end of the heap reservation, and takes a PRIMASK critical section.
  • Single UART Owner: bsp_console_init() owns USART2 configuration; pin and clock setup
    stays with HAL_UART_MspInit().
  • Dead Configuration Removal: Removed unused APP_CONFIG and CONFIG_DIR logic from the
    CMake and build scripts.
  • ARM CI Pipeline: Added the build-arm-nucleo job to .github/workflows/ci.yml, pinned
    to Arm GNU Toolchain 14.2.Rel1.

Review fixes

Applied on top of the original commit after review:

  • _sbrk() bounded the heap at BSP_RAM_END (0x20018000), the end of physical SRAM. That
    let malloc() grow the break through the reserved main stack, through the whole ThreadX
    byte pool holding every thread stack, and into the live MSP, all while reporting success.
    It now stops at _heap_limit (0x20001370), inside the 0x200 the linker script reserves.
  • bsp_console_init() had dropped the HAL_UART_Init() return value, so a failed UART
    booted on silently. Restored the failure path (MISRA C:2012 Rule 17.7).
  • UartHandle was a global whose only declaration died with board_init.h; now file-scope
    static console_uart (Rules 8.4, 8.7).
  • Newlib syscall overrides consolidated into the always-linked newlib_stubs.c. _write()
    and _read() had been archived into the BSP static library, where the linker extracted
    them only incidentally.
  • Bounds arithmetic computed in uintptr_t instead of by forming out-of-bounds pointers;
    prototypes added ahead of every syscall definition; the HAL_UART_Transmit() const cast
    documented as a Rule 11.8 deviation.
  • CMAKE_C_STANDARD 11 -> 99, and the ARM toolchain pinned to GCC 14.2.Rel1 rather than
    tracking the runner's distribution package (13.2). AGENTS.md specifies GCC 14 and C99.
  • README.md corrected: it had moved with git mv but still described the pre-migration
    paths, board_init.c, and lib/nucleo_bsp/. Validation figures re-measured.

AI assistance disclosure

bsp_board.c, bsp_led.c, bsp_console.c and newlib_stubs.c carry the AGENTS.md AI
Disclosure header. They derive from board_init.c, nucleo_bsp.c and console.c, each of
which carried // Some portions generated by Codex (GPT-5). before this migration dropped
it.

That Codex work was mine (@fdesbiens), not @AmmarOkla12772's. It was done while editing
PR #45 under the AGENTS.md exception allowing maintainer edits to incoming PRs, and landed
via squash-merge 1bddd17 — which assigns git authorship to the PR author while the real
participants appear only in its trailers (Ammar Okla, Frederic Desbiens, Codex). The
disclosure headers are not a statement about this PR's author.

Verification

  • Clean build of nucleo_f401re.elf under both GCC 13.2.1 and the pinned GCC 14.2.1, with
    -Wall -Wshadow -Wdouble-promotion -Werror. 20120 B ROM, 6000 B RAM.
  • Heap window confirmed in the linked ELF: _end 0x20001170 -> _heap_limit 0x20001370,
    clear of __RAM_segment_used_end__ 0x20001770 where the ThreadX pool begins.
  • _write, _read and _sbrk confirmed resolved from the application object, absent from
    libnucleo_bsp.a.
  • Verified the relocated STM32CubeF4 tree is detected as R100 with zero vendor-tree churn.

Not addressed

No runtime regression test for this target. The PolarFire target gates CI on a headless
Renode suite; build-arm-nucleo only checks that the ELF exists, so the clock configuration,
TIM2 timebase, console and the new heap bound are unexercised. Renode has STM32F4 platform
support and this gap should be closed in a follow-up.

@AmmarOkla12772AmmarOkla12772 self-assigned this Aug 31, 2026
@AmmarOkla12772
AmmarOkla12772 marked this pull request as ready for review August 31, 2026 11:24
@fdesbiens
fdesbiensforce-pushed the feat/nucleo-bsp-migration branch from d0b212d to 9d01d7dCompareAugust 31, 2026 15:04
ammarokla123and others added 4 commits August 31, 2026 11:07
_sbrk() bounded the heap at BSP_RAM_END (0x20018000), the end of physical
SRAM. On this target that is not a guard at all. NUCLEO_F401RE.ld lays RAM
out as:
_end 0x20001170 heap base
_end + _Min_Heap_Size 0x20001370 intended heap ceiling
__RAM_segment_used_end__ 0x20001770 ThreadX byte pool starts here
byte pool end 0x20017000
_estack / BSP_RAM_END 0x20018000
so malloc() could grow the break through the reserved main stack, through
the entire ThreadX byte pool holding every thread stack and the queue
buffer, and into the live MSP, all while _sbrk() reported success. Only an
allocation past the top of SRAM returned ENOMEM.
The linker script now exports _heap_limit at the end of the heap
reservation and _sbrk() bounds against that, which keeps the heap inside
the 0x200 the script already set aside for it. Both bounds checks are also
computed in uintptr_t rather than by forming an out-of-bounds pointer
first, and the negative branch derives its magnitude without negating
PTRDIFF_MIN. BSP_RAM_END keeps documenting the memory map but now carries
a note against reusing it as a heap bound.
All newlib syscall overrides move into newlib_stubs.c, which the
application compiles directly. _write() and _read() previously sat in
bsp_console.c inside libbsp.a, where the linker extracted them only
because bsp_board.o happened to reference bsp_console_init(); breaking
that incidental chain would have silently handed printf to the libnosys
stubs, and CI only checks that the ELF exists. Console input stays board
specific behind nucleo_console.h, since the shared <bsp/console.h>
contract is write-only.
The BSP library is renamed nucleo_bsp, matching polarfire_bsp and freeing
the bare "bsp" target name.
The AI Disclosure header on newlib_stubs.c records the Codex-assisted
origin of the syscall shims moved in from console.c. That work was done by
Frederic Desbiens while editing PR eclipse-threadx#45, not by the PR author; the
squash-merge in 1bddd17 assigned git authorship to the contributor and its
Co-authored-by trailers are the accurate record.
bsp_console_init() dropped the return value of HAL_UART_Init(). The code it
replaced routed the failure to Error_Handler(); the migration left the call
bare, so a UART that failed to initialise booted on silently with no
console. MISRA C:2012 Rule 17.7. The status is checked again and a failure
now halts, matching the previous behaviour.
The UART handle was a non-static global named UartHandle, but the extern
declaration that used to live in board_init.h went away with that header,
leaving an object with external linkage that nothing declared (Rule 8.4 and
8.7). It is now file-scope static console_uart. The redundant GPIOA and
USART2 clock enables are gone as well: HAL_UART_MspInit() already owns that
configuration, which is what "single UART owner" should mean. The const
cast required by HAL_UART_Transmit() is documented as a Rule 11.8
deviation.
bsp_board.c, bsp_led.c and bsp_console.c carry the AI Disclosure header
required by AGENTS.md for new files. They derive from board_init.c,
nucleo_bsp.c and console.c, each of which carried "Some portions generated
by Codex (GPT-5)"; the rename dropped the disclosure while keeping most of
the content, and git scores bsp_board.c as an 80% similarity rename.
That Codex work was Frederic Desbiens', done while editing another
contributor's PR under the AGENTS.md exception permitting edits to
incoming PRs. It reached the tree through 1bddd17, a GitHub squash-merge
of PR eclipse-threadx#45 whose git author is the PR author but whose trailers name the
real participants:
Co-authored-by: Ammar Okla <ammargawkla@gmail.com>
Co-authored-by: Frederic Desbiens <frederic.desbiens@eclipse-foundation.org>
Co-authored-by: Codex <codex@openai.com>
The disclosure is therefore not a statement about this PR's author.
…tion
The build-arm-nucleo job installed the distribution's gcc-arm-none-eabi,
which is GCC 13.2 on ubuntu-24.04, so the compiler drifted with the runner
image. AGENTS.md specifies GCC 14 and the sibling RISC-V job already pins
xPack GCC 14.2.0 by URL; the ARM job now pins Arm GNU Toolchain 14.2.Rel1
the same way. The toolchain bundles its own newlib, so the separate newlib
packages are no longer installed.
CMAKE_C_STANDARD moves from 11 to 99 per the AGENTS.md C99 requirement.
The target builds clean under both GCC 13.2.1 and the pinned GCC 14.2.1
with -Wall -Wshadow -Wdouble-promotion -Werror.
README.md moved with git mv but still described the pre-migration layout:
the build directory as STMicroelectronics/NUCLEO_F401RE, the HAL timebase
overrides as living in board_init.c, and the BSP as lib/nucleo_bsp/. All
three are corrected and the BSP entry now lists the four sources behind the
generic interfaces.
The Validation Record quoted 18244 B ROM and 5632 B RAM against a toolchain
the CI pipeline does not use. Re-measured against the newly pinned GCC
14.2.1: 20120 B ROM and 6000 B RAM. The hardware verification checklist is
left as the contributor recorded it.
@fdesbiens
fdesbiensforce-pushed the feat/nucleo-bsp-migration branch from 9d01d7d to 2743bceCompareAugust 31, 2026 15:09
@fdesbiens
fdesbiens merged commit 70f7da9 into eclipse-threadx:devAug 31, 2026
4 checks passed
fdesbiens added a commit that referenced this pull request Aug 31, 2026
The ARM target had no runtime test. Its CI job checked only that the ELF
existed, so the clock configuration, the TIM2 HAL timebase, the console and
the heap bound were all unexercised. This brings it to parity with the
PolarFire target, which gates CI on a headless Renode run.
Renode ships no NUCLEO-F401RE board description, so renode/nucleo_f401re.repl
derives one from the generic STM32F4 CPU platform and corrects Flash to
512 KB and SRAM to 96 KB. The generic platform is sized for the larger
F407/F429 parts, and both the linker script's heap reservation and the
_sbrk() bound depend on those limits being right.
main.c gains seven startup self-tests that run before tx_kernel_enter(), so
a failure is reported even when the scheduler never starts. They cover the
_sbrk() allocate, release, underflow and over-limit paths, the invariant
that the heap reservation ends at or below the ThreadX byte pool, the
84 MHz SystemCoreClock, and that TIM2 still ticks after HAL_RCC_ClockConfig()
re-enters HAL_InitTick().
Test 4 is the regression guard for the heap bound fixed in #50. Requesting
32 KB fits inside the 96 KB SRAM but far exceeds the heap reservation.
Reinstating the old end-of-SRAM bound was verified to fail the suite:
newlib's first malloc() then took roughly 4 KB and put the break at
0x20002170, inside the ThreadX byte pool, which also broke self-tests 1
and 3. With the bound correct, _sbrk() refuses the oversized request,
newlib retries smaller, and malloc(64) succeeds using 72 bytes of the
512-byte reservation - so the reservation is adequate and no heap growth
is needed.
scripts/test_renode.py drives nucleo_f401re_ci.resc, which advances a fixed
span of virtual time and quits on its own rather than depending on wall
clock. Beyond the self-tests it asserts the boot banner, that the blink
thread and the 1 Hz application timer have both run (covering the LED path
and the timer service), and that the mutex, queue, event-flag and semaphore
counters are all non-zero. The suite was confirmed to exit 1 on a
reintroduced bug and 0 on the fixed tree.
The Robot Framework suite covers the same ground for renode-test, and the
new test-nucleo-renode CI job mirrors test-polarfire-renode.
main.c also drops a hardcoded 0x20018000 in favour of BSP_RAM_END now that
board_config.h is in scope, with the 4 KB main-stack margin named.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@AmmarOkla12772@fdesbiens@ammarokla123