Skip to content

fix(psram): keep libgcc in the SRAM bootstrap - #26

Open
gtasseff wants to merge 1 commit into
masterfrom
fix/psram-bootstrap-libgcc
Open

gtasseff wants to merge 1 commit into
masterfrom
fix/psram-bootstrap-libgcc

Conversation

@gtasseff

@gtasseff gtasseff commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

In short: a PSRAM app spends a brief moment executing code that lives on the
PSRAM chip while that chip's timing settings are being changed. It works today,
but nothing guarantees it will, and the fix is a one-line addition to a list
that already exists for exactly this purpose.

There is no bug to reproduce. No crash, no hang, no visible symptom. This
was found by reading the linked image, not by anything failing. An app built
with fw2_psram_app() boots and runs correctly with and without this change,
which was confirmed on hardware. Treat it as hardening, not a defect report.

Why the moment exists

board_init_psram() runs from SRAM and does, in order:

  1. raise clk_sys
  2. clock_configure()
  3. psram_configure_params()
  4. psram_reinitialize() — re-times the QMI for the new clock

Between step 1 and step 4 the PSRAM timing is set for the old clock. A PSRAM app
is fetching its own instructions from that chip, so any code running in that gap
must live in SRAM instead. That is what .sram_bootstrap is for, and why
clocks.c, pll.c, vreg.c and psram.c are already listed there.

What is missing from the list

libgcc. Steps 2 and 3 both divide 64-bit values, which the compiler turns into a
call into libgcc. Those helpers were left in PSRAM, so they are fetched through
the window being reconfigured:

clock_configure                 0x2000597c   SRAM
  bl ____aeabi_uldivmod_veneer  0x20007dc0   SRAM
     ldr pc, [pc]            -> 0x1101bfcd   PSRAM

This is easy to miss by reading the code, because the branch target is a veneer
that is itself in SRAM. Only following the branch through the veneer shows that
the real destination is in PSRAM.

The change

One entry added to the .sram_bootstrap section list.

Effect

Measured on an external app built with fw2_psram_app():

symbol before after
__aeabi_uldivmod 0x1101bfcc (PSRAM) 0x20004524 (SRAM)
__udivmoddi4 0x1101bffc (PSRAM) 0x20004554 (SRAM)

Cost is 1,936 bytes of SRAM. That app boots and runs unchanged with the entry in
place: bootstrap and constructors complete, the audio codec input path comes up,
and capture holds its normal block rate.

hello_psram_exec still passes its layout check, and tests/ plus
tools/tests/ pass (87 tests).

Why it is worth taking anyway

The current behaviour depends on the QMI timing inherited from the loader stub
staying readable at 250 MHz. That happens to hold, but it is not something the
section list relies on anywhere else — every other entry is there precisely so
that no assumption about the stale window is needed. This makes libgcc
consistent with them.

board_init_psram() runs from .sram_bootstrap and raises clk_sys before
psram_reinitialize() re-times the QMI for the new clock. Between those two
points the PSRAM window is live but mis-timed, and a PSRAM app is fetching its
instructions through it.

clock_configure() and psram_configure_params() both execute in that window and
both divide 64-bit values, which compiles to a call into libgcc. libgcc was not
listed in .sram_bootstrap, so __aeabi_uldivmod and __udivmoddi4 were placed in
PSRAM and reached through a long-branch veneer:

    clock_configure         (0x2000597c, SRAM)
      bl ____aeabi_uldivmod_veneer  (0x20007dc0, SRAM)
        ldr pc, [pc]        -> 0x1101bfcd  (PSRAM)

Auditing the direct callees does not reveal this, because the veneer itself is
SRAM-resident; only following the branch through it does.

Adding libgcc to the section moves both helpers into SRAM. Measured on an
external app using fw2_psram_app(): __aeabi_uldivmod 0x1101bfcc -> 0x20004524,
at a cost of 1,936 bytes of SRAM. hello_psram_exec still passes its layout
check.

This is a latent fault rather than an observed failure: the inherited QMI timing
from the loader stub remains readable at 250 MHz, so the fetch currently
succeeds. It is not guaranteed to across parts or clock changes, which is the
same reasoning that already keeps clocks.c, pll.c, vreg.c and psram.c here.
Sign up for free to 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