Skip to content

sunxi-fel: handle H616/A133 secure-FEL handoff - #236

Open
jameshilliard wants to merge 2 commits into
linux-sunxi:masterfrom
jameshilliard:h616-secure-fel
Open

jameshilliard wants to merge 2 commits into
linux-sunxi:masterfrom
jameshilliard:h616-secure-fel

Conversation

@jameshilliard

@jameshilliard jameshilliard commented May 15, 2026

Copy link
Copy Markdown
Contributor

H616 and A133 enter FEL in non-secure SVC when secure boot is enabled. Secure register access and the later AArch64 handoff require a transition to secure SVC that leaves the BROM FEL command loop usable.

This series contains two commits:

  1. sunxi-fel: factor SMC workaround handling separates detection from execution, introduces explicit workaround selection in SoC data, and renames the restricted-memory probe field to smc_workaround_probe_addr. Existing direct-SMC behavior is unchanged.
  2. sunxi-fel: handle H616/A133 secure-FEL handoff adds the secure-SVC transition for these SoCs using an annotated arm_code[] array in fel.c.

Detection

The workaround first checks the SID controller's secure-boot status word at sid_base + 0xa0. A zero value skips the workaround. This register describes secure-boot configuration, not the CPU's current security state.

The runtime state probe reads EFUSE_LCJS at 0x03006248, corresponding to efuse offset 0x48. Bit 11 is the secure-boot fuse, so this word is nonzero in the secure view on a secure-boot device. The restricted non-secure read returns zero. Once the transition succeeds, subsequent FEL invocations see the nonzero value and skip the workaround. Both checks are read-only; no fuses are programmed.

Secure-SVC Transition

NBROM can clear the monitor vectors while leaving MVBAR pointing to SRAM. On H616/A133, the known vector table address is 0x300c0, and that SRAM is writable from non-secure FEL. The code uses the address supplied in SoC data; it does not need to read or write MVBAR from non-secure state.

  1. Preserve the FEL return address in r12, save the original SMC vector word, and temporarily replace the word at MVBAR + 8 with mov pc, lr.
  2. Execute smc #0. The patched vector branches to the instruction after the SMC without restoring CPSR from SPSR_mon, leaving execution in Monitor mode.
  3. Restore the original vector word, clear SCR, and set GICC_CTLR.AckCtl so the unchanged BROM IRQ handler can acknowledge Group 1 interrupts through secure GICC_IAR.
  4. Return directly to FEL with movs pc, r12, restoring CPSR from SPSR_mon and entering secure SVC.

SVC SP/LR, MVBAR, the GIC distributor, and the priority mask remain untouched. The two DSBs complete the vector write before SMC and the vector restoration/GIC update before returning. The sequence assumes the BROM's ARM-state, MMU/cache-disabled FEL entry.

The uploaded payload is 76 bytes, including its two address parameters. Comments explain each operation and the A32 PC + 8 literal-load calculations. No separate assembly source, generated header, SRAM swapping, or build-system changes are required.

The workaround runs at startup, not specifically during SID reads or SPL loading. Those paths remain unchanged, and the secure state persists across subsequent FEL commands and separate host invocations. TOC0 loading and USB transfer optimizations are outside this series.

Validation

  • H616: repeated cold boots, full SID reads, separate FEL invocations, and a complete SPL/U-Boot/FIT boot with successful image hash verification.
  • A133: smaeul confirmed that the reduced implementation in a95fdd4 works. The subsequent review changes only rename the probe field and improve comments; the 76-byte payload is byte-for-byte unchanged.

@apritzel apritzel left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This patch is massive (aka barely reviewable) and apparently combines code movement/refactoring and new features, which is not a good idea.
Please split this up to refactor the code first, then apply the changes required for the H616.
Also the detection doesn't work: on my non-secure boards, 0x03006240 also reads as zero, which triggers the secure code and hangs. Can you try to read the secure fuse word, and maybe filter for that bit?
And why does the secure workaround now only triggers for the SID read? And what does it have to do with the SPL? The smc #0 before was just triggered once, on the first sunxi-fel command, then the rest just fell in place. I am still hopeful something similar is possible on the newer SoCs, without having explicit entry and exit code.
At the very least this should be explained in detail in the commit message or in comments.
That's just what I found after 5 minutes into the patch, but as mentioned, it's very hard to comprehend. Please tell your LLM to make this understandable and reviewable by human beings.

@jameshilliard
jameshilliard force-pushed the h616-secure-fel branch 2 times, most recently from ab40817 to ebd5305 Compare May 16, 2026 19:34
@jameshilliard

jameshilliard commented May 16, 2026

Copy link
Copy Markdown
Contributor Author

The patch is now a single commit:

sunxi-fel: handle H616 secure-FEL handoff

The generic SPL path is left alone. thunks/fel-to-spl-thunk.S and the
generated thunks/fel-to-spl-thunk.h are unchanged from master, and SPL
loading continues through the existing SPL code path.

The H616-specific part is limited to the secure-FEL startup transition and
the SoC data used to select it. H616 now checks both:

  • the secure boot status word at SID + 0xa0 (0x030060a0) with mask 0xf
  • the existing zero-word probe at 0x03006240

That avoids triggering the secure path on non-secure H616 boards where the
zero-word probe can also read as zero.

The SMC workaround method is explicit in SoC data:

  • existing secure-FEL users select SMC_WORKAROUND_DIRECT_SMC
  • H616 selects SMC_WORKAROUND_SECURE_SVC_THUNK

H616 still follows the global startup model: sunxi-fel applies the
workaround after opening the FEL device and before processing commands. It is
not tied to SID or SPL. The SID path is only one validation case because the
protected SID words are not readable until the transition succeeds.

The difference from older SoCs is the transition mechanism. Older SoCs can
execute smc #0; bx lr directly. On H616, smc #0 returns through monitor
mode, so the direct sequence does not leave the BROM FEL command loop in the
secure-SVC state it expects.

The H616 thunk issues the SMC, switches from monitor mode to secure SVC with
the banked SP/LR restored, restores the secure GIC view expected by the BROM,
and returns to the FEL command loop. It uses the same SRAM swap-table
convention as the SPL thunk to preserve the H616 BROM SRAM workspace while
the uploaded code runs and returns to FEL. After the transition, the normal
runtime probe sees secure state and suppresses repeat application in that
sunxi-fel process, so normal SID reads and SPL execution use the existing
code paths.

The new thunk header is added to the top-level sunxi-fel dependencies, and
thunks/Makefile can regenerate it with the same old-style header format used
by fel-to-spl-thunk.h.

Validation on the H616 board:

./sunxi-fel -v sid sid-dump ver

That applied the secure-SVC return thunk once in the sunxi-fel process,
printed the protected SID words, and then ver still worked.

I also tested SPL loading with the separate TOC0 loader changes in my working
tree:

./sunxi-fel -v -p uboot u-boot-sunxi-with-spl.bin

That applied the secure-SVC return thunk before TOC0 parsing, wrapped the TOC0
SPL item, executed the normal SPL thunk, returned from SPL to FEL, loaded
ATF/U-Boot/DTB, and started U-Boot. The TOC0 loader itself remains separate
from this secure-FEL patch.

@jameshilliard
jameshilliard force-pushed the h616-secure-fel branch 2 times, most recently from 7dae1f5 to 486caf2 Compare May 17, 2026 02:08
@jameshilliard
jameshilliard requested a review from apritzel May 17, 2026 18:13
@jameshilliard
jameshilliard force-pushed the h616-secure-fel branch 5 times, most recently from ced88c1 to 681257a Compare May 24, 2026 04:12
Comment thread soc_info.c Outdated
.rvbar_reg_alt= 0x08100040,
.ver_reg = 0x03000024,
.needs_smc_workaround_if_zero_word_at_addr = 0x03006240,
.secure_boot_fuse_addr = 0x030060a0,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be an offset from .sid_base (or even always sid_base + 0xa0).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. This is now encoded as an offset from sid_base; H616 uses secure_boot_fuse_offset = 0xa0.

Comment thread fel.c Outdated
return false;

if (!soc_info->needs_smc_workaround_if_zero_word_at_addr)
return true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we want to assume the workaround is needed if we don't have a way of distinguishing secure SVC from NS SVC, as we would apply the workaround on every sunxi-fel invocation.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. The secure boot fuse is now only a guard. The runtime zero-word probe still has to match before the workaround is applied, so a secure fuse alone cannot make every sunxi-fel invocation run the thunk.

Comment thread fel.c
aw_fel_read(dev, soc_info->secure_boot_fuse_addr,
&val, sizeof(val));
if (!(le32toh(val) & soc_info->secure_boot_fuse_mask))
return false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you ever seen a value other than 0 or 1 in 0x030060a0? I don't think this needs a mask; any nonzero value is secure.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. The mask is gone; any nonzero value in the secure boot status word is treated as secure boot.

@smaeul

smaeul commented Jun 5, 2026

Copy link
Copy Markdown

A133 puts the monitor vector table at 0x300c0 (in SRAM C), and then wipes SRAM A1 and SRAM C at the beginning of NBROM, so you have to copy the monitor mode code back to SRAM before you can do smc. If I copy the stock SMC handler from the SBROM, A133 will also need something similar to this for GIC setup (smc works on A133 if I do it a second time to toggle back to NS before returning to FEL). But we could also make a smarter SMC handler that does all of the setup at once. Since the whole SRAM should be writable on H616, we should be able to overwrite the SMC handler before calling it on H616 as well.

@jameshilliard
jameshilliard force-pushed the h616-secure-fel branch 2 times, most recently from 14eeace to e2f9995 Compare June 5, 2026 18:41
@jameshilliard

Copy link
Copy Markdown
Contributor Author

The updated version uses that smarter-handler approach for H616.

Instead of copying the stock SBROM SMC handler, the thunk installs a
temporary monitor-mode SMC handler by patching only the SMC vector word at
MVBAR + 8. The handler lives in the uploaded thunk, restores the original
vector word immediately, clears SCR.NS, clears MVBAR, sets up the secure
GIC view, copies the saved SVC SP/LR into the secure bank, switches to secure
SVC, and returns to the FEL command loop.

So this avoids replacing the whole monitor vector table on H616. The handler
parameters are in SoC data, so the same mechanism can be reused for A133-style
support later, but this patch only enables it for H616.

@smaeul

smaeul commented Jun 10, 2026

Copy link
Copy Markdown

Thanks for the updates! I can confirm this works on A133 as well, with the same settings, so the following change:

diff --git a/soc_info.c b/soc_info.c
index fa7b7e2..03b8dd6 100644
--- a/soc_info.c
+++ b/soc_info.c
@@ -766,8 +766,10 @@ soc_info_t soc_info_table[] = {
                        .brom_hook_shadow_addr = 0x0003c000,
                        .dma_max_len = FEL_RX_DMA_MAX_LEN,
                },
-               .needs_smc_workaround_if_zero_word_at_addr = 0x100004,
-               .smc_workaround = SMC_WORKAROUND_DIRECT_SMC,
+               .needs_smc_workaround_if_zero_word_at_addr = 0x03006240,
+               .secure_boot_fuse_offset = 0xa0,
+               .smc_workaround = SMC_WORKAROUND_SECURE_SVC_SMC_THUNK,
+               .monitor_smc_handler = &h616_monitor_smc_handler,
                .watchdog     = &wd_h6_compat,
        },{
                .swap_buffers = NULL /* End of the table */

@jameshilliard

Copy link
Copy Markdown
Contributor Author

Thanks for the updates! I can confirm this works on A133 as well, with the same settings, so the following change:

I re-based an applied this change.

@jameshilliard

Copy link
Copy Markdown
Contributor Author

apritzel: I don't understand the whole "installs a temporary SMC handler" part? How does it even do this, from non-secure?

apritzel: The solution should be simply: issue the SMC, fix up the CPU and GIC state to be compatible with what the BROM expects, then return to the BROM.

"Installs a temporary SMC handler" is imprecise wording. The thunk does not write MVBAR or any secure system register while running in non-secure SVC. The BROM has already set MVBAR to 0x300c0, and the SRAM containing that vector table is writable from FEL. The thunk temporarily replaces only the SMC vector instruction at MVBAR + 8 with a branch into the uploaded thunk.

When smc #0 is issued, the processor enters Monitor mode and takes that branch. The target is therefore already executing in Monitor mode when it:

  • restores the original SMC vector instruction;
  • clears SCR.NS and MVBAR;
  • sets the secure GICC/GICD state expected by the BROM;
  • copies the saved FEL SP and LR into the secure-SVC bank; and
  • switches to secure SVC, restores the displaced FEL SRAM and call frame, and returns to the BROM FEL command loop.

So the resulting transition is the sequence you describe: issue the SMC, fix the CPU and GIC state, and return to the BROM in secure SVC. The temporary vector redirect only controls where execution begins after the SMC.

On H616 alone, the fixups could follow smc #0 directly because its stock SMC path was observed to return after the instruction in Monitor mode (CPSR.M = 0x16). A133 is the reason for making the Monitor entry explicit: its NBROM clears the SRAM containing the monitor vectors while leaving MVBAR = 0x300c0, so a valid SMC vector must be restored or redirected before issuing the SMC. The temporary redirect provides the same deterministic entry path for both SoCs and is removed immediately after it is taken.

@jameshilliard jameshilliard changed the title sunxi-fel: handle H616 secure-FEL SPL handoff sunxi-fel: handle H616/A133 secure-FEL handoff Sep 3, 2026
@jameshilliard

Copy link
Copy Markdown
Contributor Author

apritzel: I did some experiments yesterday, and think I found a much shorter solution for the "exception handler" problem: just put a mov pc, lr where MVBAR + 8 points, which makes the SMC immediately return, now in monitor mode of course.

Yes, that is sufficient and substantially simpler. mov pc, lr branches to LR_mon without restoring SPSR_mon, so execution resumes at the instruction after smc #0 while remaining in Monitor mode. The thunk can then restore the original vector word and perform the SCR, MVBAR, GIC, and banked SVC SP/LR fixups inline before switching to secure SVC and returning to FEL.

The vector patch does not require privileged access: MVBAR already points to 0x300c0, and non-secure FEL can write the SRAM word at MVBAR + 8. The thunk saves the original word, writes the mov pc, lr instruction, and executes DSB/ISB before issuing the SMC. It also saves the non-secure SVC SP/LR in unbanked registers before the exception because Monitor mode has separate r13/r14; after the SMC returns, those values are copied into the secure-SVC bank.

I replaced the branch-to-copied-handler path with this sequence. It removes the runtime branch construction and separate monitor handler entirely. I tested it from a cold H616 FEL boot: the secure SID read completed and FEL remained usable afterward. With the TOC0 loader series applied on top, a full SPL/FIT transfer also reached U-Boot proper successfully.

@jameshilliard

Copy link
Copy Markdown
Contributor Author

@smaeul can you check if these latest changes also work on your A133 board?

@jameshilliard

Copy link
Copy Markdown
Contributor Author

secure_boot_fuse_offset was misleadingly named, and I have renamed it to secure_boot_status_offset. For H616/A133, it selects the SID controller's secure-boot status register at SID + 0xa0 (0x030060a0), rather than an efuse word. We only read this register.

The register name and purpose come from Allwinner's H616 BSP source code. In include/arch/efuse_sun50iw9.h, the definitions distinguish the programming control register, the efuse shadow, and the secure-mode status register:

#define SID_PRCTL       (SUNXI_SID_BASE + 0x40)
#define SID_EFUSE       (SUNXI_SID_BASE + 0x200)
#define SID_SECURE_MODE (SUNXI_SID_BASE + 0xA0)

In drivers/secure/efuse.c, the BSP queries secure-boot status with:

int sid_probe_security_mode(void)
{
	return readl(SID_SECURE_MODE) & 1;
}

The same file's sid_set_security_mode() enables secure boot by programming bit 11 of EFUSE_LCJS, which is at efuse offset 0x48. This is a separate operation through the SID programming interface. The BSP therefore explicitly distinguishes burning the secure-boot fuse from reading the controller's indication of secure-boot status.

Our check serves the same purpose as sid_probe_security_mode(), but differs in testing the entire register against zero rather than masking bit 0. That detail comes from H616 NBROM disassembly: the helper at 0x6bb0 reads the word at 0x030060a0, and its caller at 0xa34c compares the returned value against zero without masking it. This supports the whole-word check used in the patch; it does not establish that the two expressions are equivalent for every possible register value.

This status check is only a prerequisite for applying the workaround. It identifies secure-boot configuration, not the CPU's current SCR.NS state. We still perform the separate restricted-memory probe at 0x03006240 before issuing the SMC. A zero status register skips the workaround; a nonzero status register allows that second probe to decide whether it is needed.

Both checks are needed because 0x03006240 can also contain zero on a normal-boot device, so a zero there alone is insufficient evidence of non-secure FEL. Conversely, secure-boot status remains set after the transition to secure SVC. When the second probe becomes readable and nonzero, it prevents repeating the workaround even though secure boot is still enabled.

@jameshilliard

Copy link
Copy Markdown
Contributor Author

I switched the restricted-memory probe to EFUSE_LCJS and simplified the secure-SVC thunk. The changes are folded into the feature commit; the preceding refactoring commit is unchanged.

Detection

The probe now reads 0x03006248, corresponding to SID_EFUSE + 0x48. Allwinner's H616 BSP defines this offset as EFUSE_LCJS, and its sid_set_security_mode() sets bit 11 to enable secure boot. Unlike the write-protection bitmap at efuse offset 0x40, LCJS therefore has a known nonzero bit on a secure-boot device when read from secure state.

The separate status check at SID + 0xa0 remains. It identifies secure-boot configuration, not the CPU's current security state: a zero status word skips the workaround; otherwise, a zero LCJS read triggers it. Once LCJS becomes readable and nonzero, subsequent FEL invocations skip the transition. Both checks are read-only; no fuses are programmed.

Assembly

The thunk retains the single-instruction mov pc, lr patch at MVBAR + 8. Non-secure FEL can write that SRAM location. After smc #0, the patched vector branches back without restoring SPSR_mon, so the remaining fixups execute in Monitor mode.

SP, LR and the original CPSR are kept in unbanked, caller-clobbered registers. The thunk restores the original vector word, clears SCR and MVBAR, configures the secure GIC view, then switches to SVC with IRQ/FIQ still masked. It restores SP/LR before restoring the interrupt masks and returning to the BROM.

This removes the temporary stack, SRAM swaps, dummy SPL entry point, banked-register instruction encodings, and ARMv5 detection. The assembly uses instruction mnemonics with an explicit architecture declaration. The thunk image is now 124 bytes instead of 304 bytes, excluding its appended parameters.

The host uses a fixed-size buffer containing the thunk and three address parameters, without allocation or swap-table construction. It rejects enabled MMU/caches instead of silently skipping the transition, removes the inherited 250 ms delay from this workaround, and rechecks the runtime probe after execution. The normal SID and SPL paths remain unchanged. The header was regenerated using the existing Podman toolchain and formatting; make sunxi-fel does not regenerate it.

H616 Validation

  • Five repeated cold boots, each followed by five separate FEL invocations, passed. Each cold boot applied the workaround once; all 25 follow-up invocations skipped it.
  • Full SID dumps succeeded. LCJS read 0x00000000 before the transition and 0x00000800 afterward; the original SMC vector word was restored.
  • CPU readback confirmed secure SVC: CPSR=0x60000153 with IRQs enabled, SCR=0, and MVBAR=0.
  • A cold-boot SPL/U-Boot/41 MB FIT upload reached U-Boot proper, verified all four FIT subimage hashes, and reached Starting kernel .... This used the existing eGON-wrapped U-Boot image because this PR does not include the TOC0 loader.

A133 has not been hardware-tested with this revision.

@jameshilliard
jameshilliard force-pushed the h616-secure-fel branch 5 times, most recently from f9c2e5c to a95fdd4 Compare September 7, 2026 02:06
@jameshilliard

Copy link
Copy Markdown
Contributor Author

I've simplified this further since the previous update. The refactoring and H616/A133 feature remain separate commits, but the transition now uses an annotated arm_code[] array inside aw_apply_smc_workaround(), matching the existing direct-SMC implementation. No separate assembly source, generated header, or build-system changes are needed.

The transition is now:

  1. Save the FEL return address in r12 and temporarily replace the writable SRAM word at MVBAR + 8 with mov pc, lr, as apritzel suggested.
  2. Execute smc #0. The patched vector resumes execution after the SMC, still in Monitor mode.
  3. Restore the original vector word, clear SCR, and set GICC_CTLR.AckCtl so the existing BROM IRQ handler can acknowledge Group 1 interrupts from secure state.
  4. Return directly to FEL with movs pc, r12, restoring CPSR from the SPSR_mon value saved by SMC and entering secure SVC.

Unlike the previous version, this does not manually save/restore CPSR or copy SP/LR: SVC SP/LR are never modified. MVBAR and the GIC priority-mask/distributor settings are also left alone. The BROM already initialized the GIC; only AckCtl needs adjusting. The two DSBs remain to complete writes before SMC and the final return.

The sequence is now 68 bytes plus two address parameters, or 76 bytes uploaded, compared with 124 bytes plus three parameters previously.

Detection is unchanged: the secure-boot status check excludes normal-boot devices, and the protected LCJS read prevents repeating the transition once secure state is accessible. This works across separate FEL invocations, not just within one process. The normal SID and SPL paths remain unchanged.

The instruction sequence passed repeated H616 cold boots, full SID reads, and an SPL/U-Boot/FIT boot with successful hash verification. The final host-only refactor was checked byte-for-byte against that tested payload. The earlier A133 confirmation covered the older implementation; this reduced version still needs an A133 hardware retest.

@paulkocialkowski

Copy link
Copy Markdown
Member

Mostly a cosmetic review here, I'm not really sure I understand how the offsets to static data are calculated in the arm code, but it looks strange. Other than that it would be good to add more comments (more or less one for each instruction) to the code to clarify what it's doing.

Comment thread fel.c Outdated

if (!soc_info->needs_smc_workaround_if_zero_word_at_addr)
return false;
aw_fel_read(dev, soc_info->needs_smc_workaround_if_zero_word_at_addr,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can add a newline before this and preserve the original comment before the call.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Restored both.

Comment thread fel.c Outdated
uint32_t arm_code[] = {
/* Keep the FEL return address in unbanked r12. */
htole32(0xe1a0c00e), /* mov r12, lr */
/* Patch the SMC vector to return in monitor mode. */

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe some more details about each instruction would be good, what is being loaded/stored, etc.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added per-instruction explanations.

Comment thread fel.c
/* Keep the FEL return address in unbanked r12. */
htole32(0xe1a0c00e), /* mov r12, lr */
/* Patch the SMC vector to return in monitor mode. */
htole32(0xe59f0038), /* ldr r0, [pc, #56] */

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here pc is at offset 0x4 of arm_code so pc + 56 should be 0x3c. I guess we are trying to load 0x44 here. Maybe I'm missing something?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A32 PC reads as instruction address + 8: 0x04 + 8 + 56 = 0x44. Now documented.

Comment thread fel.c
/* Patch the SMC vector to return in monitor mode. */
htole32(0xe59f0038), /* ldr r0, [pc, #56] */
htole32(0xe5901008), /* ldr r1, [r0, #8] */
htole32(0xe59f202c), /* ldr r2, [pc, #44] */

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And I guess we are loading offset 0x40 here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct: 0x0c + 8 + 44 = 0x40, holding the mov pc, lr encoding.

Comment thread fel.c Outdated
soc_info_t *soc_info = dev->soc_info;
uint32_t val;

if (!soc_info->needs_smc_workaround_if_zero_word_at_addr)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can take the occasion to rename this smc_workaround_check_reg or something else that is shorter?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to smc_workaround_probe_addr, since it also probes SRAM.

@smaeul

smaeul commented Sep 8, 2026

Copy link
Copy Markdown

a95fdd4 works for me on A133

Separate the runtime SMC-workaround probe from the code that executes
the workaround so later SoCs can choose a different implementation
without mixing that change into the existing direct-SMC path.

Rename the restricted-memory probe field to smc_workaround_probe_addr
and document its zero-address and zero-read semantics.

Describe the workaround method in SoC data and make every existing
secure-FEL user select the direct-SMC method explicitly. This preserves
the current behaviour because direct SMC remains the only implementation
in this patch.

Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
On secure-boot H616 and A133 devices, FEL starts in non-secure state.
A direct SMC alone does not leave the BROM command loop in secure SVC,
and A133 NBROM also clears the monitor vector table.

Temporarily patch the writable SRAM SMC vector at MVBAR + 8 with
"mov pc, lr". After SMC, this branches back without restoring SPSR_mon,
leaving execution in monitor mode. Encode the sequence directly in
fel.c using the existing direct-SMC instruction-array style.

Keep the FEL return address in r12 and let SMC save CPSR in SPSR_mon.
Restore the original vector word and clear SCR to select secure state.
Set GICC_CTLR.AckCtl so the unchanged BROM IRQ handler can acknowledge
Group 1 interrupts through secure GICC_IAR. Preserve the other GIC
settings: the BROM already enables the distributor and CPU interface
and sets the priority mask. Leave MVBAR unchanged, since IRQs use VBAR
with SCR.IRQ clear.

Return directly to FEL with "movs pc, r12", restoring CPSR from
SPSR_mon, including the original A/I/F masks and endianness. SVC SP/LR
remain untouched. Exception entry and return provide context
synchronization. DSBs complete the vector store before SMC and the GIC
update and vector restoration before returning. The sequence assumes
the BROM's ARM-state, MMU/cache-disabled FEL entry.

Gate the workaround on the secure boot status word at SID base + 0xa0.
Use EFUSE_LCJS at efuse offset 0x48 as the restricted-memory probe:
bit 11 is the secure-boot fuse, so the secure view is non-zero when
secure boot is enabled. Both probes are read-only.

Apply the workaround at startup, leaving the existing SID and SPL paths
unchanged. Once LCJS becomes readable, its non-zero value suppresses
repeat application, including across separate sunxi-fel invocations.
Normal-boot devices are excluded by the secure boot status check.

Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
@jameshilliard

Copy link
Copy Markdown
Contributor Author

Other than that it would be good to add more comments (more or less one for each instruction) to the code to clarify what it's doing.

Comments added.

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.

4 participants