Skip to content

Optimize FEL USB performance#234

Open
jameshilliard wants to merge 6 commits into
linux-sunxi:masterfrom
jameshilliard:toc0-boot
Open

Optimize FEL USB performance#234
jameshilliard wants to merge 6 commits into
linux-sunxi:masterfrom
jameshilliard:toc0-boot

Conversation

@jameshilliard

@jameshilliard jameshilliard commented May 14, 2026

Copy link
Copy Markdown
Contributor

Implementation details: README.FEL-SPEED.md

With optimizations:

sunxi-fel -v -p uboot u-boot-sunxi-with-spl.bin write 0x49000000 fit.itb
Tracking FEL device by SID 32c05000:0c004808:01075b8a:2c9a1f11
Switching USB to high-speed mode, setting FEL bulk endpoints to 512-byte packets: 0x05100040: 0x00029940 -> 0x00029860
Selecting FEL device 000:002 by SID
Applying SMC workaround via secure-SVC return thunk...  done.
TOC0: wrapped SPL item 49111 bytes from 0x840, load 0x00020060
TOC0: payload starts at 0xe000
Stack pointers: sp_irq=0x00021400, sp=0x00053FFC
MMU is not enabled by BROM
=> Executing the SPL... done.
loading image "ARM Trusted Firmware" (41065 bytes) to 0x40000000
loading image "U-Boot" (917496 bytes) to 0x4f000000
loading DTB "sun50i-h616-whatsminer" (48408 bytes)
Patching BROM RX FIFO copy to use DMA.
100% [================================================] 41082 kB, 30185.6 kB/s 
Starting U-Boot (0x40000000).
Store entry point 0x40000000 to RVBAR 0x09010040, and request warm reset with RMR mode 3... done.

Without optimizations:

sunxi-fel --no-high-speed -v -p uboot u-boot-sunxi-with-spl.bin write 0x49000000 fit.itb
Applying SMC workaround via secure-SVC return thunk...  done.
TOC0: wrapped SPL item 49111 bytes from 0x840, load 0x00020060
TOC0: payload starts at 0xe000
Stack pointers: sp_irq=0x00021400, sp=0x00053FFC
MMU is not enabled by BROM
=> Executing the SPL... done.
loading image "ARM Trusted Firmware" (41065 bytes) to 0x40000000
loading image "U-Boot" (917496 bytes) to 0x4f000000
loading DTB "sun50i-h616-whatsminer" (48408 bytes)
100% [================================================] 41082 kB,  320.0 kB/s 
Starting U-Boot (0x40000000).
Store entry point 0x40000000 to RVBAR 0x09010040, and request warm reset with RMR mode 3... done.

@jameshilliard jameshilliard force-pushed the toc0-boot branch 3 times, most recently from 2f16236 to c7aa218 Compare May 15, 2026 20:45
@jameshilliard jameshilliard changed the title Fix h616 secure FEL uboot with TOC0 images Optimize H616 FEL USB performance May 15, 2026
@jameshilliard jameshilliard force-pushed the toc0-boot branch 10 times, most recently from f712eac to df2c0b6 Compare May 17, 2026 03:04
@jameshilliard jameshilliard changed the title Optimize H616 FEL USB performance Optimize FEL USB performance May 17, 2026
@jameshilliard jameshilliard force-pushed the toc0-boot branch 4 times, most recently from 12a1b8d to 710b55a Compare May 24, 2026 04:10
Comment thread soc_info.h Outdated
uint32_t rvbar_reg_alt;/* alternative MMIO address of RVBARADDR0_L register */
uint32_t ver_reg; /* MMIO address of "Version Register" */
uint32_t usb_musb_base;/* base address of the USB OTG controller */
uint32_t fel_endpoint_table_addr; /* BROM FEL endpoint table */

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Is it possible to read this address from the MUSB controller at runtime, instead of storing it per SoC?

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.

I don't think this can be read from the MUSB controller itself.

fel_endpoint_table_addr is not a MUSB register address. It points to the
Boot-ROM's in-SRAM FEL endpoint state table. The MUSB registers expose the
hardware controller and endpoint state, but they do not provide a pointer back
to the BROM's software endpoint descriptors.

The table layout seen in the supported BROMs is:

endpoint_table + 0x10: first FEL bulk endpoint entry
endpoint_table + 0x20: second FEL bulk endpoint entry

entry + 0x00: pointer to endpoint object, or zero
entry + 0x08: cached maxpacket value in the table entry
object + 0x04: cached maxpacket value in the endpoint object

The high-speed thunk patches those cached maxpacket fields to 512 before the
USB reconnect. This keeps the BROM FEL command loop's endpoint bookkeeping in
sync with the high-speed bulk packet size. Without that, the controller can be
switched to high-speed while the BROM's software state still describes 64-byte
full-speed bulk packets.

@jameshilliard jameshilliard force-pushed the toc0-boot branch 2 times, most recently from 0cf057c to 498c80f Compare June 5, 2026 19:07
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.

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.

Also make the old-format thunk header rule pattern-based while it still
only builds the existing SPL thunk header.

Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
On H616 with the secure boot fuse set, FEL starts in non-secure
state. The older direct SMC workaround is not sufficient there because
SMC enters monitor mode instead of directly leaving the BROM FEL command
loop in secure SVC state.

Add a secure-SVC SMC thunk for this case and keep the existing global
startup workaround model. The thunk preserves the BROM SRAM workspace
using the same swap-table convention as the SPL thunk, installs a
temporary monitor-mode SMC handler by patching only the SMC vector word,
then issues SMC. The temporary handler restores the original vector word,
clears SCR.NS, clears MVBAR, restores the secure GIC view expected by
the BROM, copies the saved SVC SP/LR into the secure bank, switches to
secure SVC, and returns to the FEL command loop.

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.

H616 selects the secure-SVC SMC thunk path and gates the workaround on a
non-zero secure boot status word at SID base + 0xa0. The zero-word
runtime probe still has to match before the thunk is applied, so
non-secure H616 boards and already-transitioned secure-FEL sessions do
not enter the secure path just because one of those checks matches.

Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
Secure boot sunxi images use TOC0 containers instead of a bare eGON
SPL header. Teach the spl/uboot command path to recognize a TOC0
image, validate its header and checksum, extract the firmware item, and
wrap it in a synthetic eGON SPL header at the SoC SPL load address.

After the wrapped SPL returns to FEL, pass the payload appended after
the TOC0 container to the existing U-Boot image loader so FIT-based
u-boot-sunxi-with-spl.bin images can be loaded by the normal uboot
command path.

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

Copy link
Copy Markdown

My output with the latest push made 23 minutes ago:

sunxi-fel2 -v uboot u-boot/u-boot-sunxi-with-spl.bin
Tracking FEL device by SID 33802000:4c004808:01474788:2c6c22d1
Switching USB to high-speed mode, setting FEL bulk endpoints to 512-byte packets: 0x05100040: 0x00029940 -> 0x00029860
Selecting FEL device 005:091 by SID
found DT name in SPL header: allwinner/sun50i-h618-orangepi-zero3
Stack pointers: sp_irq=0x00021400, sp=0x00053FFC
MMU is not enabled by BROM
=> Executing the SPL... done.
loading image "ARM Trusted Firmware" (53364 bytes) to 0x40000000
loading image "U-Boot" (6829360 bytes) to 0x4a000000
loading DTB "allwinner/sun50i-h618-orangepi-zero3" (32496 bytes)
Starting U-Boot (0x40000000).
Store entry point 0x40000000 to RVBAR 0x08100040, and request warm reset with RMR mode 3... done.

@jameshilliard jameshilliard force-pushed the toc0-boot branch 2 times, most recently from 2cfe059 to e6b3d27 Compare June 9, 2026 21:54
@electricworry

Copy link
Copy Markdown

Latest test on Orange Pi Zero 3 ran in near instantaneous time:

sunxi-fel2 -v uboot u-boot/u-boot-sunxi-with-spl.bin
Tracking FEL device by SID 33802000:4c004808:01474788:2c6c22d1
Switching USB to high-speed mode, setting FEL bulk endpoints to 512-byte packets: 0x05100040: 0x00029940 -> 0x00029860
Selecting FEL device 005:036 by SID
found DT name in SPL header: allwinner/sun50i-h618-orangepi-zero3
Stack pointers: sp_irq=0x00021400, sp=0x00053FFC
MMU is not enabled by BROM
=> Executing the SPL... done.
loading image "ARM Trusted Firmware" (53364 bytes) to 0x40000000
Patching BROM RX FIFO copy to use DMA.
loading image "U-Boot" (6829360 bytes) to 0x4a000000
loading DTB "allwinner/sun50i-h618-orangepi-zero3" (32496 bytes)
Starting U-Boot (0x40000000).
Store entry point 0x40000000 to RVBAR 0x08100040, and request warm reset with RMR mode 3... done.

@CodeInPolish

CodeInPolish commented Jul 6, 2026

Copy link
Copy Markdown

Thanks for this PR!
I have successfully added support for the T113s3 with this patch:
0001-feat-sunxi-fel-add-hs-rx-dma-support.patch

On the T113s3, the fel_endpoint_table_addr is not on a fixed address, so we need to get it via a separate pointer.
I did not push dma_max_len to its limit because the performance gain is already very high and it did timeout with FEL_RX_DMA_MAX_LEN

The upload time went from 52 to just 2 seconds.

+ sunxi-fel -v -p spl u-boot-spl-ram.bin
Tracking FEL device by SID 93406000:1c004814:01059728:3c671dd7
found DT name in SPL header: XXX-XXX
Stack pointers: sp_irq=0x00021400, sp=0x00043DAC
MMU is not enabled by BROM
=> Executing the SPL... done.
loading image "U-Boot 2026.04 for sunxi board" (390792 bytes) to 0x42e00000
Patching BROM RX FIFO copy to use DMA.
loading image "U-Boot 2026.04 for sunxi board" (390792 bytes) to 0x42e00000
loading DTB "XXX-XXX" (10472 bytes)
+ sunxi-fel -v -p write 0x46500000 fit-os-ram.itb
Tracking FEL device by SID 93406000:1c004814:01059728:3c671dd7
100% [================================================]  3494 kB, 25965.5 kB/s
+ sunxi-fel -v -p write 0x44400000 rootfs.cramfs
Tracking FEL device by SID 93406000:1c004814:01059728:3c671dd7
100% [================================================] 13165 kB, 26849.9 kB/s
+ sunxi-fel -v exe 0x42E00000
Tracking FEL device by SID 93406000:1c004814:01059728:3c671dd7

(sunxi-fel is loading the main uboot binary twice because uboot creates a FIT with uboot as firmware and loadables when CONFIG_SPL_LOAD_FIT=y on the T113s3)
I'm not sure if this could be added to this PR as-is, but it's confirmed working on my side

@jameshilliard

Copy link
Copy Markdown
Contributor Author

On the T113s3, the fel_endpoint_table_addr is not on a fixed address, so we need to get it via a separate pointer.

I don't think this is true, can you run this command with sunxi-fel from master without any changes/patches?:

state=$(sunxi-fel readl 0x00047d08); table=$(sunxi-fel readl $((state + 0x24))); printf 'state=0x%08x table=0x%08x bulk_ep_addr=0x%08x fel_endpoint_table_addr=0x%08x\n' "$state" "$table" "$((table + 0x10))" "$table"

@CodeInPolish

CodeInPolish commented Jul 6, 2026

Copy link
Copy Markdown

On the T113s3, the fel_endpoint_table_addr is not on a fixed address, so we need to get it via a separate pointer.

I don't think this is true, can you run this command with sunxi-fel from master without any changes/patches?:

state=$(sunxi-fel readl 0x00047d08); table=$(sunxi-fel readl $((state + 0x24))); printf 'state=0x%08x table=0x%08x bulk_ep_addr=0x%08x fel_endpoint_table_addr=0x%08x\n' "$state" "$table" "$((table + 0x10))" "$table"

Sure

❯ state=$(sunxi-fel readl 0x00047d08); table=$(sunxi-fel readl $((state + 0x24))); printf 'state=0x%08x table=0x%08x bulk_ep_addr=0x%08x fel_endpoint_table_addr=0x%08x\n' "$state" "$table" "$((table + 0x10))" "$table"
state=0x00044f4c table=0x00044ea8 bulk_ep_addr=0x00044eb8 fel_endpoint_table_addr=0x00044ea8

I cannot reproduce the dynamic table pointer but I still have some logs from my experimentation that show state/table with a different address

❯ STATE=$(sunxi-fel --no-high-speed readl 0x00047d08) 
echo STATE=$STATE 
EPTABLE=$(sunxi-fel --no-high-speed readl $((STATE + 0x24))) 
echo EPTABLE=$EPTABLE 
P1=$(sunxi-fel --no-high-speed readl $((EPTABLE + 0x10))) M1=$(sunxi-fel --no-high-speed readl $((EPTABLE + 0x18))) 
P2=$(sunxi-fel --no-high-speed readl $((EPTABLE + 0x20))) M2=$(sunxi-fel --no-high-speed readl $((EPTABLE + 0x28))) 
echo P1=$P1 M1=$M1 echo P2=$P2 M2=$M2 sunxi-fel --no-high-speed readl $((P1 + 4)) sunxi-fel --no-high-speed readl $((P2 + 4)) 

STATE=0x00044f44 
EPTABLE=0x00044ea0 
P1=0x00044f10 M1=0x00000040 
P2=0x00044f08 M2=0x00000040

@jameshilliard

Copy link
Copy Markdown
Contributor Author

@CodeInPolish I forced pushed some changes adding T113s3 support, let me know if that works on your board.

@jameshilliard

Copy link
Copy Markdown
Contributor Author

If that doesn't work try running this command(from master immediately after a hard power cycle):

for b in 0x00044ea0 0x00044ea8; do p1=$(sunxi-fel readl $((b+0x10))); m1=$(sunxi-fel readl $((b+0x18))); p2=$(sunxi-fel readl $((b+0x20))); m2=$(sunxi-fel readl $((b+0x28))); o1=$(sunxi-fel readl $((p1+4))); o2=$(sunxi-fel readl $((p2+4))); printf 'base=0x%08x p1=0x%08x m1=0x%08x p2=0x%08x m2=0x%08x o1=0x%08x o2=0x%08x\n' "$b" "$p1" "$m1" "$p2" "$m2" "$o1" "$o2"; done

@jameshilliard

Copy link
Copy Markdown
Contributor Author

@CodeInPolish can you also dump the BROM using master from a cold boot?

I think something like this:

sunxi-fel read 0xffff0000 65536 brom_t113.bin

Teach sunxi-fel to switch supported MUSB FEL sessions to high-speed USB.

The USB speed switch disconnects the device, so run the BROM endpoint
maxpacket update and MUSB POWER write from a small device-side thunk.
Do not rely on ordinary FEL commands after the disconnect starts. Treat
the expected disconnect during that EXEC path as success.

Add SID-based device selection and reuse it for re-enumeration
tracking. This lets command sequences reopen the same FEL device after
the speed switch, avoids accidentally continuing on another attached
FEL device, and tolerates transient libusb open and version-probe
failures while the device is reappearing. Keep --no-high-speed as an
escape hatch for the old full-speed path.

Apply the secure-state workaround after the optional speed switch, so
it runs once on the final FEL handle. This still runs before commands
on --no-high-speed, already-high-speed, and unsupported high-speed
paths.

Before requesting an RMR warm reset, clear the USB soft-connect bit
when the USB controller is known and mark the handle disconnected so
libusb cleanup does not wait on a device that intentionally left FEL.

Provide USB controller base and BROM FEL state-pointer addresses for
SoCs whose BROM dumps show the compatible MUSB FEL endpoint layout.
The high-speed thunk follows the BROM state pointer to find the live
endpoint table before patching the two bulk endpoint maxpacket values.
Enable this on A33, A64, H3, H5, A63, H6, H616, V853,
R528/T113-S3/T528, V5, A523 and A133.

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

Copy link
Copy Markdown
Contributor Author

I'll test that and come back to you but I'm pretty sure that if we use a hard-coded location for fel_endpoint_table_addr it will break at some point. During all of my testing so far, I have had 2 different pointer adresses for state:

Hmm, yeah, I think you're right, I pushed a new revision that dynamically calculates the location in the high speed switch thunk. I updated all SoC's to use this new approach as well based on static BROM analysis.

smaeul added a commit to smaeul/sunxi-blobs that referenced this pull request Jul 6, 2026
@jameshilliard

Copy link
Copy Markdown
Contributor Author

I did not push dma_max_len to its limit because the performance gain is already very high and it did timeout with FEL_RX_DMA_MAX_LEN

This is strange, in the current code I've kept it at your tested value but would be nice to know at what size it starts failing.

If you can also paste the output of this script run from master on a cold boot.

@CodeInPolish

Copy link
Copy Markdown

I retested based on commit fbe098dd94845093a3a4f55461d37ccb5f8ff972 and it works correctly! Thanks

I also tested with .dma_max_len = FEL_RX_DMA_MAX_LEN, and it works without issues on all T113s3 boards I have. Unsure why it wasn't stable before.

Do we have any idea why the state pointer seems to be increasing?
Since the first testing the pointer moved by 8 bytes and my oldest board has a 72 byte offset compared to the lowest seen value.
Does the BootROM keep track of something that it needs extra storage?

If you can also paste the output of this script run from master on a cold boot.

There you go:

# FEL USB State Dump
label: n/a
sunxi-fel: sunxi-fel
version: AWUSBFEX soc=00001859(R528) 00000001 ver=0001 44 08 scratchpad=00045000 00000000 00000000
soc_id: 0x00001859
usb_base: 0x04100000
fel_state_ptr_addr: 0x00047d08

## Core registers
POWER_DEVCTL_INDEX_VEND0 0x04100040   0x00029970
TX_RX_INTERRUPTS         0x04100044   0x00000000
EP_INTR_ENABLE           0x04100048   0x00020005
FRAME_MISC               0x0410004c   0x00000328
FIFO_CONFIG              0x04100090   0x01000007
ISCR                     0x04100400   0x4300fc00
PHYCTL                   0x04100404   0x00000000
PHYTUNE                  0x04100418   0x023438e4

## USB core window (0x04100040 +0x80)
04100040: 70 99 02 00 00 00 00 00 05 00 02 00 28 03 00 00  p...........(...
04100050: 07 00 00 00 fe 07 00 00 00 00 00 00 00 00 00 00  ................
04100060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
04100070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 12 00  ................
04100080: 00 02 04 20 00 00 00 00 00 00 00 00 00 00 00 00  ... ............
04100090: 07 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00  ................
041000a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
041000b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

## USB PHY/control window (0x04100400 +0x40)
04100400: 00 fc 00 43 00 00 00 00 00 00 00 00 00 00 00 00  ...C............
04100410: 20 00 00 00 00 00 00 00 e4 38 34 02 00 00 00 00   ........84.....
04100420: 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
04100430: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

## FEL endpoint state
state_ptr                0x00047d08   0x00044f4c
endpoint_table_ptr       0x00044f70   0x00044ea8

## FEL state (0x00044f4c +0x80)
00044f4c: 38 4f 04 00 2c 4f 04 00 20 4f 04 00 18 4f 04 00  8O..,O.. O...O..
00044f5c: 10 4f 04 00 08 4f 04 00 00 4f 04 00 f8 4e 04 00  .O...O...O...N..
00044f6c: f0 4e 04 00 a8 4e 04 00 8c 4e 04 00 70 4e 04 00  .N...N...N..pN..
00044f7c: 54 4e 04 00 38 4e 04 00 00 00 00 00 00 00 00 00  TN..8N..........
00044f8c: 00 00 00 00 ff ff ff ff 00 00 00 00 00 00 00 00  ................
00044f9c: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00044fac: 00 00 00 00 e4 66 00 00 03 00 00 00 7c 0e 00 00  .....f......|...
00044fbc: ff ff ff ff 00 00 00 00 03 00 00 00 b4 10 00 00  ................

## FEL endpoint table (0x00044ea8 +0x80)
00044ea8: 00 00 00 00 00 00 00 00 40 00 00 00 70 4e 04 00  ........@...pN..
00044eb8: 18 4f 04 00 01 01 02 00 00 02 00 00 38 4e 04 00  .O..........8N..
00044ec8: 10 4f 04 00 02 82 02 00 00 02 00 00 54 4e 04 00  .O..........TN..
00044ed8: 00 00 00 00 02 00 00 00 00 00 00 00 06 01 00 09  ................
00044ee8: 01 00 00 00 00 00 00 00 00 4f 04 00 f8 4e 04 00  .........O...N..
00044ef8: 07 05 82 02 40 00 00 00 07 05 01 02 40 00 00 00  ....@.......@...
00044f08: 18 4f 04 00 10 4f 04 00 07 05 82 02 00 02 00 00  .O...O..........
00044f18: 07 05 01 02 00 02 00 00 09 04 00 00 02 ff ff ff  ................

### Endpoint table words
ep0  0x00044ea8   0x00000000 0x00000000 0x00000040 0x00044e70
ep1  0x00044eb8   0x00044f18 0x00020101 0x00000200 0x00044e38
ep2  0x00044ec8   0x00044f10 0x00028202 0x00000200 0x00044e54
ep3  0x00044ed8   0x00000000 0x00000002 0x00000000 0x09000106
ep4  0x00044ee8   0x00000001 0x00000000 0x00044f00 0x00044ef8
ep5  0x00044ef8   0x02820507 0x00000040 0x02010507 0x00000040
ep6  0x00044f08   0x00044f18 0x00044f10 0x02820507 0x00000200
ep7  0x00044f18   0x02010507 0x00000200 0x00000409 0xffffff02

## Inner DMA registers
DMA_INTE                 0x04100500   0x00000000
DMA_INTS                 0x04100504   0x00000000

### DMA channel 0
CHAN_CFN                 0x04100540   0x00000000
SDRAM_ADDR               0x04100544   0x00000000
BYTE_COUNT               0x04100548   0x00000000
RESIDUAL                 0x0410054c   0x00000000

### DMA channel 1
CHAN_CFN                 0x04100550   0x00000000
SDRAM_ADDR               0x04100554   0x00000000
BYTE_COUNT               0x04100558   0x00000000
RESIDUAL                 0x0410055c   0x00000000

### DMA channel 2
CHAN_CFN                 0x04100560   0x00000000
SDRAM_ADDR               0x04100564   0x00000000
BYTE_COUNT               0x04100568   0x00000000
RESIDUAL                 0x0410056c   0x00000000

### DMA channel 3
CHAN_CFN                 0x04100570   0x00000000
SDRAM_ADDR               0x04100574   0x00000000
BYTE_COUNT               0x04100578   0x00000000
RESIDUAL                 0x0410057c   0x00000000

## Inner DMA window (0x04100500 +0x100)
04100500: 00 00 00 00 00 00 00 00 00 00 00 80 00 00 00 00  ................
04100510: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
04100520: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
04100530: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
04100540: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
04100550: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
04100560: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
04100570: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
04100580: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
04100590: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
041005a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
041005b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
041005c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
041005d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
041005e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
041005f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

And thanks again for this PR! It really is a huge performance gain

@jameshilliard

Copy link
Copy Markdown
Contributor Author

I retested based on commit fbe098dd94845093a3a4f55461d37ccb5f8ff972 and it works correctly! Thanks

I pushed some more optimizations just now, could you test that the latest commit works as well?

Do we have any idea why the state pointer seems to be increasing?
Since the first testing the pointer moved by 8 bytes and my oldest board has a 72 byte offset compared to the lowest seen value.
Does the BootROM keep track of something that it needs extra storage?

The current evidence points to this not being extra BootROM state allocation.

In the T113/R528 BROM, 0x00047d08 is a fixed global slot holding a pointer to the live FEL/USB state. The state object itself appears to be built on the BROM stack from SP-relative addresses, and the endpoint table is then reached through that state object.

That explains the observed movement: the global pointer is stable, but the stack-local state object can move if the BROM enters FEL with a slightly different stack pointer or call path. The endpoint table moves with it. In all reported values so far, the relative layout stays consistent:

state - endpoint_table = 0xa4

Examples:

0x00044f44 - 0x00044ea0 = 0xa4
0x00044f4c - 0x00044ea8 = 0xa4
0x00044f8c - 0x00044ee8 = 0xa4

So this looks like stack placement variance, not the BootROM growing some persistent storage area over time. The right thing for sunxi-fel is to keep the fixed pointer-slot address in the SoC table and resolve the live endpoint table through it at runtime, instead of hard-coding one observed endpoint table address.

@CodeInPolish

CodeInPolish commented Jul 7, 2026

Copy link
Copy Markdown

I pushed some more optimizations just now, could you test that the latest commit works as well?

Still works on my side with commit d4320e2b5ea69ad3255ddeec0b2191755e6e59f8

+ sunxi-fel -v -p write 0x43000000 spl-only-flash.bin
Tracking FEL device by SID 93406000:1c004814:01059728:3c671dd7
100% [================================================]  1049 kB, 26243.8 kB/s
+ sunxi-fel -v -p write 0x46500000 fit-os.itb
Tracking FEL device by SID 93406000:1c004814:01059728:3c671dd7
100% [================================================]  3907 kB, 28176.3 kB/s
+ sunxi-fel -v -p write 0x42F00000 hconfig.ubifs
Tracking FEL device by SID 93406000:1c004814:01059728:3c671dd7
100% [================================================]  1651 kB, 26383.9 kB/s
+ sunxi-fel -v -p write 0x44400000 rootfs.ubifs
Tracking FEL device by SID 93406000:1c004814:01059728:3c671dd7
100% [================================================] 17650 kB, 28316.6 kB/s

So this looks like stack placement variance, not the BootROM growing some persistent storage area over time. The right thing for sunxi-fel is to keep the fixed pointer-slot address in the SoC table and resolve the live endpoint table through it at runtime, instead of hard-coding one observed endpoint table address.

That makes sense! Thanks

The newer BROM FEL path copies received USB data byte-by-byte from
the MUSB FIFO. Even after switching the controller to high-speed
mode, this leaves large post-SPL DRAM writes far slower than the USB
link can support.

After SPL has initialized DRAM, install a parameterized RX DMA thunk
for writes into the known DRAM window. The thunk builds the MMU
remap it needs, hooks the BROM RX FIFO copy helper, and replaces it
with a MUSB/USBC endpoint-DMA receive path. It routes the active RX
endpoint DRQ through VEND0 before starting the internal DMA channel,
so the FEL wire protocol stays unchanged.

Put the common thunk code in fel_lib.c and install it from the
shared aw_fel_write_buffer() path. This makes normal writes and FIT
image loading use the same post-SPL DRAM write preparation path. Put
the SoC-specific hook, translation-table, shadow-page,
endpoint-state and GIC parameters in the SoC table. Enable the path
for SoCs whose BROM dumps contain the same FIFO-copy helper ABI and
compatible MUSB register layout: A33, A64, H3, H5, A63, H6, H616,
V853, R528/T113-S3/T528, V5, A523 and A133.

Coalesce full high-speed packets into bounded RX DMA requests. Keep
each host write as a single AW_FEL_1_WRITE request, and have the
thunk copy the final short packet with PIO after stopping DMA. This
keeps arbitrary post-SPL DRAM write sizes working without changing
the FEL wire protocol.

Wait for DMA completion with a temporary IRQ vector and WFI instead
of busy-waiting for the entire transfer. The thunk temporarily drops
the BROM USB IRQ active state, accepts either the DMA completion
interrupt or SOF as the wake source, and restores the BROM interrupt
state before returning. The SoC table supplies either GICv2
CPU-interface state or GICv3 distributor state as needed.

Use the largest 512-byte-aligned DMA request size below the observed
18-bit byte-count wrap point. This avoids the 0x40000 wrap while
reducing per-request setup overhead. The R528/T113-S3/T528 reports
confirmed that this cap is stable there too.

Add the checked-in DMA thunk header to the sunxi-fel prerequisites
and thunk documentation. This makes the binary rebuild when the
generated header changes without making the sunxi-fel target
regenerate thunk headers.

Signed-off-by: James Hilliard <james.hilliard1@gmail.com>
Use a small queue of asynchronous libusb bulk OUT transfers for the
normal write path. This keeps the host controller fed during fast
high-speed RX-DMA writes without relying on large individual
transfers, while still keeping the disconnect-prone re-enumeration
paths on synchronous transfers.

Use 16 KiB transfers with a queue depth of four. USB tracing on H616
shows this keeps the queue full during the main FIT write and reaches
the same throughput plateau as deeper queues, while avoiding
excessive in-flight allocation.

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

Copy link
Copy Markdown
Contributor Author

@CodeInPolish I pushed some more refactoring, let me know if that still works on your board.

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