Skip to content

Moved the startup self-tests and RAM sizing behind new BSP interfaces - #56

Merged
fdesbiens merged 1 commit into
eclipse-threadx:devfrom
fdesbiens:feature/bsp-selftest-memory-interfaces
Aug 31, 2026
Merged

Moved the startup self-tests and RAM sizing behind new BSP interfaces#56
fdesbiens merged 1 commit into
eclipse-threadx:devfrom
fdesbiens:feature/bsp-selftest-memory-interfaces

Conversation

@fdesbiens

Copy link
Copy Markdown
Contributor

Why

The framework's stated purpose is that application code can be built for any board that implements the bsp/ contracts. Neither demo could be. Both main.c files reached past those contracts for two things no portable application can see: the board's memory extents, and the hardware specifics its startup self-tests assert on.

Neither was ever application logic. The self-tests test the BSP — linker reservations, clock trees, interrupt controllers — so they belong in the BSP. Moving them takes most of the contamination in main.c with them.

What

Two new contracts in bsp/include/bsp/:

  • selftest.hbsp_self_test(report, context) runs the board's checks and reports each through an application-supplied callback, returning the failure count. Message formatting — and therefore the choice between printf() and bsp_console_write() — stays with the application while the checks stay with the board.
  • memory.hbsp_ram_region(first_unused, &base, &size) reports what RAM the application may claim, clamped against whatever the board reserves above ThreadX's first unused address.

Both targets implement both. Both main.c files now include nothing but C standard headers, tx_api.h and <bsp/...>. The NUCLEO byte pool comes out byte-identical at 88204 bytes, so there is no behaviour change on that target.

The target template and docs/architecture.md gained the two interfaces, and the template ships bsp_memory.c / bsp_selftest.c stubs so a new board has something to fill in. Neither restores the portability claim that #52 walked back — there is still no apps/ directory, and that stays true until CI builds one for two architectures.

One scope call worth flagging

Prototyping bsp_ram_region() on the PolarFire first — as the design intended — surfaced a latent bug there. Its _sbrk() bounded the heap at the end of DRAM, which is the same mistake the NUCLEO shipped with before #50. On the PolarFire it was harmless only because nothing else claimed that memory: the LM75 demo uses static stacks. The moment bsp_ram_region() promises that DRAM to an application, one oversized malloc() could take memory holding thread stacks.

Shipping the interface without fixing that would have made it a trap, so this PR also bounds the PolarFire heap against a documented 64 KB reservation that bsp_ram_region() skips, and adds a self-test guarding the bound the way the NUCLEO's test 4 does. Measured heap use on that target is under 256 bytes, so the reservation has roughly 256x headroom.

The PolarFire tick-rate compile-time check moved to hwtimer.c, next to the TICK_CYCLES constant it guards, because it needs ThreadX headers a portable main.c should not have to pull in.

Verification

Built and Renode-tested locally on both architectures with the CI-pinned toolchains (Arm GNU 14.3.Rel1, xPack RISC-V GCC 14.3.0-1, Renode 1.16.1):

TargetBuildtest_renode.py
NUCLEO-F401RE (Cortex-M4)clean, no warningsexit 0, 8/8 self-tests pass
PolarFire Icicle (RV64)clean apart from the known pre-existing RWX LOAD-segment warningexit 0, 7/7 self-tests pass

Both suites were also confirmed to have kept their teeth after the move. Weakening each target's _sbrk() bound back to the end of RAM makes them exit 1 — three failures on the NUCLEO (matching what was documented when they were first written), one on the PolarFire's new guard — so no assertion was silently lost in the refactor.

Notes

  • New self-test coverage: bsp_ram_region()'s invariant on both targets, and the PolarFire heap bound.
  • This is the first of four steps toward closing the portability gap, and a coherent resting place on its own: BSP tests belong in the BSP, and vendor headers are out of both main.c files regardless of whether a shared apps/ ever lands.

The framework's stated purpose is that application code can be built for
any board implementing the `bsp/` contracts. Neither demo could be,
because both `main.c` files reached past those contracts for two things
no portable application can see: the board's memory extents, and the
hardware specifics its startup self-tests assert on.
Neither was ever application logic. The self-tests test the BSP - linker
reservations, clock trees, interrupt controllers - so they belong in the
BSP. Two new contracts move both behind the boundary:
* `bsp/selftest.h` - `bsp_self_test()` runs the board's checks and
reports each through an application-supplied callback, so message
formatting (and therefore the choice between `printf()` and
`bsp_console_write()`) stays with the application while the checks stay
with the board.
* `bsp/memory.h` - `bsp_ram_region()` reports what RAM the application
may claim, clamped against whatever the board reserves.
Both targets implement both, and both `main.c` files now include nothing
but the C standard headers, `tx_api.h` and `<bsp/...>`. The NUCLEO byte
pool comes out byte-identical at 88204 bytes.
Prototyping `bsp_ram_region()` on the PolarFire first surfaced a latent
bug there. Its `_sbrk()` bounded the heap at the end of DRAM, which is
the same mistake the NUCLEO shipped with before eclipse-threadx#50: harmless only
because nothing else claimed that memory. The moment `bsp_ram_region()`
promises it to an application, an oversized `malloc()` could take memory
holding thread stacks. The heap is now bounded against a documented 64 KB
reservation that `bsp_ram_region()` skips, and a new self-test guards the
bound the way the NUCLEO's test 4 does. Measured heap use is under 256
bytes, so the reservation has ample headroom.
The PolarFire tick-rate compile-time check moved to `hwtimer.c`, next to
the `TICK_CYCLES` constant it guards, since it needed ThreadX headers
that a portable `main.c` should not have to pull in.
Verified locally on both targets: clean builds plus `test_renode.py`
green on Cortex-M4 and RV64. Both suites were also confirmed to still
have teeth - weakening each target's `_sbrk()` bound back to the end of
RAM makes them exit 1 (three failures on the NUCLEO, one on the
PolarFire), so no assertion was lost in the move.
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@fdesbiens
fdesbiens merged commit 92707e0 into eclipse-threadx:devAug 31, 2026
5 checks passed
@fdesbiens
fdesbiens deleted the feature/bsp-selftest-memory-interfaces branch August 31, 2026 20:17
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.

1 participant

@fdesbiens
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Moved the startup self-tests and RAM sizing behind new BSP interfaces by fdesbiens · Pull Request #56 · eclipse-threadx/samplex · GitHub
Skip to content

Moved the startup self-tests and RAM sizing behind new BSP interfaces - #56

Merged
fdesbiens merged 1 commit into
eclipse-threadx:devfrom
fdesbiens:feature/bsp-selftest-memory-interfaces
Aug 31, 2026
Merged

Moved the startup self-tests and RAM sizing behind new BSP interfaces#56
fdesbiens merged 1 commit into
eclipse-threadx:devfrom
fdesbiens:feature/bsp-selftest-memory-interfaces

Conversation

@fdesbiens

Copy link
Copy Markdown
Contributor

Why

The framework's stated purpose is that application code can be built for any board that implements the bsp/ contracts. Neither demo could be. Both main.c files reached past those contracts for two things no portable application can see: the board's memory extents, and the hardware specifics its startup self-tests assert on.

Neither was ever application logic. The self-tests test the BSP — linker reservations, clock trees, interrupt controllers — so they belong in the BSP. Moving them takes most of the contamination in main.c with them.

What

Two new contracts in bsp/include/bsp/:

  • selftest.hbsp_self_test(report, context) runs the board's checks and reports each through an application-supplied callback, returning the failure count. Message formatting — and therefore the choice between printf() and bsp_console_write() — stays with the application while the checks stay with the board.
  • memory.hbsp_ram_region(first_unused, &base, &size) reports what RAM the application may claim, clamped against whatever the board reserves above ThreadX's first unused address.

Both targets implement both. Both main.c files now include nothing but C standard headers, tx_api.h and <bsp/...>. The NUCLEO byte pool comes out byte-identical at 88204 bytes, so there is no behaviour change on that target.

The target template and docs/architecture.md gained the two interfaces, and the template ships bsp_memory.c / bsp_selftest.c stubs so a new board has something to fill in. Neither restores the portability claim that #52 walked back — there is still no apps/ directory, and that stays true until CI builds one for two architectures.

One scope call worth flagging

Prototyping bsp_ram_region() on the PolarFire first — as the design intended — surfaced a latent bug there. Its _sbrk() bounded the heap at the end of DRAM, which is the same mistake the NUCLEO shipped with before #50. On the PolarFire it was harmless only because nothing else claimed that memory: the LM75 demo uses static stacks. The moment bsp_ram_region() promises that DRAM to an application, one oversized malloc() could take memory holding thread stacks.

Shipping the interface without fixing that would have made it a trap, so this PR also bounds the PolarFire heap against a documented 64 KB reservation that bsp_ram_region() skips, and adds a self-test guarding the bound the way the NUCLEO's test 4 does. Measured heap use on that target is under 256 bytes, so the reservation has roughly 256x headroom.

The PolarFire tick-rate compile-time check moved to hwtimer.c, next to the TICK_CYCLES constant it guards, because it needs ThreadX headers a portable main.c should not have to pull in.

Verification

Built and Renode-tested locally on both architectures with the CI-pinned toolchains (Arm GNU 14.3.Rel1, xPack RISC-V GCC 14.3.0-1, Renode 1.16.1):

TargetBuildtest_renode.py
NUCLEO-F401RE (Cortex-M4)clean, no warningsexit 0, 8/8 self-tests pass
PolarFire Icicle (RV64)clean apart from the known pre-existing RWX LOAD-segment warningexit 0, 7/7 self-tests pass

Both suites were also confirmed to have kept their teeth after the move. Weakening each target's _sbrk() bound back to the end of RAM makes them exit 1 — three failures on the NUCLEO (matching what was documented when they were first written), one on the PolarFire's new guard — so no assertion was silently lost in the refactor.

Notes

  • New self-test coverage: bsp_ram_region()'s invariant on both targets, and the PolarFire heap bound.
  • This is the first of four steps toward closing the portability gap, and a coherent resting place on its own: BSP tests belong in the BSP, and vendor headers are out of both main.c files regardless of whether a shared apps/ ever lands.

The framework's stated purpose is that application code can be built for
any board implementing the `bsp/` contracts. Neither demo could be,
because both `main.c` files reached past those contracts for two things
no portable application can see: the board's memory extents, and the
hardware specifics its startup self-tests assert on.
Neither was ever application logic. The self-tests test the BSP - linker
reservations, clock trees, interrupt controllers - so they belong in the
BSP. Two new contracts move both behind the boundary:
* `bsp/selftest.h` - `bsp_self_test()` runs the board's checks and
reports each through an application-supplied callback, so message
formatting (and therefore the choice between `printf()` and
`bsp_console_write()`) stays with the application while the checks stay
with the board.
* `bsp/memory.h` - `bsp_ram_region()` reports what RAM the application
may claim, clamped against whatever the board reserves.
Both targets implement both, and both `main.c` files now include nothing
but the C standard headers, `tx_api.h` and `<bsp/...>`. The NUCLEO byte
pool comes out byte-identical at 88204 bytes.
Prototyping `bsp_ram_region()` on the PolarFire first surfaced a latent
bug there. Its `_sbrk()` bounded the heap at the end of DRAM, which is
the same mistake the NUCLEO shipped with before eclipse-threadx#50: harmless only
because nothing else claimed that memory. The moment `bsp_ram_region()`
promises it to an application, an oversized `malloc()` could take memory
holding thread stacks. The heap is now bounded against a documented 64 KB
reservation that `bsp_ram_region()` skips, and a new self-test guards the
bound the way the NUCLEO's test 4 does. Measured heap use is under 256
bytes, so the reservation has ample headroom.
The PolarFire tick-rate compile-time check moved to `hwtimer.c`, next to
the `TICK_CYCLES` constant it guards, since it needed ThreadX headers
that a portable `main.c` should not have to pull in.
Verified locally on both targets: clean builds plus `test_renode.py`
green on Cortex-M4 and RV64. Both suites were also confirmed to still
have teeth - weakening each target's `_sbrk()` bound back to the end of
RAM makes them exit 1 (three failures on the NUCLEO, one on the
PolarFire), so no assertion was lost in the move.
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@fdesbiens
fdesbiens merged commit 92707e0 into eclipse-threadx:devAug 31, 2026
5 checks passed
@fdesbiens
fdesbiens deleted the feature/bsp-selftest-memory-interfaces branch August 31, 2026 20:17
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.

1 participant

@fdesbiens
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Moved the startup self-tests and RAM sizing behind new BSP interfaces by fdesbiens · Pull Request #56 · eclipse-threadx/samplex · GitHub
Skip to content

Moved the startup self-tests and RAM sizing behind new BSP interfaces - #56

Merged
fdesbiens merged 1 commit into
eclipse-threadx:devfrom
fdesbiens:feature/bsp-selftest-memory-interfaces
Aug 31, 2026
Merged

Moved the startup self-tests and RAM sizing behind new BSP interfaces#56
fdesbiens merged 1 commit into
eclipse-threadx:devfrom
fdesbiens:feature/bsp-selftest-memory-interfaces

Conversation

@fdesbiens

Copy link
Copy Markdown
Contributor

Why

The framework's stated purpose is that application code can be built for any board that implements the bsp/ contracts. Neither demo could be. Both main.c files reached past those contracts for two things no portable application can see: the board's memory extents, and the hardware specifics its startup self-tests assert on.

Neither was ever application logic. The self-tests test the BSP — linker reservations, clock trees, interrupt controllers — so they belong in the BSP. Moving them takes most of the contamination in main.c with them.

What

Two new contracts in bsp/include/bsp/:

  • selftest.hbsp_self_test(report, context) runs the board's checks and reports each through an application-supplied callback, returning the failure count. Message formatting — and therefore the choice between printf() and bsp_console_write() — stays with the application while the checks stay with the board.
  • memory.hbsp_ram_region(first_unused, &base, &size) reports what RAM the application may claim, clamped against whatever the board reserves above ThreadX's first unused address.

Both targets implement both. Both main.c files now include nothing but C standard headers, tx_api.h and <bsp/...>. The NUCLEO byte pool comes out byte-identical at 88204 bytes, so there is no behaviour change on that target.

The target template and docs/architecture.md gained the two interfaces, and the template ships bsp_memory.c / bsp_selftest.c stubs so a new board has something to fill in. Neither restores the portability claim that #52 walked back — there is still no apps/ directory, and that stays true until CI builds one for two architectures.

One scope call worth flagging

Prototyping bsp_ram_region() on the PolarFire first — as the design intended — surfaced a latent bug there. Its _sbrk() bounded the heap at the end of DRAM, which is the same mistake the NUCLEO shipped with before #50. On the PolarFire it was harmless only because nothing else claimed that memory: the LM75 demo uses static stacks. The moment bsp_ram_region() promises that DRAM to an application, one oversized malloc() could take memory holding thread stacks.

Shipping the interface without fixing that would have made it a trap, so this PR also bounds the PolarFire heap against a documented 64 KB reservation that bsp_ram_region() skips, and adds a self-test guarding the bound the way the NUCLEO's test 4 does. Measured heap use on that target is under 256 bytes, so the reservation has roughly 256x headroom.

The PolarFire tick-rate compile-time check moved to hwtimer.c, next to the TICK_CYCLES constant it guards, because it needs ThreadX headers a portable main.c should not have to pull in.

Verification

Built and Renode-tested locally on both architectures with the CI-pinned toolchains (Arm GNU 14.3.Rel1, xPack RISC-V GCC 14.3.0-1, Renode 1.16.1):

TargetBuildtest_renode.py
NUCLEO-F401RE (Cortex-M4)clean, no warningsexit 0, 8/8 self-tests pass
PolarFire Icicle (RV64)clean apart from the known pre-existing RWX LOAD-segment warningexit 0, 7/7 self-tests pass

Both suites were also confirmed to have kept their teeth after the move. Weakening each target's _sbrk() bound back to the end of RAM makes them exit 1 — three failures on the NUCLEO (matching what was documented when they were first written), one on the PolarFire's new guard — so no assertion was silently lost in the refactor.

Notes

  • New self-test coverage: bsp_ram_region()'s invariant on both targets, and the PolarFire heap bound.
  • This is the first of four steps toward closing the portability gap, and a coherent resting place on its own: BSP tests belong in the BSP, and vendor headers are out of both main.c files regardless of whether a shared apps/ ever lands.

The framework's stated purpose is that application code can be built for
any board implementing the `bsp/` contracts. Neither demo could be,
because both `main.c` files reached past those contracts for two things
no portable application can see: the board's memory extents, and the
hardware specifics its startup self-tests assert on.
Neither was ever application logic. The self-tests test the BSP - linker
reservations, clock trees, interrupt controllers - so they belong in the
BSP. Two new contracts move both behind the boundary:
* `bsp/selftest.h` - `bsp_self_test()` runs the board's checks and
reports each through an application-supplied callback, so message
formatting (and therefore the choice between `printf()` and
`bsp_console_write()`) stays with the application while the checks stay
with the board.
* `bsp/memory.h` - `bsp_ram_region()` reports what RAM the application
may claim, clamped against whatever the board reserves.
Both targets implement both, and both `main.c` files now include nothing
but the C standard headers, `tx_api.h` and `<bsp/...>`. The NUCLEO byte
pool comes out byte-identical at 88204 bytes.
Prototyping `bsp_ram_region()` on the PolarFire first surfaced a latent
bug there. Its `_sbrk()` bounded the heap at the end of DRAM, which is
the same mistake the NUCLEO shipped with before eclipse-threadx#50: harmless only
because nothing else claimed that memory. The moment `bsp_ram_region()`
promises it to an application, an oversized `malloc()` could take memory
holding thread stacks. The heap is now bounded against a documented 64 KB
reservation that `bsp_ram_region()` skips, and a new self-test guards the
bound the way the NUCLEO's test 4 does. Measured heap use is under 256
bytes, so the reservation has ample headroom.
The PolarFire tick-rate compile-time check moved to `hwtimer.c`, next to
the `TICK_CYCLES` constant it guards, since it needed ThreadX headers
that a portable `main.c` should not have to pull in.
Verified locally on both targets: clean builds plus `test_renode.py`
green on Cortex-M4 and RV64. Both suites were also confirmed to still
have teeth - weakening each target's `_sbrk()` bound back to the end of
RAM makes them exit 1 (three failures on the NUCLEO, one on the
PolarFire), so no assertion was lost in the move.
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@fdesbiens
fdesbiens merged commit 92707e0 into eclipse-threadx:devAug 31, 2026
5 checks passed
@fdesbiens
fdesbiens deleted the feature/bsp-selftest-memory-interfaces branch August 31, 2026 20:17
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.

1 participant

@fdesbiens
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', '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('^' + ".*" + ' Moved the startup self-tests and RAM sizing behind new BSP interfaces by fdesbiens · Pull Request #56 · eclipse-threadx/samplex · GitHub
Skip to content

Moved the startup self-tests and RAM sizing behind new BSP interfaces - #56

Merged
fdesbiens merged 1 commit into
eclipse-threadx:devfrom
fdesbiens:feature/bsp-selftest-memory-interfaces
Aug 31, 2026
Merged

Moved the startup self-tests and RAM sizing behind new BSP interfaces#56
fdesbiens merged 1 commit into
eclipse-threadx:devfrom
fdesbiens:feature/bsp-selftest-memory-interfaces

Conversation

@fdesbiens

Copy link
Copy Markdown
Contributor

Why

The framework's stated purpose is that application code can be built for any board that implements the bsp/ contracts. Neither demo could be. Both main.c files reached past those contracts for two things no portable application can see: the board's memory extents, and the hardware specifics its startup self-tests assert on.

Neither was ever application logic. The self-tests test the BSP — linker reservations, clock trees, interrupt controllers — so they belong in the BSP. Moving them takes most of the contamination in main.c with them.

What

Two new contracts in bsp/include/bsp/:

  • selftest.hbsp_self_test(report, context) runs the board's checks and reports each through an application-supplied callback, returning the failure count. Message formatting — and therefore the choice between printf() and bsp_console_write() — stays with the application while the checks stay with the board.
  • memory.hbsp_ram_region(first_unused, &base, &size) reports what RAM the application may claim, clamped against whatever the board reserves above ThreadX's first unused address.

Both targets implement both. Both main.c files now include nothing but C standard headers, tx_api.h and <bsp/...>. The NUCLEO byte pool comes out byte-identical at 88204 bytes, so there is no behaviour change on that target.

The target template and docs/architecture.md gained the two interfaces, and the template ships bsp_memory.c / bsp_selftest.c stubs so a new board has something to fill in. Neither restores the portability claim that #52 walked back — there is still no apps/ directory, and that stays true until CI builds one for two architectures.

One scope call worth flagging

Prototyping bsp_ram_region() on the PolarFire first — as the design intended — surfaced a latent bug there. Its _sbrk() bounded the heap at the end of DRAM, which is the same mistake the NUCLEO shipped with before #50. On the PolarFire it was harmless only because nothing else claimed that memory: the LM75 demo uses static stacks. The moment bsp_ram_region() promises that DRAM to an application, one oversized malloc() could take memory holding thread stacks.

Shipping the interface without fixing that would have made it a trap, so this PR also bounds the PolarFire heap against a documented 64 KB reservation that bsp_ram_region() skips, and adds a self-test guarding the bound the way the NUCLEO's test 4 does. Measured heap use on that target is under 256 bytes, so the reservation has roughly 256x headroom.

The PolarFire tick-rate compile-time check moved to hwtimer.c, next to the TICK_CYCLES constant it guards, because it needs ThreadX headers a portable main.c should not have to pull in.

Verification

Built and Renode-tested locally on both architectures with the CI-pinned toolchains (Arm GNU 14.3.Rel1, xPack RISC-V GCC 14.3.0-1, Renode 1.16.1):

TargetBuildtest_renode.py
NUCLEO-F401RE (Cortex-M4)clean, no warningsexit 0, 8/8 self-tests pass
PolarFire Icicle (RV64)clean apart from the known pre-existing RWX LOAD-segment warningexit 0, 7/7 self-tests pass

Both suites were also confirmed to have kept their teeth after the move. Weakening each target's _sbrk() bound back to the end of RAM makes them exit 1 — three failures on the NUCLEO (matching what was documented when they were first written), one on the PolarFire's new guard — so no assertion was silently lost in the refactor.

Notes

  • New self-test coverage: bsp_ram_region()'s invariant on both targets, and the PolarFire heap bound.
  • This is the first of four steps toward closing the portability gap, and a coherent resting place on its own: BSP tests belong in the BSP, and vendor headers are out of both main.c files regardless of whether a shared apps/ ever lands.

The framework's stated purpose is that application code can be built for
any board implementing the `bsp/` contracts. Neither demo could be,
because both `main.c` files reached past those contracts for two things
no portable application can see: the board's memory extents, and the
hardware specifics its startup self-tests assert on.
Neither was ever application logic. The self-tests test the BSP - linker
reservations, clock trees, interrupt controllers - so they belong in the
BSP. Two new contracts move both behind the boundary:
* `bsp/selftest.h` - `bsp_self_test()` runs the board's checks and
reports each through an application-supplied callback, so message
formatting (and therefore the choice between `printf()` and
`bsp_console_write()`) stays with the application while the checks stay
with the board.
* `bsp/memory.h` - `bsp_ram_region()` reports what RAM the application
may claim, clamped against whatever the board reserves.
Both targets implement both, and both `main.c` files now include nothing
but the C standard headers, `tx_api.h` and `<bsp/...>`. The NUCLEO byte
pool comes out byte-identical at 88204 bytes.
Prototyping `bsp_ram_region()` on the PolarFire first surfaced a latent
bug there. Its `_sbrk()` bounded the heap at the end of DRAM, which is
the same mistake the NUCLEO shipped with before eclipse-threadx#50: harmless only
because nothing else claimed that memory. The moment `bsp_ram_region()`
promises it to an application, an oversized `malloc()` could take memory
holding thread stacks. The heap is now bounded against a documented 64 KB
reservation that `bsp_ram_region()` skips, and a new self-test guards the
bound the way the NUCLEO's test 4 does. Measured heap use is under 256
bytes, so the reservation has ample headroom.
The PolarFire tick-rate compile-time check moved to `hwtimer.c`, next to
the `TICK_CYCLES` constant it guards, since it needed ThreadX headers
that a portable `main.c` should not have to pull in.
Verified locally on both targets: clean builds plus `test_renode.py`
green on Cortex-M4 and RV64. Both suites were also confirmed to still
have teeth - weakening each target's `_sbrk()` bound back to the end of
RAM makes them exit 1 (three failures on the NUCLEO, one on the
PolarFire), so no assertion was lost in the move.
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@fdesbiens
fdesbiens merged commit 92707e0 into eclipse-threadx:devAug 31, 2026
5 checks passed
@fdesbiens
fdesbiens deleted the feature/bsp-selftest-memory-interfaces branch August 31, 2026 20:17
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.

1 participant

@fdesbiens
, '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" + ' Moved the startup self-tests and RAM sizing behind new BSP interfaces by fdesbiens · Pull Request #56 · eclipse-threadx/samplex · GitHub
Skip to content

Moved the startup self-tests and RAM sizing behind new BSP interfaces - #56

Merged
fdesbiens merged 1 commit into
eclipse-threadx:devfrom
fdesbiens:feature/bsp-selftest-memory-interfaces
Aug 31, 2026
Merged

Moved the startup self-tests and RAM sizing behind new BSP interfaces#56
fdesbiens merged 1 commit into
eclipse-threadx:devfrom
fdesbiens:feature/bsp-selftest-memory-interfaces

Conversation

@fdesbiens

Copy link
Copy Markdown
Contributor

Why

The framework's stated purpose is that application code can be built for any board that implements the bsp/ contracts. Neither demo could be. Both main.c files reached past those contracts for two things no portable application can see: the board's memory extents, and the hardware specifics its startup self-tests assert on.

Neither was ever application logic. The self-tests test the BSP — linker reservations, clock trees, interrupt controllers — so they belong in the BSP. Moving them takes most of the contamination in main.c with them.

What

Two new contracts in bsp/include/bsp/:

  • selftest.hbsp_self_test(report, context) runs the board's checks and reports each through an application-supplied callback, returning the failure count. Message formatting — and therefore the choice between printf() and bsp_console_write() — stays with the application while the checks stay with the board.
  • memory.hbsp_ram_region(first_unused, &base, &size) reports what RAM the application may claim, clamped against whatever the board reserves above ThreadX's first unused address.

Both targets implement both. Both main.c files now include nothing but C standard headers, tx_api.h and <bsp/...>. The NUCLEO byte pool comes out byte-identical at 88204 bytes, so there is no behaviour change on that target.

The target template and docs/architecture.md gained the two interfaces, and the template ships bsp_memory.c / bsp_selftest.c stubs so a new board has something to fill in. Neither restores the portability claim that #52 walked back — there is still no apps/ directory, and that stays true until CI builds one for two architectures.

One scope call worth flagging

Prototyping bsp_ram_region() on the PolarFire first — as the design intended — surfaced a latent bug there. Its _sbrk() bounded the heap at the end of DRAM, which is the same mistake the NUCLEO shipped with before #50. On the PolarFire it was harmless only because nothing else claimed that memory: the LM75 demo uses static stacks. The moment bsp_ram_region() promises that DRAM to an application, one oversized malloc() could take memory holding thread stacks.

Shipping the interface without fixing that would have made it a trap, so this PR also bounds the PolarFire heap against a documented 64 KB reservation that bsp_ram_region() skips, and adds a self-test guarding the bound the way the NUCLEO's test 4 does. Measured heap use on that target is under 256 bytes, so the reservation has roughly 256x headroom.

The PolarFire tick-rate compile-time check moved to hwtimer.c, next to the TICK_CYCLES constant it guards, because it needs ThreadX headers a portable main.c should not have to pull in.

Verification

Built and Renode-tested locally on both architectures with the CI-pinned toolchains (Arm GNU 14.3.Rel1, xPack RISC-V GCC 14.3.0-1, Renode 1.16.1):

TargetBuildtest_renode.py
NUCLEO-F401RE (Cortex-M4)clean, no warningsexit 0, 8/8 self-tests pass
PolarFire Icicle (RV64)clean apart from the known pre-existing RWX LOAD-segment warningexit 0, 7/7 self-tests pass

Both suites were also confirmed to have kept their teeth after the move. Weakening each target's _sbrk() bound back to the end of RAM makes them exit 1 — three failures on the NUCLEO (matching what was documented when they were first written), one on the PolarFire's new guard — so no assertion was silently lost in the refactor.

Notes

  • New self-test coverage: bsp_ram_region()'s invariant on both targets, and the PolarFire heap bound.
  • This is the first of four steps toward closing the portability gap, and a coherent resting place on its own: BSP tests belong in the BSP, and vendor headers are out of both main.c files regardless of whether a shared apps/ ever lands.

The framework's stated purpose is that application code can be built for
any board implementing the `bsp/` contracts. Neither demo could be,
because both `main.c` files reached past those contracts for two things
no portable application can see: the board's memory extents, and the
hardware specifics its startup self-tests assert on.
Neither was ever application logic. The self-tests test the BSP - linker
reservations, clock trees, interrupt controllers - so they belong in the
BSP. Two new contracts move both behind the boundary:
* `bsp/selftest.h` - `bsp_self_test()` runs the board's checks and
reports each through an application-supplied callback, so message
formatting (and therefore the choice between `printf()` and
`bsp_console_write()`) stays with the application while the checks stay
with the board.
* `bsp/memory.h` - `bsp_ram_region()` reports what RAM the application
may claim, clamped against whatever the board reserves.
Both targets implement both, and both `main.c` files now include nothing
but the C standard headers, `tx_api.h` and `<bsp/...>`. The NUCLEO byte
pool comes out byte-identical at 88204 bytes.
Prototyping `bsp_ram_region()` on the PolarFire first surfaced a latent
bug there. Its `_sbrk()` bounded the heap at the end of DRAM, which is
the same mistake the NUCLEO shipped with before eclipse-threadx#50: harmless only
because nothing else claimed that memory. The moment `bsp_ram_region()`
promises it to an application, an oversized `malloc()` could take memory
holding thread stacks. The heap is now bounded against a documented 64 KB
reservation that `bsp_ram_region()` skips, and a new self-test guards the
bound the way the NUCLEO's test 4 does. Measured heap use is under 256
bytes, so the reservation has ample headroom.
The PolarFire tick-rate compile-time check moved to `hwtimer.c`, next to
the `TICK_CYCLES` constant it guards, since it needed ThreadX headers
that a portable `main.c` should not have to pull in.
Verified locally on both targets: clean builds plus `test_renode.py`
green on Cortex-M4 and RV64. Both suites were also confirmed to still
have teeth - weakening each target's `_sbrk()` bound back to the end of
RAM makes them exit 1 (three failures on the NUCLEO, one on the
PolarFire), so no assertion was lost in the move.
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@fdesbiens
fdesbiens merged commit 92707e0 into eclipse-threadx:devAug 31, 2026
5 checks passed
@fdesbiens
fdesbiens deleted the feature/bsp-selftest-memory-interfaces branch August 31, 2026 20:17
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.

1 participant

@fdesbiens
, '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('^' + ".*" + ' Moved the startup self-tests and RAM sizing behind new BSP interfaces by fdesbiens · Pull Request #56 · eclipse-threadx/samplex · GitHub
Skip to content

Moved the startup self-tests and RAM sizing behind new BSP interfaces - #56

Merged
fdesbiens merged 1 commit into
eclipse-threadx:devfrom
fdesbiens:feature/bsp-selftest-memory-interfaces
Aug 31, 2026
Merged

Moved the startup self-tests and RAM sizing behind new BSP interfaces#56
fdesbiens merged 1 commit into
eclipse-threadx:devfrom
fdesbiens:feature/bsp-selftest-memory-interfaces

Conversation

@fdesbiens

Copy link
Copy Markdown
Contributor

Why

The framework's stated purpose is that application code can be built for any board that implements the bsp/ contracts. Neither demo could be. Both main.c files reached past those contracts for two things no portable application can see: the board's memory extents, and the hardware specifics its startup self-tests assert on.

Neither was ever application logic. The self-tests test the BSP — linker reservations, clock trees, interrupt controllers — so they belong in the BSP. Moving them takes most of the contamination in main.c with them.

What

Two new contracts in bsp/include/bsp/:

  • selftest.hbsp_self_test(report, context) runs the board's checks and reports each through an application-supplied callback, returning the failure count. Message formatting — and therefore the choice between printf() and bsp_console_write() — stays with the application while the checks stay with the board.
  • memory.hbsp_ram_region(first_unused, &base, &size) reports what RAM the application may claim, clamped against whatever the board reserves above ThreadX's first unused address.

Both targets implement both. Both main.c files now include nothing but C standard headers, tx_api.h and <bsp/...>. The NUCLEO byte pool comes out byte-identical at 88204 bytes, so there is no behaviour change on that target.

The target template and docs/architecture.md gained the two interfaces, and the template ships bsp_memory.c / bsp_selftest.c stubs so a new board has something to fill in. Neither restores the portability claim that #52 walked back — there is still no apps/ directory, and that stays true until CI builds one for two architectures.

One scope call worth flagging

Prototyping bsp_ram_region() on the PolarFire first — as the design intended — surfaced a latent bug there. Its _sbrk() bounded the heap at the end of DRAM, which is the same mistake the NUCLEO shipped with before #50. On the PolarFire it was harmless only because nothing else claimed that memory: the LM75 demo uses static stacks. The moment bsp_ram_region() promises that DRAM to an application, one oversized malloc() could take memory holding thread stacks.

Shipping the interface without fixing that would have made it a trap, so this PR also bounds the PolarFire heap against a documented 64 KB reservation that bsp_ram_region() skips, and adds a self-test guarding the bound the way the NUCLEO's test 4 does. Measured heap use on that target is under 256 bytes, so the reservation has roughly 256x headroom.

The PolarFire tick-rate compile-time check moved to hwtimer.c, next to the TICK_CYCLES constant it guards, because it needs ThreadX headers a portable main.c should not have to pull in.

Verification

Built and Renode-tested locally on both architectures with the CI-pinned toolchains (Arm GNU 14.3.Rel1, xPack RISC-V GCC 14.3.0-1, Renode 1.16.1):

TargetBuildtest_renode.py
NUCLEO-F401RE (Cortex-M4)clean, no warningsexit 0, 8/8 self-tests pass
PolarFire Icicle (RV64)clean apart from the known pre-existing RWX LOAD-segment warningexit 0, 7/7 self-tests pass

Both suites were also confirmed to have kept their teeth after the move. Weakening each target's _sbrk() bound back to the end of RAM makes them exit 1 — three failures on the NUCLEO (matching what was documented when they were first written), one on the PolarFire's new guard — so no assertion was silently lost in the refactor.

Notes

  • New self-test coverage: bsp_ram_region()'s invariant on both targets, and the PolarFire heap bound.
  • This is the first of four steps toward closing the portability gap, and a coherent resting place on its own: BSP tests belong in the BSP, and vendor headers are out of both main.c files regardless of whether a shared apps/ ever lands.

The framework's stated purpose is that application code can be built for
any board implementing the `bsp/` contracts. Neither demo could be,
because both `main.c` files reached past those contracts for two things
no portable application can see: the board's memory extents, and the
hardware specifics its startup self-tests assert on.
Neither was ever application logic. The self-tests test the BSP - linker
reservations, clock trees, interrupt controllers - so they belong in the
BSP. Two new contracts move both behind the boundary:
* `bsp/selftest.h` - `bsp_self_test()` runs the board's checks and
reports each through an application-supplied callback, so message
formatting (and therefore the choice between `printf()` and
`bsp_console_write()`) stays with the application while the checks stay
with the board.
* `bsp/memory.h` - `bsp_ram_region()` reports what RAM the application
may claim, clamped against whatever the board reserves.
Both targets implement both, and both `main.c` files now include nothing
but the C standard headers, `tx_api.h` and `<bsp/...>`. The NUCLEO byte
pool comes out byte-identical at 88204 bytes.
Prototyping `bsp_ram_region()` on the PolarFire first surfaced a latent
bug there. Its `_sbrk()` bounded the heap at the end of DRAM, which is
the same mistake the NUCLEO shipped with before eclipse-threadx#50: harmless only
because nothing else claimed that memory. The moment `bsp_ram_region()`
promises it to an application, an oversized `malloc()` could take memory
holding thread stacks. The heap is now bounded against a documented 64 KB
reservation that `bsp_ram_region()` skips, and a new self-test guards the
bound the way the NUCLEO's test 4 does. Measured heap use is under 256
bytes, so the reservation has ample headroom.
The PolarFire tick-rate compile-time check moved to `hwtimer.c`, next to
the `TICK_CYCLES` constant it guards, since it needed ThreadX headers
that a portable `main.c` should not have to pull in.
Verified locally on both targets: clean builds plus `test_renode.py`
green on Cortex-M4 and RV64. Both suites were also confirmed to still
have teeth - weakening each target's `_sbrk()` bound back to the end of
RAM makes them exit 1 (three failures on the NUCLEO, one on the
PolarFire), so no assertion was lost in the move.
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@fdesbiens
fdesbiens merged commit 92707e0 into eclipse-threadx:devAug 31, 2026
5 checks passed
@fdesbiens
fdesbiens deleted the feature/bsp-selftest-memory-interfaces branch August 31, 2026 20:17
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.

1 participant

@fdesbiens
, '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); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Moved the startup self-tests and RAM sizing behind new BSP interfaces by fdesbiens · Pull Request #56 · eclipse-threadx/samplex · GitHub
Skip to content

Moved the startup self-tests and RAM sizing behind new BSP interfaces - #56

Merged
fdesbiens merged 1 commit into
eclipse-threadx:devfrom
fdesbiens:feature/bsp-selftest-memory-interfaces
Aug 31, 2026
Merged

Moved the startup self-tests and RAM sizing behind new BSP interfaces#56
fdesbiens merged 1 commit into
eclipse-threadx:devfrom
fdesbiens:feature/bsp-selftest-memory-interfaces

Conversation

@fdesbiens

Copy link
Copy Markdown
Contributor

Why

The framework's stated purpose is that application code can be built for any board that implements the bsp/ contracts. Neither demo could be. Both main.c files reached past those contracts for two things no portable application can see: the board's memory extents, and the hardware specifics its startup self-tests assert on.

Neither was ever application logic. The self-tests test the BSP — linker reservations, clock trees, interrupt controllers — so they belong in the BSP. Moving them takes most of the contamination in main.c with them.

What

Two new contracts in bsp/include/bsp/:

  • selftest.hbsp_self_test(report, context) runs the board's checks and reports each through an application-supplied callback, returning the failure count. Message formatting — and therefore the choice between printf() and bsp_console_write() — stays with the application while the checks stay with the board.
  • memory.hbsp_ram_region(first_unused, &base, &size) reports what RAM the application may claim, clamped against whatever the board reserves above ThreadX's first unused address.

Both targets implement both. Both main.c files now include nothing but C standard headers, tx_api.h and <bsp/...>. The NUCLEO byte pool comes out byte-identical at 88204 bytes, so there is no behaviour change on that target.

The target template and docs/architecture.md gained the two interfaces, and the template ships bsp_memory.c / bsp_selftest.c stubs so a new board has something to fill in. Neither restores the portability claim that #52 walked back — there is still no apps/ directory, and that stays true until CI builds one for two architectures.

One scope call worth flagging

Prototyping bsp_ram_region() on the PolarFire first — as the design intended — surfaced a latent bug there. Its _sbrk() bounded the heap at the end of DRAM, which is the same mistake the NUCLEO shipped with before #50. On the PolarFire it was harmless only because nothing else claimed that memory: the LM75 demo uses static stacks. The moment bsp_ram_region() promises that DRAM to an application, one oversized malloc() could take memory holding thread stacks.

Shipping the interface without fixing that would have made it a trap, so this PR also bounds the PolarFire heap against a documented 64 KB reservation that bsp_ram_region() skips, and adds a self-test guarding the bound the way the NUCLEO's test 4 does. Measured heap use on that target is under 256 bytes, so the reservation has roughly 256x headroom.

The PolarFire tick-rate compile-time check moved to hwtimer.c, next to the TICK_CYCLES constant it guards, because it needs ThreadX headers a portable main.c should not have to pull in.

Verification

Built and Renode-tested locally on both architectures with the CI-pinned toolchains (Arm GNU 14.3.Rel1, xPack RISC-V GCC 14.3.0-1, Renode 1.16.1):

TargetBuildtest_renode.py
NUCLEO-F401RE (Cortex-M4)clean, no warningsexit 0, 8/8 self-tests pass
PolarFire Icicle (RV64)clean apart from the known pre-existing RWX LOAD-segment warningexit 0, 7/7 self-tests pass

Both suites were also confirmed to have kept their teeth after the move. Weakening each target's _sbrk() bound back to the end of RAM makes them exit 1 — three failures on the NUCLEO (matching what was documented when they were first written), one on the PolarFire's new guard — so no assertion was silently lost in the refactor.

Notes

  • New self-test coverage: bsp_ram_region()'s invariant on both targets, and the PolarFire heap bound.
  • This is the first of four steps toward closing the portability gap, and a coherent resting place on its own: BSP tests belong in the BSP, and vendor headers are out of both main.c files regardless of whether a shared apps/ ever lands.

The framework's stated purpose is that application code can be built for
any board implementing the `bsp/` contracts. Neither demo could be,
because both `main.c` files reached past those contracts for two things
no portable application can see: the board's memory extents, and the
hardware specifics its startup self-tests assert on.
Neither was ever application logic. The self-tests test the BSP - linker
reservations, clock trees, interrupt controllers - so they belong in the
BSP. Two new contracts move both behind the boundary:
* `bsp/selftest.h` - `bsp_self_test()` runs the board's checks and
reports each through an application-supplied callback, so message
formatting (and therefore the choice between `printf()` and
`bsp_console_write()`) stays with the application while the checks stay
with the board.
* `bsp/memory.h` - `bsp_ram_region()` reports what RAM the application
may claim, clamped against whatever the board reserves.
Both targets implement both, and both `main.c` files now include nothing
but the C standard headers, `tx_api.h` and `<bsp/...>`. The NUCLEO byte
pool comes out byte-identical at 88204 bytes.
Prototyping `bsp_ram_region()` on the PolarFire first surfaced a latent
bug there. Its `_sbrk()` bounded the heap at the end of DRAM, which is
the same mistake the NUCLEO shipped with before eclipse-threadx#50: harmless only
because nothing else claimed that memory. The moment `bsp_ram_region()`
promises it to an application, an oversized `malloc()` could take memory
holding thread stacks. The heap is now bounded against a documented 64 KB
reservation that `bsp_ram_region()` skips, and a new self-test guards the
bound the way the NUCLEO's test 4 does. Measured heap use is under 256
bytes, so the reservation has ample headroom.
The PolarFire tick-rate compile-time check moved to `hwtimer.c`, next to
the `TICK_CYCLES` constant it guards, since it needed ThreadX headers
that a portable `main.c` should not have to pull in.
Verified locally on both targets: clean builds plus `test_renode.py`
green on Cortex-M4 and RV64. Both suites were also confirmed to still
have teeth - weakening each target's `_sbrk()` bound back to the end of
RAM makes them exit 1 (three failures on the NUCLEO, one on the
PolarFire), so no assertion was lost in the move.
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@fdesbiens
fdesbiens merged commit 92707e0 into eclipse-threadx:devAug 31, 2026
5 checks passed
@fdesbiens
fdesbiens deleted the feature/bsp-selftest-memory-interfaces branch August 31, 2026 20:17
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.

1 participant

@fdesbiens
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Moved the startup self-tests and RAM sizing behind new BSP interfaces by fdesbiens · Pull Request #56 · eclipse-threadx/samplex · GitHub
Skip to content

Moved the startup self-tests and RAM sizing behind new BSP interfaces - #56

Merged
fdesbiens merged 1 commit into
eclipse-threadx:devfrom
fdesbiens:feature/bsp-selftest-memory-interfaces
Aug 31, 2026
Merged

Moved the startup self-tests and RAM sizing behind new BSP interfaces#56
fdesbiens merged 1 commit into
eclipse-threadx:devfrom
fdesbiens:feature/bsp-selftest-memory-interfaces

Conversation

@fdesbiens

Copy link
Copy Markdown
Contributor

Why

The framework's stated purpose is that application code can be built for any board that implements the bsp/ contracts. Neither demo could be. Both main.c files reached past those contracts for two things no portable application can see: the board's memory extents, and the hardware specifics its startup self-tests assert on.

Neither was ever application logic. The self-tests test the BSP — linker reservations, clock trees, interrupt controllers — so they belong in the BSP. Moving them takes most of the contamination in main.c with them.

What

Two new contracts in bsp/include/bsp/:

  • selftest.hbsp_self_test(report, context) runs the board's checks and reports each through an application-supplied callback, returning the failure count. Message formatting — and therefore the choice between printf() and bsp_console_write() — stays with the application while the checks stay with the board.
  • memory.hbsp_ram_region(first_unused, &base, &size) reports what RAM the application may claim, clamped against whatever the board reserves above ThreadX's first unused address.

Both targets implement both. Both main.c files now include nothing but C standard headers, tx_api.h and <bsp/...>. The NUCLEO byte pool comes out byte-identical at 88204 bytes, so there is no behaviour change on that target.

The target template and docs/architecture.md gained the two interfaces, and the template ships bsp_memory.c / bsp_selftest.c stubs so a new board has something to fill in. Neither restores the portability claim that #52 walked back — there is still no apps/ directory, and that stays true until CI builds one for two architectures.

One scope call worth flagging

Prototyping bsp_ram_region() on the PolarFire first — as the design intended — surfaced a latent bug there. Its _sbrk() bounded the heap at the end of DRAM, which is the same mistake the NUCLEO shipped with before #50. On the PolarFire it was harmless only because nothing else claimed that memory: the LM75 demo uses static stacks. The moment bsp_ram_region() promises that DRAM to an application, one oversized malloc() could take memory holding thread stacks.

Shipping the interface without fixing that would have made it a trap, so this PR also bounds the PolarFire heap against a documented 64 KB reservation that bsp_ram_region() skips, and adds a self-test guarding the bound the way the NUCLEO's test 4 does. Measured heap use on that target is under 256 bytes, so the reservation has roughly 256x headroom.

The PolarFire tick-rate compile-time check moved to hwtimer.c, next to the TICK_CYCLES constant it guards, because it needs ThreadX headers a portable main.c should not have to pull in.

Verification

Built and Renode-tested locally on both architectures with the CI-pinned toolchains (Arm GNU 14.3.Rel1, xPack RISC-V GCC 14.3.0-1, Renode 1.16.1):

TargetBuildtest_renode.py
NUCLEO-F401RE (Cortex-M4)clean, no warningsexit 0, 8/8 self-tests pass
PolarFire Icicle (RV64)clean apart from the known pre-existing RWX LOAD-segment warningexit 0, 7/7 self-tests pass

Both suites were also confirmed to have kept their teeth after the move. Weakening each target's _sbrk() bound back to the end of RAM makes them exit 1 — three failures on the NUCLEO (matching what was documented when they were first written), one on the PolarFire's new guard — so no assertion was silently lost in the refactor.

Notes

  • New self-test coverage: bsp_ram_region()'s invariant on both targets, and the PolarFire heap bound.
  • This is the first of four steps toward closing the portability gap, and a coherent resting place on its own: BSP tests belong in the BSP, and vendor headers are out of both main.c files regardless of whether a shared apps/ ever lands.

The framework's stated purpose is that application code can be built for
any board implementing the `bsp/` contracts. Neither demo could be,
because both `main.c` files reached past those contracts for two things
no portable application can see: the board's memory extents, and the
hardware specifics its startup self-tests assert on.
Neither was ever application logic. The self-tests test the BSP - linker
reservations, clock trees, interrupt controllers - so they belong in the
BSP. Two new contracts move both behind the boundary:
* `bsp/selftest.h` - `bsp_self_test()` runs the board's checks and
reports each through an application-supplied callback, so message
formatting (and therefore the choice between `printf()` and
`bsp_console_write()`) stays with the application while the checks stay
with the board.
* `bsp/memory.h` - `bsp_ram_region()` reports what RAM the application
may claim, clamped against whatever the board reserves.
Both targets implement both, and both `main.c` files now include nothing
but the C standard headers, `tx_api.h` and `<bsp/...>`. The NUCLEO byte
pool comes out byte-identical at 88204 bytes.
Prototyping `bsp_ram_region()` on the PolarFire first surfaced a latent
bug there. Its `_sbrk()` bounded the heap at the end of DRAM, which is
the same mistake the NUCLEO shipped with before eclipse-threadx#50: harmless only
because nothing else claimed that memory. The moment `bsp_ram_region()`
promises it to an application, an oversized `malloc()` could take memory
holding thread stacks. The heap is now bounded against a documented 64 KB
reservation that `bsp_ram_region()` skips, and a new self-test guards the
bound the way the NUCLEO's test 4 does. Measured heap use is under 256
bytes, so the reservation has ample headroom.
The PolarFire tick-rate compile-time check moved to `hwtimer.c`, next to
the `TICK_CYCLES` constant it guards, since it needed ThreadX headers
that a portable `main.c` should not have to pull in.
Verified locally on both targets: clean builds plus `test_renode.py`
green on Cortex-M4 and RV64. Both suites were also confirmed to still
have teeth - weakening each target's `_sbrk()` bound back to the end of
RAM makes them exit 1 (three failures on the NUCLEO, one on the
PolarFire), so no assertion was lost in the move.
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@fdesbiens
fdesbiens merged commit 92707e0 into eclipse-threadx:devAug 31, 2026
5 checks passed
@fdesbiens
fdesbiens deleted the feature/bsp-selftest-memory-interfaces branch August 31, 2026 20:17
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.

1 participant

@fdesbiens