From 4324d8e83e0c0c385d354d7c28dac8bbcc5635db Mon Sep 17 00:00:00 2001 From: James Hilliard Date: Sun, 17 May 2026 17:20:27 -0600 Subject: [PATCH 1/5] sunxi-fel: factor SMC workaround handling 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 --- fel.c | 29 ++++++++++++++++++----------- soc_info.c | 5 +++++ soc_info.h | 13 ++++++++++--- thunks/Makefile | 4 ++-- 4 files changed, 35 insertions(+), 16 deletions(-) diff --git a/fel.c b/fel.c index 79a7c24d0..1946901ca 100644 --- a/fel.c +++ b/fel.c @@ -564,31 +564,38 @@ void aw_set_sctlr(feldev_handle *dev, soc_info_t *soc_info, aw_write_arm_cp_reg(dev, soc_info, 15, 0, 1, 0, 0, sctlr); } +static bool aw_fel_needs_smc_workaround(feldev_handle *dev) +{ + soc_info_t *soc_info = dev->soc_info; + uint32_t val; + + /* Return if the SoC does not need this workaround */ + if (!soc_info->needs_smc_workaround_if_zero_word_at_addr) + return false; + + /* This has less overhead than fel_readl_n() and may be good enough */ + aw_fel_read(dev, soc_info->needs_smc_workaround_if_zero_word_at_addr, + &val, sizeof(val)); + + return val == 0; +} + /* * Issue a "smc #0" instruction. This brings a SoC booted in "secure boot" * state from the default non-secure FEL into secure FEL. * This crashes on devices using "non-secure boot", as the BROM does not * provide a handler address in MVBAR. So we have a runtime check. */ -void aw_apply_smc_workaround(feldev_handle *dev) +static void aw_apply_smc_workaround(feldev_handle *dev) { soc_info_t *soc_info = dev->soc_info; - uint32_t val; uint32_t arm_code[] = { htole32(0xe1600070), /* smc #0 */ htole32(0xe12fff1e), /* bx lr */ }; - /* Return if the SoC does not need this workaround */ - if (!soc_info->needs_smc_workaround_if_zero_word_at_addr) - return; - - /* This has less overhead than fel_readl_n() and may be good enough */ - aw_fel_read(dev, soc_info->needs_smc_workaround_if_zero_word_at_addr, - &val, sizeof(val)); - /* Return if the workaround is not needed or has been already applied */ - if (val != 0) + if (!aw_fel_needs_smc_workaround(dev)) return; pr_info("Applying SMC workaround... "); diff --git a/soc_info.c b/soc_info.c index 4cb6bcc44..5ca40e2b3 100644 --- a/soc_info.c +++ b/soc_info.c @@ -397,6 +397,7 @@ soc_info_t soc_info_table[] = { .rvbar_reg = 0x017000A0, /* Check L.NOP in the OpenRISC reset vector */ .needs_smc_workaround_if_zero_word_at_addr = 0x40004, + .smc_workaround = SMC_WORKAROUND_DIRECT_SMC, .watchdog = &wd_h3_compat, },{ .soc_id = 0x1639, /* Allwinner A80 */ @@ -445,6 +446,7 @@ soc_info_t soc_info_table[] = { .sid_sections = h3_sid_maps, /* Check L.NOP in the OpenRISC reset vector */ .needs_smc_workaround_if_zero_word_at_addr = 0x40004, + .smc_workaround = SMC_WORKAROUND_DIRECT_SMC, .watchdog = &wd_h3_compat, },{ .soc_id = 0x1681, /* Allwinner V3s */ @@ -483,6 +485,7 @@ soc_info_t soc_info_table[] = { .rvbar_reg = 0x017000A0, /* Check L.NOP in the OpenRISC reset vector */ .needs_smc_workaround_if_zero_word_at_addr = 0x40004, + .smc_workaround = SMC_WORKAROUND_DIRECT_SMC, .watchdog = &wd_h3_compat, },{ .soc_id = 0x1701, /* Allwinner R40 */ @@ -522,6 +525,7 @@ soc_info_t soc_info_table[] = { .rvbar_reg = 0x09010040, /* Check L.NOP in the OpenRISC reset vector */ .needs_smc_workaround_if_zero_word_at_addr = 0x100004, + .smc_workaround = SMC_WORKAROUND_DIRECT_SMC, .watchdog = &wd_h6_compat, },{ .soc_id = 0x1816, /* Allwinner V536 */ @@ -641,6 +645,7 @@ soc_info_t soc_info_table[] = { .sid_sections = generic_2k_sid_maps, .rvbar_reg = 0x08100040, .needs_smc_workaround_if_zero_word_at_addr = 0x100004, + .smc_workaround = SMC_WORKAROUND_DIRECT_SMC, .watchdog = &wd_h6_compat, },{ .swap_buffers = NULL /* End of the table */ diff --git a/soc_info.h b/soc_info.h index 508f29dab..330145859 100644 --- a/soc_info.h +++ b/soc_info.h @@ -107,13 +107,18 @@ typedef struct { * - No access to the secure side of the GIC, so it can't be configured to * be accessible from non-secure world. * - No RMR trigger on ARMv8 cores to bring the core into AArch64. - * However it has been found out that a simple "smc" call will immediately - * return from monitor mode, but with the NS bit cleared, so access to all - * secure peripherals is suddenly possible. + * On older SoCs, a simple "smc" call returns with the NS bit cleared, + * so access to all secure peripherals is suddenly possible. * The 'needs_smc_workaround_if_zero_word_at_addr' field can be used to * have a check for this condition (reading from restricted addresses * typically returns zero) and then activate the SMC workaround if needed. + * The 'smc_workaround' field selects how to apply the workaround once the + * runtime checks say that it is needed. */ +typedef enum { + SMC_WORKAROUND_DIRECT_SMC, +} smc_workaround_t; + typedef struct { uint32_t soc_id; /* ID of the SoC */ const char *name; /* human-readable SoC name string */ @@ -135,6 +140,8 @@ typedef struct { bool icache_fix; /* Use SMC workaround (enter secure mode) if can't read from this address */ uint32_t needs_smc_workaround_if_zero_word_at_addr; + /* How to apply the SMC workaround */ + smc_workaround_t smc_workaround; uint32_t sram_size; /* Usable contiguous SRAM at spl_addr */ sram_swap_buffers *swap_buffers; } soc_info_t; diff --git a/thunks/Makefile b/thunks/Makefile index 27ba55d0e..5f3a9614d 100644 --- a/thunks/Makefile +++ b/thunks/Makefile @@ -24,9 +24,9 @@ OBJDUMP := $(CROSS_COMPILE)objdump AWK_O_TO_H := LC_ALL=C awk -f objdump_to_h.awk -# The SPL thunk requires a different output format. The "style" variable for +# These thunks require the old output format. The "style" variable for # awk controls this, and causes the htole32() conversion to be omitted. -fel-to-spl-thunk.h: fel-to-spl-thunk.S FORCE +$(SPL_THUNK): %.h: %.S FORCE $(AS) -o $(subst .S,.o,$<) -march=armv5te $< $(OBJDUMP) -d $(subst .S,.o,$<) | $(AWK_O_TO_H) -v style=old > $@ From 62ed64621361b2fe94231987b9b45c50b1817854 Mon Sep 17 00:00:00 2001 From: James Hilliard Date: Sun, 17 May 2026 17:20:27 -0600 Subject: [PATCH 2/5] sunxi-fel: handle H616 secure-FEL handoff 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 --- Makefile | 2 +- fel.c | 104 +++++++++++++++--- soc_info.c | 10 ++ soc_info.h | 18 +++- thunks/Makefile | 1 + thunks/README.md | 8 +- thunks/fel-to-secure-svc-smc-thunk.S | 155 +++++++++++++++++++++++++++ thunks/fel-to-secure-svc-smc-thunk.h | 108 +++++++++++++++++++ 8 files changed, 384 insertions(+), 22 deletions(-) create mode 100644 thunks/fel-to-secure-svc-smc-thunk.S create mode 100644 thunks/fel-to-secure-svc-smc-thunk.h diff --git a/Makefile b/Makefile index fd1c15e4b..8af1e63cb 100644 --- a/Makefile +++ b/Makefile @@ -164,7 +164,7 @@ SOC_INFO := soc_info.c soc_info.h FEL_LIB := fel_lib.c fel_lib.h SPI_FLASH:= fel-spiflash.c fel-spiflash.h fel-remotefunc-spi-data-transfer.h -sunxi-fel: fel.c fit_image.c thunks/fel-to-spl-thunk.h $(PROGRESS) $(SOC_INFO) $(FEL_LIB) $(SPI_FLASH) +sunxi-fel: fel.c fit_image.c thunks/fel-to-spl-thunk.h thunks/fel-to-secure-svc-smc-thunk.h $(PROGRESS) $(SOC_INFO) $(FEL_LIB) $(SPI_FLASH) $(CC) $(HOST_CFLAGS) $(LIBUSB_CFLAGS) $(ZLIB_CFLAGS) $(LIBFDT_CFLAGS) $(LDFLAGS) -o $@ \ $(filter %.c,$^) $(LIBS) $(LIBUSB_LIBS) $(ZLIB_LIBS) $(LIBFDT_LIBS) diff --git a/fel.c b/fel.c index 1946901ca..a5238fcf3 100644 --- a/fel.c +++ b/fel.c @@ -358,6 +358,10 @@ static uint32_t fel_to_spl_thunk[] = { #include "thunks/fel-to-spl-thunk.h" }; +static uint32_t fel_to_secure_svc_smc_thunk[] = { + #include "thunks/fel-to-secure-svc-smc-thunk.h" +}; + #define DRAM_BASE 0x40000000 #define DRAM_SIZE 0x80000000 @@ -569,38 +573,106 @@ static bool aw_fel_needs_smc_workaround(feldev_handle *dev) soc_info_t *soc_info = dev->soc_info; uint32_t val; - /* Return if the SoC does not need this workaround */ + if (soc_info->secure_boot_fuse_offset) { + if (!soc_info->sid_base) + return false; + aw_fel_read(dev, soc_info->sid_base + + soc_info->secure_boot_fuse_offset, + &val, sizeof(val)); + if (!le32toh(val)) + return false; + } + if (!soc_info->needs_smc_workaround_if_zero_word_at_addr) return false; - - /* This has less overhead than fel_readl_n() and may be good enough */ aw_fel_read(dev, soc_info->needs_smc_workaround_if_zero_word_at_addr, - &val, sizeof(val)); + &val, sizeof(val)); + + return le32toh(val) == 0; +} + +static void aw_fel_execute_secure_svc_smc_thunk(feldev_handle *dev) +{ + soc_info_t *soc_info = dev->soc_info; + const monitor_smc_handler *handler = soc_info->monitor_smc_handler; + const sram_swap_buffers *swap_buffers = soc_info->swap_buffers; + struct timespec req = { .tv_nsec = 250000000 }; /* 250ms */ + uint32_t arm_code[] = { + htole32(0xe12fff1e), /* bx lr */ + }; + uint32_t *thunk_buf, *p; + size_t thunk_size; + size_t i; + + if (!handler) + pr_fatal("FEL thunk: missing monitor SMC handler info\n"); + + for (i = 0; swap_buffers[i].size; i++) + ; + + thunk_size = sizeof(fel_to_secure_svc_smc_thunk) + + 4 * sizeof(uint32_t) + + (i + 1) * sizeof(*swap_buffers); + + if (thunk_size > soc_info->thunk_size) + pr_fatal("FEL thunk: bad size (need %zu, have %u)\n", + thunk_size, soc_info->thunk_size); - return val == 0; + thunk_buf = malloc(thunk_size); + if (!thunk_buf) + pr_fatal("FEL thunk: failed to allocate buffer\n"); + memcpy(thunk_buf, fel_to_secure_svc_smc_thunk, + sizeof(fel_to_secure_svc_smc_thunk)); + + p = thunk_buf + ARRAY_SIZE(fel_to_secure_svc_smc_thunk); + *p++ = soc_info->spl_addr; + *p++ = handler->vector_addr; + *p++ = handler->gicc_base; + *p++ = handler->gicd_base; + memcpy(p, swap_buffers, (i + 1) * sizeof(*swap_buffers)); + + for (i = 0; i < thunk_size / sizeof(uint32_t); i++) + thunk_buf[i] = htole32(thunk_buf[i]); + + aw_fel_write(dev, arm_code, soc_info->spl_addr, sizeof(arm_code)); + aw_fel_write(dev, thunk_buf, soc_info->thunk_addr, thunk_size); + aw_fel_execute(dev, soc_info->thunk_addr); + free(thunk_buf); + + /* TODO: Try to find and fix the bug, which needs this workaround */ + nanosleep(&req, NULL); } /* - * Issue a "smc #0" instruction. This brings a SoC booted in "secure boot" - * state from the default non-secure FEL into secure FEL. + * Apply the "smc #0" workaround. This moves a secure-boot FEL session from + * the default non-secure state into secure state. * This crashes on devices using "non-secure boot", as the BROM does not * provide a handler address in MVBAR. So we have a runtime check. + * Some newer SoCs need to perform the SMC and return to FEL via a thunk, + * which handles the monitor-to-SVC transition details. */ static void aw_apply_smc_workaround(feldev_handle *dev) { soc_info_t *soc_info = dev->soc_info; - uint32_t arm_code[] = { - htole32(0xe1600070), /* smc #0 */ - htole32(0xe12fff1e), /* bx lr */ - }; - /* Return if the workaround is not needed or has been already applied */ + /* Return if the workaround is not needed */ if (!aw_fel_needs_smc_workaround(dev)) return; - pr_info("Applying SMC workaround... "); - aw_fel_write(dev, arm_code, soc_info->scratch_addr, sizeof(arm_code)); - aw_fel_execute(dev, soc_info->scratch_addr); + if (soc_info->smc_workaround == SMC_WORKAROUND_SECURE_SVC_SMC_THUNK) { + pr_info("Applying SMC workaround via secure-SVC SMC thunk... "); + aw_fel_execute_secure_svc_smc_thunk(dev); + } else { + uint32_t arm_code[] = { + htole32(0xe1600070), /* smc #0 */ + htole32(0xe12fff1e), /* bx lr */ + }; + + pr_info("Applying SMC workaround... "); + aw_fel_write(dev, arm_code, soc_info->scratch_addr, + sizeof(arm_code)); + aw_fel_execute(dev, soc_info->scratch_addr); + } pr_info(" done.\n"); } @@ -1388,7 +1460,7 @@ int main(int argc, char **argv) */ handle = feldev_open(busnum, devnum, AW_USB_VENDOR_ID, AW_USB_PRODUCT_ID); - /* Some SoCs need the SMC workaround to enter the secure boot mode */ + /* Some SoCs need the SMC workaround to enter secure state */ aw_apply_smc_workaround(handle); /* Handle command-style arguments, in order of appearance */ diff --git a/soc_info.c b/soc_info.c index 5ca40e2b3..492804c39 100644 --- a/soc_info.c +++ b/soc_info.c @@ -156,6 +156,12 @@ sram_swap_buffers a133_sram_swap_buffers[] = { { .size = 0 } /* End of the table */ }; +static const monitor_smc_handler h616_monitor_smc_handler = { + .vector_addr = 0x000300c0, + .gicc_base = 0x03022000, + .gicd_base = 0x03021000, +}; + /* * R329 has no SRAM A1, but a huge SRAM A2 at 0x100000. SPL and BROM uses * this SRAM A2's first part like how other SoCs use SRAM A1. The sp and @@ -565,6 +571,10 @@ soc_info_t soc_info_table[] = { .rvbar_reg = 0x09010040, .rvbar_reg_alt= 0x08100040, .ver_reg = 0x03000024, + .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, },{ .soc_id = 0x1851, /* Allwinner R329 */ diff --git a/soc_info.h b/soc_info.h index 330145859..a3c3e14a5 100644 --- a/soc_info.h +++ b/soc_info.h @@ -108,17 +108,30 @@ typedef struct { * be accessible from non-secure world. * - No RMR trigger on ARMv8 cores to bring the core into AArch64. * On older SoCs, a simple "smc" call returns with the NS bit cleared, - * so access to all secure peripherals is suddenly possible. + * so access to all secure peripherals is suddenly possible. Newer SoCs + * may need a secure-SVC thunk to handle the monitor-to-SVC transition + * after the SMC call before returning to FEL. * The 'needs_smc_workaround_if_zero_word_at_addr' field can be used to * have a check for this condition (reading from restricted addresses * typically returns zero) and then activate the SMC workaround if needed. + * The 'secure_boot_fuse_offset' field can be used when the SoC has an + * explicit readable secure boot status word at sid_base + offset. It is a + * gate only; a separate runtime state probe is still required to avoid + * applying the workaround on every invocation. * The 'smc_workaround' field selects how to apply the workaround once the * runtime checks say that it is needed. */ typedef enum { SMC_WORKAROUND_DIRECT_SMC, + SMC_WORKAROUND_SECURE_SVC_SMC_THUNK, } smc_workaround_t; +typedef struct { + uint32_t vector_addr; + uint32_t gicc_base; + uint32_t gicd_base; +} monitor_smc_handler; + typedef struct { uint32_t soc_id; /* ID of the SoC */ const char *name; /* human-readable SoC name string */ @@ -140,8 +153,11 @@ typedef struct { bool icache_fix; /* Use SMC workaround (enter secure mode) if can't read from this address */ uint32_t needs_smc_workaround_if_zero_word_at_addr; + /* Require non-zero sid_base + offset before applying SMC workaround */ + uint32_t secure_boot_fuse_offset; /* How to apply the SMC workaround */ smc_workaround_t smc_workaround; + const monitor_smc_handler *monitor_smc_handler; uint32_t sram_size; /* Usable contiguous SRAM at spl_addr */ sram_swap_buffers *swap_buffers; } soc_info_t; diff --git a/thunks/Makefile b/thunks/Makefile index 5f3a9614d..fc7286004 100644 --- a/thunks/Makefile +++ b/thunks/Makefile @@ -3,6 +3,7 @@ # SPL_THUNK := fel-to-spl-thunk.h +SPL_THUNK += fel-to-secure-svc-smc-thunk.h THUNKS := clrsetbits.h THUNKS += memcpy.h THUNKS += readl_writel.h diff --git a/thunks/README.md b/thunks/README.md index e9ca949f2..4063bbd82 100644 --- a/thunks/README.md +++ b/thunks/README.md @@ -8,10 +8,10 @@ usually via `sunxi-fel`. Normally you don't need to change or (re)build anything within this folder. Currently our main build process (via the parent directory's _Makefile_) -only includes `fel-to-spl-thunk.h` directly. Other _.h_ files are provided -**just for reference**. The main purpose of this folder is simply keeping -track of _.S_ sources, to help with possible future maintenance of the -various code snippets. +includes `fel-to-spl-thunk.h` and `fel-to-secure-svc-smc-thunk.h` directly. +Other _.h_ files are provided **just for reference**. The main purpose of this +folder is simply keeping track of _.S_ sources, to help with possible future +maintenance of the various code snippets. Please note that any files lacking explicit license information are intended to be covered by the project's [overall license](../LICENSE.md) (GPLv2). diff --git a/thunks/fel-to-secure-svc-smc-thunk.S b/thunks/fel-to-secure-svc-smc-thunk.S new file mode 100644 index 000000000..4b121d3e1 --- /dev/null +++ b/thunks/fel-to-secure-svc-smc-thunk.S @@ -0,0 +1,155 @@ +/* secure-FEL thunk using a temporary monitor-mode SMC handler. */ + +.arm + +BUF1 .req r0 +BUF2 .req r1 +TMP1 .req r2 +TMP2 .req r3 +SWAPTBL .req r4 +ORIG_VECTOR .req r5 +BUFSIZE .req r6 +ENTRY_ADDR .req r8 +MON_VECTOR .req r9 +SAVED_SP .req r10 +SAVED_LR .req r11 + +entry_point: + b setup_stack + +stack_begin: + .space 32, 0xff +stack_end: + nop + + /* Walk the table and swap all buffers. */ +swap_all_buffers: + adr SWAPTBL, appended_data + 16 +swap_next_buffer: + ldr BUF1, [SWAPTBL], #4 + ldr BUF2, [SWAPTBL], #4 + ldr BUFSIZE, [SWAPTBL], #4 + cmp BUFSIZE, #0 + bxeq lr +swap_next_word: + ldr TMP1, [BUF1] + ldr TMP2, [BUF2] + subs BUFSIZE, BUFSIZE, #4 + str TMP1, [BUF2], #4 + str TMP2, [BUF1], #4 + bne swap_next_word + b swap_next_buffer + +setup_stack: + ldr ENTRY_ADDR, appended_data + ldr MON_VECTOR, appended_data + 4 + adr BUF1, stack_end + str sp, [BUF1, #-4]! + mov sp, BUF1 + mrs TMP1, cpsr + push {TMP1, lr} + + /* Disable IRQ and FIQ. */ + orr TMP1, #0xc0 + msr cpsr_c, TMP1 + + /* Check if the instructions or data cache is enabled. */ + mrc p15, 0, TMP1, c1, c0, 0 + tst TMP1, #(1 << 2) + tsteq TMP1, #(1 << 12) + bne return_to_fel_noswap + + bl swap_all_buffers + bl call_entry + b return_to_secure_fel + +return_to_secure_fel: + bl swap_all_buffers + +return_to_fel_noswap: + pop {TMP1, lr} + msr cpsr_c, TMP1 + ldr sp, [sp] + bx lr + +call_entry: + /* Parameterize the copied monitor handler. */ + ldr TMP2, appended_data + 8 + adr TMP1, monitor_gicc_ctlr + str TMP2, [TMP1] + ldr TMP2, appended_data + 12 + adr TMP1, monitor_gicd_ctlr + str TMP2, [TMP1] + + /* Temporarily redirect only the monitor SMC vector. */ + ldr ORIG_VECTOR, [MON_VECTOR, #8] + adr TMP1, monitor_smc_handler + add TMP2, MON_VECTOR, #16 /* PC at the SMC vector */ + sub TMP1, TMP1, TMP2 + mov TMP1, TMP1, asr #2 + ldr TMP2, branch_offset_mask + and TMP1, TMP1, TMP2 + ldr TMP2, branch_opcode + orr TMP1, TMP1, TMP2 + str TMP1, [MON_VECTOR, #8] + + /* + * The handler returns from the SMC exception into secure SVC, using the + * banked SP/LR copied from this non-secure SVC call frame. + */ + mcr p15, 0, TMP1, c7, c10, 4 /* CP15DSB */ + mrc p15, 0, TMP1, c0, c0, 0 /* read MIDR */ + and TMP1, TMP1, #(0xf << 16) /* architecture */ + cmp TMP1, #(0x6 << 16) /* ARMv5TEJ */ + mcrgt p15, 0, TMP1, c7, c5, 4 /* CP15ISB, if > ARMv5TEJ */ + mov SAVED_SP, sp + mov SAVED_LR, lr + .word 0xe1600070 /* smc #0 */ + /* The custom handler switches to secure SVC and does not return here. */ + bx ENTRY_ADDR + +monitor_smc_handler: + str ORIG_VECTOR, [MON_VECTOR, #8] + mov TMP1, #0 + mcr p15, 0, TMP1, c1, c1, 0 /* SCR = secure */ + mcr p15, 0, TMP1, c12, c0, 1 /* MVBAR = 0 */ + + ldr TMP1, monitor_gicc_ctlr + mov TMP2, #0xf + str TMP2, [TMP1] + mov TMP2, #0xf8 + str TMP2, [TMP1, #4] + ldr TMP1, monitor_gicd_ctlr + mov TMP2, #3 + str TMP2, [TMP1] + + .word 0xe123f30a /* msr SP_svc, SAVED_SP */ + .word 0xe122f30b /* msr LR_svc, SAVED_LR */ + .word 0xf57ff06f /* isb */ + mrs TMP1, cpsr + bic TMP1, TMP1, #0x1f + orr TMP1, TMP1, #0x13 + msr cpsr_fc, TMP1 /* switch to secure-SVC mode */ + bx ENTRY_ADDR + +monitor_gicc_ctlr: + .word 0 +monitor_gicd_ctlr: + .word 0 +branch_offset_mask: + .word 0x00ffffff +branch_opcode: + .word 0xea000000 + +appended_data: +/* + * The appended data uses the following format: + * + * struct { + * uint32_t entry_addr; + * uint32_t monitor_vector_addr; + * uint32_t gicc_ctlr_addr; + * uint32_t gicd_ctlr_addr; + * sram_swap_buffers swaptbl[]; + * }; + */ diff --git a/thunks/fel-to-secure-svc-smc-thunk.h b/thunks/fel-to-secure-svc-smc-thunk.h new file mode 100644 index 000000000..c6d10409f --- /dev/null +++ b/thunks/fel-to-secure-svc-smc-thunk.h @@ -0,0 +1,108 @@ + /* : */ + 0xea000015, /* 0: b 5c */ + /* : */ + 0xffffffff, /* 4: .word 0xffffffff */ + 0xffffffff, /* 8: .word 0xffffffff */ + 0xffffffff, /* c: .word 0xffffffff */ + 0xffffffff, /* 10: .word 0xffffffff */ + 0xffffffff, /* 14: .word 0xffffffff */ + 0xffffffff, /* 18: .word 0xffffffff */ + 0xffffffff, /* 1c: .word 0xffffffff */ + 0xffffffff, /* 20: .word 0xffffffff */ + /* : */ + 0xe1a00000, /* 24: nop */ + /* : */ + 0xe28f4f55, /* 28: add r4, pc, #340 */ + /* : */ + 0xe4940004, /* 2c: ldr r0, [r4], #4 */ + 0xe4941004, /* 30: ldr r1, [r4], #4 */ + 0xe4946004, /* 34: ldr r6, [r4], #4 */ + 0xe3560000, /* 38: cmp r6, #0 */ + 0x012fff1e, /* 3c: bxeq lr */ + /* : */ + 0xe5902000, /* 40: ldr r2, [r0] */ + 0xe5913000, /* 44: ldr r3, [r1] */ + 0xe2566004, /* 48: subs r6, r6, #4 */ + 0xe4812004, /* 4c: str r2, [r1], #4 */ + 0xe4803004, /* 50: str r3, [r0], #4 */ + 0x1afffff9, /* 54: bne 40 */ + 0xeafffff3, /* 58: b 2c */ + /* : */ + 0xe59f8110, /* 5c: ldr r8, [pc, #272] */ + 0xe59f9110, /* 60: ldr r9, [pc, #272] */ + 0xe24f0048, /* 64: sub r0, pc, #72 */ + 0xe520d004, /* 68: str sp, [r0, #-4]! */ + 0xe1a0d000, /* 6c: mov sp, r0 */ + 0xe10f2000, /* 70: mrs r2, CPSR */ + 0xe92d4004, /* 74: push {r2, lr} */ + 0xe38220c0, /* 78: orr r2, r2, #192 */ + 0xe121f002, /* 7c: msr CPSR_c, r2 */ + 0xee112f10, /* 80: mrc 15, 0, r2, cr1, cr0, {0} */ + 0xe3120004, /* 84: tst r2, #4 */ + 0x03120a01, /* 88: tsteq r2, #4096 */ + 0x1a000003, /* 8c: bne a0 */ + 0xebffffe4, /* 90: bl 28 */ + 0xeb000005, /* 94: bl b0 */ + 0xeaffffff, /* 98: b 9c */ + /* : */ + 0xebffffe1, /* 9c: bl 28 */ + /* : */ + 0xe8bd4004, /* a0: pop {r2, lr} */ + 0xe121f002, /* a4: msr CPSR_c, r2 */ + 0xe59dd000, /* a8: ldr sp, [sp] */ + 0xe12fff1e, /* ac: bx lr */ + /* : */ + 0xe59f30c4, /* b0: ldr r3, [pc, #196] */ + 0xe28f20a8, /* b4: add r2, pc, #168 */ + 0xe5823000, /* b8: str r3, [r2] */ + 0xe59f30bc, /* bc: ldr r3, [pc, #188] */ + 0xe28f20a0, /* c0: add r2, pc, #160 */ + 0xe5823000, /* c4: str r3, [r2] */ + 0xe5995008, /* c8: ldr r5, [r9, #8] */ + 0xe28f2040, /* cc: add r2, pc, #64 */ + 0xe2893010, /* d0: add r3, r9, #16 */ + 0xe0422003, /* d4: sub r2, r2, r3 */ + 0xe1a02142, /* d8: asr r2, r2, #2 */ + 0xe59f3088, /* dc: ldr r3, [pc, #136] */ + 0xe0022003, /* e0: and r2, r2, r3 */ + 0xe59f3084, /* e4: ldr r3, [pc, #132] */ + 0xe1822003, /* e8: orr r2, r2, r3 */ + 0xe5892008, /* ec: str r2, [r9, #8] */ + 0xee072f9a, /* f0: mcr 15, 0, r2, cr7, cr10, {4} */ + 0xee102f10, /* f4: mrc 15, 0, r2, cr0, cr0, {0} */ + 0xe202280f, /* f8: and r2, r2, #983040 */ + 0xe3520806, /* fc: cmp r2, #393216 */ + 0xce072f95, /* 100: mcrgt 15, 0, r2, cr7, cr5, {4} */ + 0xe1a0a00d, /* 104: mov sl, sp */ + 0xe1a0b00e, /* 108: mov fp, lr */ + 0xe1600070, /* 10c: .word 0xe1600070 */ + 0xe12fff18, /* 110: bx r8 */ + /* : */ + 0xe5895008, /* 114: str r5, [r9, #8] */ + 0xe3a02000, /* 118: mov r2, #0 */ + 0xee012f11, /* 11c: mcr 15, 0, r2, cr1, cr1, {0} */ + 0xee0c2f30, /* 120: mcr 15, 0, r2, cr12, cr0, {1} */ + 0xe59f2038, /* 124: ldr r2, [pc, #56] */ + 0xe3a0300f, /* 128: mov r3, #15 */ + 0xe5823000, /* 12c: str r3, [r2] */ + 0xe3a030f8, /* 130: mov r3, #248 */ + 0xe5823004, /* 134: str r3, [r2, #4] */ + 0xe59f2028, /* 138: ldr r2, [pc, #40] */ + 0xe3a03003, /* 13c: mov r3, #3 */ + 0xe5823000, /* 140: str r3, [r2] */ + 0xe123f30a, /* 144: .word 0xe123f30a */ + 0xe122f30b, /* 148: .word 0xe122f30b */ + 0xf57ff06f, /* 14c: .word 0xf57ff06f */ + 0xe10f2000, /* 150: mrs r2, CPSR */ + 0xe3c2201f, /* 154: bic r2, r2, #31 */ + 0xe3822013, /* 158: orr r2, r2, #19 */ + 0xe129f002, /* 15c: msr CPSR_fc, r2 */ + 0xe12fff18, /* 160: bx r8 */ + /* : */ + 0x00000000, /* 164: .word 0x00000000 */ + /* : */ + 0x00000000, /* 168: .word 0x00000000 */ + /* : */ + 0x00ffffff, /* 16c: .word 0x00ffffff */ + /* : */ + 0xea000000, /* 170: .word 0xea000000 */ From 7a886225023af163f61b1a66300bf0ef05099080 Mon Sep 17 00:00:00 2001 From: James Hilliard Date: Wed, 13 May 2026 19:24:16 -0600 Subject: [PATCH 3/5] sunxi-fel: add TOC0 boot image support 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 --- fel.c | 160 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 158 insertions(+), 2 deletions(-) diff --git a/fel.c b/fel.c index a5238fcf3..549262b3c 100644 --- a/fel.c +++ b/fel.c @@ -76,6 +76,38 @@ typedef struct image_header { #define HEADER_NAME_OFFSET offsetof(image_header_t, ih_name) #define HEADER_SIZE sizeof(image_header_t) +#define TOC0_HEADER_SIZE 0x30 +#define TOC0_ITEM_SIZE 0x20 +#define TOC0_MAIN_MAGIC_OFFSET 0x08 +#define TOC0_CHECKSUM_OFFSET 0x0c +#define TOC0_NUM_ITEMS_OFFSET 0x18 +#define TOC0_LENGTH_OFFSET 0x1c +#define TOC0_MAIN_END_OFFSET 0x2c +#define TOC0_ITEM_NAME_OFFSET 0x00 +#define TOC0_ITEM_OFFSET_OFFSET 0x04 +#define TOC0_ITEM_SIZE_OFFSET 0x08 +#define TOC0_ITEM_LOAD_OFFSET 0x14 +#define TOC0_ITEM_END_OFFSET 0x1c +#define TOC0_MAIN_INFO_MAGIC 0x89119800 +#define TOC0_ITEM_INFO_NAME_FIRMWARE 0x00010202 +#define EGON_HEADER_SIZE 0x60 +#define EGON_CHECKSUM_SEED 0x5F0A6C39 +#define SPL_HEADER_VERSION 2 + +static uint32_t get_le32(const void *ptr) +{ + uint32_t val; + + memcpy(&val, ptr, sizeof(val)); + return le32toh(val); +} + +static void put_le32(void *ptr, uint32_t val) +{ + val = htole32(val); + memcpy(ptr, &val, sizeof(val)); +} + /* * Utility function to determine the image type from a mkimage-compatible * header at given buffer (address). @@ -854,7 +886,7 @@ uint32_t aw_fel_write_and_execute_spl(feldev_handle *dev, uint8_t *buf, size_t l if (len < 32 || memcmp(buf + 4, "eGON.BT0", 8) != 0) pr_fatal("SPL: eGON header is not found\n"); - spl_checksum = 2 * le32toh(buf32[3]) - 0x5F0A6C39; + spl_checksum = 2 * le32toh(buf32[3]) - EGON_CHECKSUM_SEED; spl_len = le32toh(buf32[4]); if (spl_len > len || (spl_len % 4) != 0) @@ -1049,6 +1081,112 @@ static void aw_fel_write_uboot_image(feldev_handle *dev, uint8_t *buf, uboot_size = data_size; } +static bool is_toc0_image(const uint8_t *buf, size_t len) +{ + return len >= TOC0_HEADER_SIZE && memcmp(buf, "TOC0.GLH", 8) == 0; +} + +static uint8_t *toc0_make_egon_spl(const soc_info_t *soc_info, + const uint8_t *buf, + size_t len, size_t *egon_len, + size_t *payload_offset) +{ + uint32_t toc0_len, num_items, checksum, hdr_checksum; + uint32_t code_offset = 0, code_size = 0, load_addr = 0; + uint32_t code_pos, branch_words; + uint64_t egon_size; + uint8_t *egon; + size_t i; + + if (!soc_info) + pr_fatal("TOC0: Unsupported SoC type\n"); + if (len < TOC0_HEADER_SIZE) + pr_fatal("TOC0: invalid header\n"); + + num_items = get_le32(buf + TOC0_NUM_ITEMS_OFFSET); + toc0_len = get_le32(buf + TOC0_LENGTH_OFFSET); + hdr_checksum = get_le32(buf + TOC0_CHECKSUM_OFFSET); + if ((toc0_len & 3) || toc0_len < TOC0_HEADER_SIZE || + toc0_len > len || num_items == 0 || + num_items > (toc0_len - TOC0_HEADER_SIZE) / TOC0_ITEM_SIZE) + pr_fatal("TOC0: invalid header\n"); + if (get_le32(buf + TOC0_MAIN_MAGIC_OFFSET) != TOC0_MAIN_INFO_MAGIC || + memcmp(buf + TOC0_MAIN_END_OFFSET, "MIE;", 4) != 0) + pr_fatal("TOC0: invalid main info header\n"); + + checksum = EGON_CHECKSUM_SEED; + for (i = 0; i < toc0_len; i += 4) + checksum += get_le32(buf + i); + if (checksum != 2 * hdr_checksum) + pr_fatal("TOC0: checksum mismatch\n"); + + for (i = 0; i < num_items; i++) { + const uint8_t *item = buf + TOC0_HEADER_SIZE + + i * TOC0_ITEM_SIZE; + uint32_t item_name; + uint32_t item_offset, item_size, item_load; + + if (memcmp(item + TOC0_ITEM_END_OFFSET, "IIE;", 4) != 0) + pr_fatal("TOC0: invalid item header\n"); + + item_name = get_le32(item + TOC0_ITEM_NAME_OFFSET); + if (item_name != TOC0_ITEM_INFO_NAME_FIRMWARE) + continue; + + item_offset = get_le32(item + TOC0_ITEM_OFFSET_OFFSET); + item_size = get_le32(item + TOC0_ITEM_SIZE_OFFSET); + item_load = get_le32(item + TOC0_ITEM_LOAD_OFFSET); + if (item_size == 0 || item_load == 0) + continue; + if (item_offset > toc0_len || item_size > toc0_len - item_offset) + pr_fatal("TOC0: invalid SPL item bounds\n"); + + code_offset = item_offset; + code_size = item_size; + load_addr = item_load; + break; + } + + if (!code_size) + pr_fatal("TOC0: no loadable SPL item found\n"); + if (load_addr < soc_info->spl_addr) + pr_fatal("TOC0: SPL load address 0x%08x is below 0x%08x\n", + load_addr, soc_info->spl_addr); + + code_pos = load_addr - soc_info->spl_addr; + if (code_pos < EGON_HEADER_SIZE || (code_pos & 3)) + pr_fatal("TOC0: unsupported SPL load offset 0x%x\n", code_pos); + + egon_size = (uint64_t)code_pos + code_size; + egon_size = (egon_size + 3) & ~(uint64_t)3; + if (egon_size > soc_info->sram_size) + pr_fatal("TOC0: SPL item too large\n"); + *egon_len = (size_t)egon_size; + egon = calloc(1, *egon_len); + if (!egon) + pr_fatal("TOC0: failed to allocate SPL buffer\n"); + + branch_words = (code_pos - 8) >> 2; + put_le32(egon, 0xea000000 | (branch_words & 0x00ffffff)); + memcpy(egon + 4, "eGON.BT0", 8); + put_le32(egon + 0x10, (uint32_t)*egon_len); + memcpy(egon + 0x14, "SPL", 3); + egon[0x17] = SPL_HEADER_VERSION; + memcpy(egon + code_pos, buf + code_offset, code_size); + + checksum = EGON_CHECKSUM_SEED; + for (i = 0; i < *egon_len; i += 4) + checksum += get_le32(egon + i); + put_le32(egon + 0x0c, checksum); + + *payload_offset = toc0_len; + pr_info("TOC0: wrapped SPL item %u bytes from 0x%x, load 0x%08x\n", + code_size, code_offset, load_addr); + pr_info("TOC0: payload starts at 0x%x\n", toc0_len); + + return egon; +} + static const char *spl_get_dtb_name(uint8_t *spl_buf) { uint32_t dt_offset; @@ -1080,7 +1218,25 @@ void aw_fel_process_spl_and_uboot(feldev_handle *dev, const char *filename) uint32_t offset; /* load file into memory buffer */ uint8_t *buf = load_file(filename, &size); - const char *dt_name = spl_get_dtb_name(buf); + const char *dt_name; + + if (is_toc0_image(buf, size)) { + size_t egon_spl_size, payload_offset; + uint8_t *egon_spl = toc0_make_egon_spl(dev->soc_info, buf, + size, &egon_spl_size, + &payload_offset); + + aw_fel_write_and_execute_spl(dev, egon_spl, egon_spl_size); + free(egon_spl); + + if (size > payload_offset) + aw_fel_write_uboot_image(dev, buf + payload_offset, + size - payload_offset, NULL); + free(buf); + return; + } + + dt_name = spl_get_dtb_name(buf); /* write and execute the SPL from the buffer */ offset = aw_fel_write_and_execute_spl(dev, buf, size); From 4affc4ccc2c18d490196334615ec580b0d934afe Mon Sep 17 00:00:00 2001 From: James Hilliard Date: Sat, 16 May 2026 00:20:23 -0600 Subject: [PATCH 4/5] sunxi-fel: switch supported FEL sessions to high-speed USB 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 endpoint table addresses for SoCs whose BROM dumps show the compatible MUSB FEL endpoint layout. This allows the generic high-speed switch and 512-byte endpoint maxpacket patch paths to run on A33, A64, H3, H5, A63, H6, H616, V853, V5, A523 and A133. Signed-off-by: James Hilliard --- fel.c | 244 ++++++++++++++++++++++++++++++++++++++++++++++++---- fel_lib.c | 228 +++++++++++++++++++++++++++++++++++++++++++----- fel_lib.h | 3 + soc_info.c | 22 +++++ soc_info.h | 2 + sunxi-fel.1 | 5 ++ 6 files changed, 464 insertions(+), 40 deletions(-) diff --git a/fel.c b/fel.c index 549262b3c..ce47ecc6a 100644 --- a/fel.c +++ b/fel.c @@ -36,6 +36,10 @@ bool verbose = false; /* If set, makes the 'fel' tool more talkative */ static uint32_t uboot_entry = 0; /* entry point (address) of U-Boot */ static uint32_t uboot_size = 0; /* size of U-Boot binary */ static bool enter_in_aarch64 = false; +static bool fel_usb_high_speed_enabled = false; + +#define FEL_REENUM_TIMEOUT_MS 5000 +#define FEL_REENUM_RETRY_MS 100 /* printf-style output, but only if "verbose" flag is active */ #define pr_info(...) \ @@ -94,6 +98,17 @@ typedef struct image_header { #define EGON_CHECKSUM_SEED 0x5F0A6C39 #define SPL_HEADER_VERSION 2 +#define MUSB_POWER_OFFSET 0x40 +#define MUSB_POWER_HSMODE 0x10 +#define MUSB_POWER_HSENAB 0x20 +/* MUSB DEVCTL is the next byte after POWER. */ +#define MUSB_DEVCTL_SESSION (0x01 << 8) + +#define FEL_HIGH_SPEED_MAXPACKET 512 +#define FEL_BULK_ENDPOINT_OFFSET 0x10 + +#define SID_STR_SIZE 36 + static uint32_t get_le32(const void *ptr) { uint32_t val; @@ -451,9 +466,109 @@ void fel_writel(feldev_handle *dev, uint32_t addr, uint32_t val) fel_writel_n(dev, addr, &val, 1); } +static bool aw_fel_switch_usb_high_speed(feldev_handle *dev) +{ + soc_info_t *soc_info = dev->soc_info; + uint32_t arm_code[22], bulk_ep_addr, musb_power, new_val, val; + + if (!soc_info->usb_musb_base) + return false; + + musb_power = soc_info->usb_musb_base + MUSB_POWER_OFFSET; + val = fel_readl(dev, musb_power); + if (val & MUSB_POWER_HSMODE) + return false; + + new_val = (val & ~MUSB_DEVCTL_SESSION) | MUSB_POWER_HSENAB; + bulk_ep_addr = soc_info->fel_endpoint_table_addr; + if (bulk_ep_addr) + bulk_ep_addr += FEL_BULK_ENDPOINT_OFFSET; + /* + * Switching to high-speed disconnects USB. Do the endpoint patching + * and POWER write in one device-side thunk so no ordinary FEL command + * has to complete after the disconnect starts. + */ + arm_code[0] = htole32(0xe59f0044); /* ldr r0, [ep_addr] */ + arm_code[1] = htole32(0xe3500000); /* cmp r0, #0 */ + arm_code[2] = htole32(0x0a000009); /* beq power */ + arm_code[3] = htole32(0xe3a05002); /* mov r5, #2 */ + arm_code[4] = htole32(0xe3a04c02); /* mov r4, #512 */ + arm_code[5] = htole32(0xe5901000); /* ldr r1, [r0] */ + arm_code[6] = htole32(0xe3510000); /* cmp r1, #0 */ + arm_code[7] = htole32(0x0a000001); /* beq ep_next */ + arm_code[8] = htole32(0xe5804008); /* str r4, [r0, #8] */ + arm_code[9] = htole32(0xe5814004); /* str r4, [r1, #4] */ + arm_code[10] = htole32(0xe2800010); /* add r0, r0, #16 */ + arm_code[11] = htole32(0xe2555001); /* subs r5, r5, #1 */ + arm_code[12] = htole32(0x1afffff7); /* bne ep_loop */ + arm_code[13] = htole32(0xe59f0014); /* ldr r0, [musb_power] */ + arm_code[14] = htole32(0xe59f1014); /* ldr r1, [new_val] */ + arm_code[15] = htole32(0xe5801000); /* str r1, [r0] */ + arm_code[16] = htole32(0xf57ff04f); /* dsb sy */ + arm_code[17] = htole32(0xf57ff06f); /* isb sy */ + arm_code[18] = htole32(0xe12fff1e); /* bx lr */ + arm_code[19] = htole32(bulk_ep_addr); + arm_code[20] = htole32(musb_power); + arm_code[21] = htole32(new_val); + + if (bulk_ep_addr) { + pr_info("Switching USB to high-speed mode, setting FEL bulk " + "endpoints to %u-byte packets: " + "0x%08x: 0x%08x -> 0x%08x\n", + FEL_HIGH_SPEED_MAXPACKET, musb_power, val, new_val); + } else { + pr_info("Switching USB to high-speed mode: " + "0x%08x: 0x%08x -> 0x%08x\n", + musb_power, val, new_val); + } + aw_fel_write(dev, arm_code, soc_info->scratch_addr, sizeof(arm_code)); + aw_fel_execute_may_disconnect(dev, soc_info->scratch_addr); + feldev_mark_disconnected(dev); + + return true; +} + +static void aw_apply_smc_workaround(feldev_handle *dev); + +static bool sid_is_zero(const uint32_t sid[4]) +{ + return !(sid[0] | sid[1] | sid[2] | sid[3]); +} + +static void format_sid(char *sid_str, const uint32_t sid[4]) +{ + snprintf(sid_str, SID_STR_SIZE, "%08x:%08x:%08x:%08x", + sid[0], sid[1], sid[2], sid[3]); +} + +static bool read_device_sid(feldev_handle *dev, char *sid_str) +{ + uint32_t sid[4]; + + if (fel_read_sid(dev, sid, 0, sizeof(sid), false) < 0) + return false; + if (sid_is_zero(sid)) + return false; + + format_sid(sid_str, sid); + return true; +} + +static void sleep_ms(unsigned int ms) +{ + struct timespec req = { + .tv_sec = ms / 1000, + .tv_nsec = (ms % 1000) * 1000000, + }; + + while (nanosleep(&req, &req) && errno == EINTR) + ; +} + void aw_fel_print_sid(feldev_handle *dev, bool force_workaround) { uint32_t key[4]; + char sid[SID_STR_SIZE]; soc_info_t *soc_info = dev->soc_info; if (!soc_info->sid_base) { @@ -471,9 +586,8 @@ void aw_fel_print_sid(feldev_handle *dev, bool force_workaround) } fel_read_sid(dev, key, 0, sizeof(key), force_workaround); - /* output SID in "xxxxxxxx:xxxxxxxx:xxxxxxxx:xxxxxxxx" format */ - for (unsigned i = 0; i <= 3; i++) - printf("%08x%c", key[i], i < 3 ? ':' : '\n'); + format_sid(sid, key); + puts(sid); } void aw_fel_dump_sid(feldev_handle *dev) @@ -1346,14 +1460,26 @@ void aw_rmr_request(feldev_handle *dev, uint32_t entry_point, bool aarch64) } uint32_t rmr_mode = (1 << 1) | (aarch64 ? 1 : 0); /* RR, AA64 flag */ + uint32_t usb_power = soc_info->usb_musb_base ? + soc_info->usb_musb_base + MUSB_POWER_OFFSET : 0; + uint32_t arm_code[] = { - htole32(0xe59f0028), /* ldr r0, [rvbar_reg] */ - htole32(0xe59f1028), /* ldr r1, [entry_point] */ + htole32(0xe59f0048), /* ldr r0, [rvbar_reg] */ + htole32(0xe59f1048), /* ldr r1, [entry_point] */ htole32(0xe5801000), /* str r1, [r0] */ htole32(0xf57ff04f), /* dsb sy */ htole32(0xf57ff06f), /* isb sy */ - htole32(0xe59f101c), /* ldr r1, [rmr_mode] */ + htole32(0xe59f003c), /* ldr r0, [usb_power] */ + htole32(0xe3500000), /* cmp r0, #0 */ + htole32(0x0a000004), /* beq rmr_request */ + htole32(0xe5d01000), /* ldrb r1, [r0] */ + htole32(0xe3c11040), /* bic r1, r1, #0x40 */ + htole32(0xe5c01000), /* strb r1, [r0] */ + htole32(0xf57ff04f), /* dsb sy */ + htole32(0xf57ff06f), /* isb sy */ + + htole32(0xe59f1020), /* ldr r1, [rmr_mode] */ htole32(0xee1c0f50), /* mrc 15, 0, r0, cr12, cr0, {2}*/ htole32(0xe1800001), /* orr r0, r0, r1 */ htole32(0xee0c0f50), /* mcr 15, 0, r0, cr12, cr0, {2}*/ @@ -1364,6 +1490,7 @@ void aw_rmr_request(feldev_handle *dev, uint32_t entry_point, bool aarch64) htole32(rvbar_reg), htole32(entry_point), + htole32(usb_power), htole32(rmr_mode) }; /* scratch buffer setup: transfers ARM code and parameter values */ @@ -1373,6 +1500,7 @@ void aw_rmr_request(feldev_handle *dev, uint32_t entry_point, bool aarch64) "and request warm reset with RMR mode %u...", entry_point, rvbar_reg, rmr_mode); aw_fel_execute(dev, soc_info->scratch_addr); + feldev_mark_disconnected(dev); pr_info(" done.\n"); } @@ -1437,15 +1565,17 @@ static void felusb_list_devices(void) { size_t devices; /* FEL device count */ feldev_list_entry *list, *entry; + char sid[SID_STR_SIZE]; list = list_fel_devices(&devices); for (entry = list; entry->soc_version.soc_id; entry++) { printf("USB device %03d:%03d Allwinner %-8s", entry->busnum, entry->devnum, entry->soc_name); /* output SID only if non-zero */ - if (entry->SID[0] | entry->SID[1] | entry->SID[2] | entry->SID[3]) - printf("%08x:%08x:%08x:%08x", - entry->SID[0], entry->SID[1], entry->SID[2], entry->SID[3]); + if (!sid_is_zero(entry->SID)) { + format_sid(sid, entry->SID); + printf("%s", sid); + } putchar('\n'); } free(list); @@ -1457,22 +1587,61 @@ static void felusb_list_devices(void) exit(devices > 0 ? EXIT_SUCCESS : EXIT_FAILURE); } -static void select_by_sid(const char *sid_arg, int *busnum, int *devnum) +static bool select_by_sid(const char *sid_arg, int *busnum, int *devnum) { - char sid[36]; + char sid[SID_STR_SIZE]; feldev_list_entry *list, *entry; + bool found = false; + + *busnum = *devnum = -1; list = list_fel_devices(NULL); for (entry = list; entry->soc_version.soc_id; entry++) { - snprintf(sid, sizeof(sid), "%08x:%08x:%08x:%08x", - entry->SID[0], entry->SID[1], entry->SID[2], entry->SID[3]); + if (sid_is_zero(entry->SID)) + continue; + format_sid(sid, entry->SID); if (strcmp(sid, sid_arg) == 0) { *busnum = entry->busnum; *devnum = entry->devnum; + found = true; break; } } free(list); + + return found; +} + +static bool wait_for_sid(const char *sid_arg, int *busnum, int *devnum) +{ + unsigned int waited = 0; + + while (waited <= FEL_REENUM_TIMEOUT_MS) { + if (select_by_sid(sid_arg, busnum, devnum)) + return true; + sleep_ms(FEL_REENUM_RETRY_MS); + waited += FEL_REENUM_RETRY_MS; + } + + return false; +} + +static bool wait_for_fel_device(void) +{ + unsigned int waited = 0; + + while (waited <= FEL_REENUM_TIMEOUT_MS) { + size_t devices = 0; + feldev_list_entry *list = list_fel_devices(&devices); + + free(list); + if (devices > 0) + return true; + sleep_ms(FEL_REENUM_RETRY_MS); + waited += FEL_REENUM_RETRY_MS; + } + + return false; } void usage(const char *cmd) { @@ -1481,6 +1650,7 @@ void usage(const char *cmd) { " -h, --help Print this usage summary and exit\n" " -v, --verbose Verbose logging\n" " -p, --progress \"write\" transfers show a progress bar\n" + " --no-high-speed Disable high-speed USB\n" " -l, --list Enumerate all (USB) FEL devices and exit\n" " -d, --dev bus:devnum Use specific USB bus and device number\n" " --sid SID Select device by SID key (exact match)\n" @@ -1534,9 +1704,12 @@ int main(int argc, char **argv) bool pflag_active = false; /* -p switch, causing "write" to output progress */ bool device_list = false; /* -l switch, prints device list and exits */ bool socs_list = false; /* list all supported SoCs and exit */ + bool high_speed = true; feldev_handle *handle; int busnum = -1, devnum = -1; char *sid_arg = NULL; + char reenum_sid[SID_STR_SIZE]; + const char *reenum_sid_arg; if (argc <= 1) usage(argv[0]); @@ -1549,6 +1722,8 @@ int main(int argc, char **argv) verbose = true; else if (strcmp(argv[1], "--progress") == 0 || strcmp(argv[1], "-p") == 0) pflag_active = true; + else if (strcmp(argv[1], "--no-high-speed") == 0) + high_speed = false; else if (strcmp(argv[1], "--list") == 0 || strcmp(argv[1], "-l") == 0 || strcmp(argv[1], "list") == 0) device_list = true; @@ -1603,8 +1778,7 @@ int main(int argc, char **argv) } if (sid_arg) { /* try to set busnum and devnum according to "--sid" option */ - select_by_sid(sid_arg, &busnum, &devnum); - if (busnum <= 0 || devnum <= 0) + if (!select_by_sid(sid_arg, &busnum, &devnum)) pr_fatal("No matching FEL device found for SID '%s'\n", sid_arg); pr_info("Selecting FEL device %03d:%03d by SID\n", busnum, devnum); @@ -1616,7 +1790,41 @@ int main(int argc, char **argv) */ handle = feldev_open(busnum, devnum, AW_USB_VENDOR_ID, AW_USB_PRODUCT_ID); - /* Some SoCs need the SMC workaround to enter secure state */ + fel_usb_high_speed_enabled = high_speed && + handle->soc_info->usb_musb_base != 0; + reenum_sid_arg = sid_arg; + if (fel_usb_high_speed_enabled && !reenum_sid_arg && + read_device_sid(handle, reenum_sid)) { + reenum_sid_arg = reenum_sid; + pr_info("Tracking FEL device by SID %s\n", reenum_sid_arg); + } + if (high_speed && aw_fel_switch_usb_high_speed(handle)) { + feldev_done(handle); + + /* + * Re-enumeration changes the USB device number, so a previous + * bus:devnum selection can't be reused. + */ + if (reenum_sid_arg) { + if (!wait_for_sid(reenum_sid_arg, &busnum, &devnum)) + pr_fatal("No matching FEL device found for SID '%s'\n", + reenum_sid_arg); + pr_info("Selecting FEL device %03d:%03d by SID\n", + busnum, devnum); + } else if (busnum > 0 || devnum > 0) { + pr_error("Warning: re-opening the first FEL device after USB re-enumeration.\n"); + if (!wait_for_fel_device()) + pr_fatal("No FEL device found after USB re-enumeration\n"); + busnum = devnum = -1; + } + + handle = feldev_open(busnum, devnum, AW_USB_VENDOR_ID, + AW_USB_PRODUCT_ID); + fel_usb_high_speed_enabled = high_speed && + handle->soc_info->usb_musb_base != 0; + } + + /* Some SoCs need the SMC workaround to enter secure state. */ aw_apply_smc_workaround(handle); /* Handle command-style arguments, in order of appearance */ @@ -1740,8 +1948,10 @@ int main(int argc, char **argv) pr_info("Starting U-Boot (0x%08X).\n", uboot_entry); if (enter_in_aarch64) aw_rmr_request(handle, uboot_entry, true); - else + else { aw_fel_execute(handle, uboot_entry); + feldev_mark_disconnected(handle); + } } feldev_done(handle); diff --git a/fel_lib.c b/fel_lib.c index b667dd16c..f2c30e136 100644 --- a/fel_lib.c +++ b/fel_lib.c @@ -32,6 +32,7 @@ #include #define USB_TIMEOUT 10000 /* 10 seconds */ +#define USB_DISCONNECT_TIMEOUT 250 /* 250 milliseconds */ static bool fel_lib_initialized = false; @@ -112,6 +113,45 @@ static void usb_bulk_recv(libusb_device_handle *usb, int ep, void *data, } } +static bool is_disconnect_error(int rc) +{ + return rc == LIBUSB_ERROR_IO || rc == LIBUSB_ERROR_NO_DEVICE || + rc == LIBUSB_ERROR_NOT_FOUND || rc == LIBUSB_ERROR_TIMEOUT || + rc == LIBUSB_ERROR_OVERFLOW || rc == LIBUSB_ERROR_PIPE; +} + +static bool usb_bulk_send_may_disconnect(libusb_device_handle *usb, int ep, + const void *data, size_t length) +{ + int rc, sent; + + rc = libusb_bulk_transfer(usb, ep, (void *)data, length, &sent, + USB_DISCONNECT_TIMEOUT); + if (rc) { + if (is_disconnect_error(rc)) + return false; + usb_error(rc, "usb_bulk_send()", 2); + } + + return sent == (int)length; +} + +static bool usb_bulk_recv_may_disconnect(libusb_device_handle *usb, int ep, + void *data, int length) +{ + int rc, recv; + + rc = libusb_bulk_transfer(usb, ep, data, length, &recv, + USB_DISCONNECT_TIMEOUT); + if (rc) { + if (is_disconnect_error(rc)) + return false; + usb_error(rc, "usb_bulk_recv()", 2); + } + + return recv == length; +} + struct aw_usb_request { char signature[8]; uint32_t length; @@ -150,6 +190,21 @@ static void aw_send_usb_request(feldev_handle *dev, int type, int length) &req, sizeof(req), false); } +static bool aw_send_usb_request_may_disconnect(feldev_handle *dev, int type, + int length) +{ + struct aw_usb_request req = { + .signature = "AWUC", + .request = htole16(type), + .length = htole32(length), + .unknown1 = htole32(0x0c000000) + }; + req.length2 = req.length; + return usb_bulk_send_may_disconnect(dev->usb->handle, + dev->usb->endpoint_out, + &req, sizeof(req)); +} + static void aw_read_usb_response(feldev_handle *dev) { char buf[13]; @@ -158,6 +213,29 @@ static void aw_read_usb_response(feldev_handle *dev) assert(strcmp(buf, "AWUS") == 0); } +static bool aw_read_usb_response_may_disconnect(feldev_handle *dev) +{ + char buf[13]; + + if (!usb_bulk_recv_may_disconnect(dev->usb->handle, + dev->usb->endpoint_in, + buf, sizeof(buf))) + return false; + assert(strcmp(buf, "AWUS") == 0); + return true; +} + +static bool aw_usb_read_may_disconnect(feldev_handle *dev, void *data, + size_t len) +{ + if (!aw_send_usb_request_may_disconnect(dev, AW_USB_READ, len)) + return false; + if (!usb_bulk_recv_may_disconnect(dev->usb->handle, + dev->usb->endpoint_in, data, len)) + return false; + return aw_read_usb_response_may_disconnect(dev); +} + static void aw_usb_write(feldev_handle *dev, const void *data, size_t len, bool progress) { @@ -167,6 +245,17 @@ static void aw_usb_write(feldev_handle *dev, const void *data, size_t len, aw_read_usb_response(dev); } +static bool aw_usb_write_may_disconnect(feldev_handle *dev, const void *data, + size_t len) +{ + if (!aw_send_usb_request_may_disconnect(dev, AW_USB_WRITE, len)) + return false; + if (!usb_bulk_send_may_disconnect(dev->usb->handle, + dev->usb->endpoint_out, data, len)) + return false; + return aw_read_usb_response_may_disconnect(dev); +} + static void aw_usb_read(feldev_handle *dev, void *data, size_t len) { aw_send_usb_request(dev, AW_USB_READ, len); @@ -185,19 +274,33 @@ static void aw_send_fel_request(feldev_handle *dev, int type, aw_usb_write(dev, &req, sizeof(req), false); } +static bool aw_send_fel_request_may_disconnect(feldev_handle *dev, int type, + uint32_t addr, uint32_t length) +{ + struct aw_fel_request req = { + .request = htole32(type), + .address = htole32(addr), + .length = htole32(length) + }; + + return aw_usb_write_may_disconnect(dev, &req, sizeof(req)); +} + static void aw_read_fel_status(feldev_handle *dev) { char buf[8]; aw_usb_read(dev, buf, sizeof(buf)); } -/* AW_FEL_VERSION request */ -static void aw_fel_get_version(feldev_handle *dev, struct aw_fel_version *buf) +static bool aw_read_fel_status_may_disconnect(feldev_handle *dev) { - aw_send_fel_request(dev, AW_FEL_VERSION, 0, 0); - aw_usb_read(dev, buf, sizeof(*buf)); - aw_read_fel_status(dev); + char buf[8]; + + return aw_usb_read_may_disconnect(dev, buf, sizeof(buf)); +} +static void aw_normalize_fel_version(struct aw_fel_version *buf) +{ buf->soc_id = (le32toh(buf->soc_id) >> 8) & 0xFFFF; buf->unknown_0a = le32toh(buf->unknown_0a); buf->protocol = le32toh(buf->protocol); @@ -206,6 +309,28 @@ static void aw_fel_get_version(feldev_handle *dev, struct aw_fel_version *buf) buf->pad[1] = le32toh(buf->pad[1]); } +/* AW_FEL_VERSION request */ +static void aw_fel_get_version(feldev_handle *dev, struct aw_fel_version *buf) +{ + aw_send_fel_request(dev, AW_FEL_VERSION, 0, 0); + aw_usb_read(dev, buf, sizeof(*buf)); + aw_read_fel_status(dev); + aw_normalize_fel_version(buf); +} + +static bool aw_fel_get_version_may_disconnect(feldev_handle *dev, + struct aw_fel_version *buf) +{ + if (!aw_send_fel_request_may_disconnect(dev, AW_FEL_VERSION, 0, 0)) + return false; + if (!aw_usb_read_may_disconnect(dev, buf, sizeof(*buf))) + return false; + if (!aw_read_fel_status_may_disconnect(dev)) + return false; + aw_normalize_fel_version(buf); + return true; +} + /* AW_FEL_1_READ request */ void aw_fel_read(feldev_handle *dev, uint32_t offset, void *buf, size_t len) { @@ -232,6 +357,12 @@ void aw_fel_execute(feldev_handle *dev, uint32_t offset) aw_read_fel_status(dev); } +void aw_fel_execute_may_disconnect(feldev_handle *dev, uint32_t offset) +{ + if (aw_send_fel_request_may_disconnect(dev, AW_FEL_1_EXEC, offset, 0)) + (void)aw_read_fel_status_may_disconnect(dev); +} + static void aw_disable_icache(feldev_handle *dev) { soc_info_t *soc_info = dev->soc_info; @@ -670,7 +801,7 @@ static int feldev_get_endpoint(feldev_handle *dev) } /* claim USB interface associated with the libusb handle for a FEL device */ -static void feldev_claim(feldev_handle *dev) +static int feldev_claim(feldev_handle *dev) { int rc = libusb_claim_interface(dev->usb->handle, 0); #if defined(__linux__) @@ -681,11 +812,9 @@ static void feldev_claim(feldev_handle *dev) } #endif if (rc) - usb_error(rc, "libusb_claim_interface()", 1); + return rc; - rc = feldev_get_endpoint(dev); - if (rc) - usb_error(rc, "FAILED to get FEL mode endpoint addresses!", 1); + return feldev_get_endpoint(dev); } /* release USB interface associated with the libusb handle for a FEL device */ @@ -698,9 +827,9 @@ static void feldev_release(feldev_handle *dev) #endif } -/* open handle to desired FEL device */ -feldev_handle *feldev_open(int busnum, int devnum, - uint16_t vendor_id, uint16_t product_id) +static feldev_handle *feldev_open_internal(int busnum, int devnum, + uint16_t vendor_id, + uint16_t product_id, bool fatal) { if (!fel_lib_initialized) /* if not already done: auto-initialize */ feldev_init(); @@ -723,6 +852,8 @@ feldev_handle *feldev_open(int busnum, int devnum, */ result->usb->handle = libusb_open_device_with_vid_pid(NULL, vendor_id, product_id); if (!result->usb->handle) { + if (!fatal) + goto err_free; switch (errno) { case EACCES: fprintf(stderr, "ERROR: You don't have permission to access Allwinner USB FEL device\n"); @@ -757,28 +888,61 @@ feldev_handle *feldev_open(int busnum, int devnum, } /* open handle to this specific device (incrementing its refcount) */ rc = libusb_open(list[i], &result->usb->handle); - if (rc != 0) + if (rc != 0) { + if (!fatal) { + libusb_free_device_list(list, true); + goto err_free; + } usb_error(rc, "libusb_open()", 1); + } break; } } libusb_free_device_list(list, true); if (!found) { + if (!fatal) + goto err_free; fprintf(stderr, "ERROR: Bus %03d Device %03d not found in libusb device list\n", busnum, devnum); exit(1); } } - feldev_claim(result); /* claim interface, detect USB endpoints */ + int rc = feldev_claim(result); /* claim interface, detect USB endpoints */ + if (rc) { + if (!fatal) + goto err_close; + usb_error(rc, "libusb_claim_interface()", 1); + } /* retrieve BROM version and SoC information */ - aw_fel_get_version(result, &result->soc_version); + if (fatal) + aw_fel_get_version(result, &result->soc_version); + else if (!aw_fel_get_version_may_disconnect(result, + &result->soc_version)) + goto err_release; get_soc_name_from_id(result->soc_name, result->soc_version.soc_id); result->soc_info = get_soc_info_from_version(&result->soc_version); return result; + +err_release: + feldev_release(result); +err_close: + if (result->usb->handle) + libusb_close(result->usb->handle); +err_free: + free(result->usb); + free(result); + return NULL; +} + +/* open handle to desired FEL device */ +feldev_handle *feldev_open(int busnum, int devnum, + uint16_t vendor_id, uint16_t product_id) +{ + return feldev_open_internal(busnum, devnum, vendor_id, product_id, true); } /* close FEL device (optional, dev may be NULL) */ @@ -786,13 +950,20 @@ void feldev_close(feldev_handle *dev) { if (dev) { if (dev->usb->handle) { - feldev_release(dev); + if (!dev->disconnected) + feldev_release(dev); libusb_close(dev->usb->handle); } free(dev->usb); /* release memory allocated for felusb_handle */ } } +void feldev_mark_disconnected(feldev_handle *dev) +{ + if (dev) + dev->disconnected = true; +} + void feldev_init(void) { int rc = libusb_init(NULL); @@ -803,9 +974,14 @@ void feldev_init(void) void feldev_done(feldev_handle *dev) { + bool disconnected = dev && dev->disconnected; + feldev_close(dev); free(dev); - if (fel_lib_initialized) libusb_exit(NULL); + if (fel_lib_initialized && !disconnected) { + libusb_exit(NULL); + fel_lib_initialized = false; + } } /* @@ -846,13 +1022,19 @@ feldev_list_entry *list_fel_devices(size_t *count) || desc.idProduct != AW_USB_PRODUCT_ID) continue; /* not an Allwinner FEL device */ + int busnum = libusb_get_bus_number(usb[i]); + int devnum = libusb_get_device_address(usb[i]); + + dev = feldev_open_internal(busnum, devnum, + AW_USB_VENDOR_ID, + AW_USB_PRODUCT_ID, false); + if (!dev) + continue; + entry = list + devices; /* pointer to current feldev_list_entry */ devices += 1; - - entry->busnum = libusb_get_bus_number(usb[i]); - entry->devnum = libusb_get_device_address(usb[i]); - dev = feldev_open(entry->busnum, entry->devnum, - AW_USB_VENDOR_ID, AW_USB_PRODUCT_ID); + entry->busnum = busnum; + entry->devnum = devnum; /* copy relevant fields */ entry->soc_version = dev->soc_version; diff --git a/fel_lib.h b/fel_lib.h index 68b1ab893..eabf8f775 100644 --- a/fel_lib.h +++ b/fel_lib.h @@ -34,6 +34,7 @@ typedef struct { struct aw_fel_version soc_version; soc_name_t soc_name; soc_info_t *soc_info; + bool disconnected; } feldev_handle; /* list_fel_devices() will return an array of this type */ @@ -49,6 +50,7 @@ typedef struct { void feldev_init(void); void feldev_done(feldev_handle *dev); +void feldev_mark_disconnected(feldev_handle *dev); feldev_handle *feldev_open(int busnum, int devnum, uint16_t vendor_id, uint16_t product_id); @@ -63,6 +65,7 @@ void aw_fel_write(feldev_handle *dev, const void *buf, uint32_t offset, size_t l void aw_fel_write_buffer(feldev_handle *dev, const void *buf, uint32_t offset, size_t len, bool progress); void aw_fel_execute(feldev_handle *dev, uint32_t offset); +void aw_fel_execute_may_disconnect(feldev_handle *dev, uint32_t offset); void fel_readl_n(feldev_handle *dev, uint32_t addr, uint32_t *dst, size_t count); void fel_writel_n(feldev_handle *dev, uint32_t addr, uint32_t *src, size_t count); diff --git a/soc_info.c b/soc_info.c index 492804c39..d0e9d426c 100644 --- a/soc_info.c +++ b/soc_info.c @@ -388,6 +388,8 @@ soc_info_t soc_info_table[] = { .sram_size = 32 * 1024, .sid_base = 0x01C23800, .sid_sections = generic_2k_sid_maps, + .usb_musb_base= 0x01c19000, + .fel_endpoint_table_addr = 0x00006ed8, .watchdog = &wd_h3_compat, },{ .soc_id = 0x1689, /* Allwinner A64 */ @@ -401,6 +403,8 @@ soc_info_t soc_info_table[] = { .sid_offset = 0x200, .sid_sections = h3_sid_maps, .rvbar_reg = 0x017000A0, + .usb_musb_base= 0x01c19000, + .fel_endpoint_table_addr = 0x00016ecc, /* Check L.NOP in the OpenRISC reset vector */ .needs_smc_workaround_if_zero_word_at_addr = 0x40004, .smc_workaround = SMC_WORKAROUND_DIRECT_SMC, @@ -450,6 +454,8 @@ soc_info_t soc_info_table[] = { .sid_offset = 0x200, .sid_fix = true, .sid_sections = h3_sid_maps, + .usb_musb_base= 0x01c19000, + .fel_endpoint_table_addr = 0x00006ecc, /* Check L.NOP in the OpenRISC reset vector */ .needs_smc_workaround_if_zero_word_at_addr = 0x40004, .smc_workaround = SMC_WORKAROUND_DIRECT_SMC, @@ -489,6 +495,8 @@ soc_info_t soc_info_table[] = { .sid_offset = 0x200, .sid_sections = h3_sid_maps, .rvbar_reg = 0x017000A0, + .usb_musb_base= 0x01c19000, + .fel_endpoint_table_addr = 0x00016ecc, /* Check L.NOP in the OpenRISC reset vector */ .needs_smc_workaround_if_zero_word_at_addr = 0x40004, .smc_workaround = SMC_WORKAROUND_DIRECT_SMC, @@ -516,6 +524,8 @@ soc_info_t soc_info_table[] = { .sid_offset = 0x200, .sid_sections = generic_2k_sid_maps, .rvbar_reg = 0x09010040, + .usb_musb_base= 0x05100000, + .fel_endpoint_table_addr = 0x00026ecc, .watchdog = &wd_h6_compat, },{ .soc_id = 0x1728, /* Allwinner H6 */ @@ -529,6 +539,8 @@ soc_info_t soc_info_table[] = { .sid_offset = 0x200, .sid_sections = h6_sid_maps, .rvbar_reg = 0x09010040, + .usb_musb_base= 0x05100000, + .fel_endpoint_table_addr = 0x00026ecc, /* Check L.NOP in the OpenRISC reset vector */ .needs_smc_workaround_if_zero_word_at_addr = 0x100004, .smc_workaround = SMC_WORKAROUND_DIRECT_SMC, @@ -571,6 +583,8 @@ soc_info_t soc_info_table[] = { .rvbar_reg = 0x09010040, .rvbar_reg_alt= 0x08100040, .ver_reg = 0x03000024, + .usb_musb_base= 0x05100000, + .fel_endpoint_table_addr = 0x000550f0, .needs_smc_workaround_if_zero_word_at_addr = 0x03006240, .secure_boot_fuse_offset = 0xa0, .smc_workaround = SMC_WORKAROUND_SECURE_SVC_SMC_THUNK, @@ -601,6 +615,8 @@ soc_info_t soc_info_table[] = { .sid_base = 0x03006000, .sid_offset = 0x200, .sid_sections = generic_2k_sid_maps, + .usb_musb_base= 0x04100000, + .fel_endpoint_table_addr = 0x000402e8, .icache_fix = true, .watchdog = &wd_v853_compat, },{ @@ -627,6 +643,8 @@ soc_info_t soc_info_table[] = { .sid_base = 0x03006000, .sid_offset = 0x200, .sid_sections = generic_2k_sid_maps, + .usb_musb_base= 0x05100000, + .fel_endpoint_table_addr = 0x00026efc, .watchdog = &wd_h6_compat, },{ .soc_id = 0x1890, /* Allwinner A523 */ @@ -640,6 +658,8 @@ soc_info_t soc_info_table[] = { .sid_offset = 0x200, .sid_sections = generic_2k_sid_maps, .rvbar_reg = 0x08000040, + .usb_musb_base= 0x04100000, + .fel_endpoint_table_addr = 0x0005e6ec, .icache_fix = true, .watchdog = &wd_a523_compat, },{ @@ -654,6 +674,8 @@ soc_info_t soc_info_table[] = { .sid_offset = 0x200, .sid_sections = generic_2k_sid_maps, .rvbar_reg = 0x08100040, + .usb_musb_base= 0x05100000, + .fel_endpoint_table_addr = 0x00041ff0, .needs_smc_workaround_if_zero_word_at_addr = 0x100004, .smc_workaround = SMC_WORKAROUND_DIRECT_SMC, .watchdog = &wd_h6_compat, diff --git a/soc_info.h b/soc_info.h index a3c3e14a5..3aaf4fc90 100644 --- a/soc_info.h +++ b/soc_info.h @@ -147,6 +147,8 @@ typedef struct { uint32_t rvbar_reg; /* MMIO address of RVBARADDR0_L register */ 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 */ const watchdog_info *watchdog; /* Used for reset */ bool sid_fix; /* Use SID workaround (read via register) */ /* Use I$ workaround (disable I$ before first write to prevent stale thunk */ diff --git a/sunxi-fel.1 b/sunxi-fel.1 index 3923bc0e2..87d595307 100644 --- a/sunxi-fel.1 +++ b/sunxi-fel.1 @@ -40,6 +40,11 @@ Enable verbose logging. "write" transfers show a progress bar. .RE .sp +.B \-\-no-high-speed +.RS 4 +Disable automatic high-speed USB switching. +.RE +.sp .B \-l, \-\-list .RS 4 Enumerate all (USB) FEL devices and exit. From e6b3d27d64dc8fc5cc5b321ac4fff5c813aaac66 Mon Sep 17 00:00:00 2001 From: James Hilliard Date: Sat, 16 May 2026 00:21:27 -0600 Subject: [PATCH 5/5] sunxi-fel: use RX DMA for FEL writes 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 and shadow-page addresses 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, 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. Use a 192 KiB DMA request cap; 256 KiB requests can time out while the host is writing. 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 --- Makefile | 4 +- fel.c | 16 +- fel_lib.c | 84 ++++++++-- fel_lib.h | 4 + soc_info.c | 90 ++++++++++ soc_info.h | 10 ++ sunxi-fel.1 | 2 +- thunks/Makefile | 14 +- thunks/README.md | 8 +- thunks/fel-rx-dma-thunk.S | 338 ++++++++++++++++++++++++++++++++++++++ thunks/fel-rx-dma-thunk.h | 238 +++++++++++++++++++++++++++ 11 files changed, 777 insertions(+), 31 deletions(-) create mode 100644 thunks/fel-rx-dma-thunk.S create mode 100644 thunks/fel-rx-dma-thunk.h diff --git a/Makefile b/Makefile index 8af1e63cb..639b1bcad 100644 --- a/Makefile +++ b/Makefile @@ -163,8 +163,10 @@ PROGRESS := progress.c progress.h SOC_INFO := soc_info.c soc_info.h FEL_LIB := fel_lib.c fel_lib.h SPI_FLASH:= fel-spiflash.c fel-spiflash.h fel-remotefunc-spi-data-transfer.h +FEL_THUNKS := thunks/fel-to-spl-thunk.h thunks/fel-to-secure-svc-smc-thunk.h +FEL_THUNKS += thunks/fel-rx-dma-thunk.h -sunxi-fel: fel.c fit_image.c thunks/fel-to-spl-thunk.h thunks/fel-to-secure-svc-smc-thunk.h $(PROGRESS) $(SOC_INFO) $(FEL_LIB) $(SPI_FLASH) +sunxi-fel: fel.c fit_image.c $(FEL_THUNKS) $(PROGRESS) $(SOC_INFO) $(FEL_LIB) $(SPI_FLASH) $(CC) $(HOST_CFLAGS) $(LIBUSB_CFLAGS) $(ZLIB_CFLAGS) $(LIBFDT_CFLAGS) $(LDFLAGS) -o $@ \ $(filter %.c,$^) $(LIBS) $(LIBUSB_LIBS) $(ZLIB_LIBS) $(LIBFDT_LIBS) diff --git a/fel.c b/fel.c index ce47ecc6a..03a9c7d6b 100644 --- a/fel.c +++ b/fel.c @@ -36,7 +36,6 @@ bool verbose = false; /* If set, makes the 'fel' tool more talkative */ static uint32_t uboot_entry = 0; /* entry point (address) of U-Boot */ static uint32_t uboot_size = 0; /* size of U-Boot binary */ static bool enter_in_aarch64 = false; -static bool fel_usb_high_speed_enabled = false; #define FEL_REENUM_TIMEOUT_MS 5000 #define FEL_REENUM_RETRY_MS 100 @@ -1118,6 +1117,7 @@ uint32_t aw_fel_write_and_execute_spl(feldev_handle *dev, uint8_t *buf, size_t l /* re-enable the MMU if it was enabled by BROM */ if (tt != NULL) aw_restore_and_enable_mmu(dev, soc_info, tt); + dev->dram_ready = true; return spl_len; } @@ -1650,7 +1650,7 @@ void usage(const char *cmd) { " -h, --help Print this usage summary and exit\n" " -v, --verbose Verbose logging\n" " -p, --progress \"write\" transfers show a progress bar\n" - " --no-high-speed Disable high-speed USB\n" + " --no-high-speed Disable high-speed USB and fast writes\n" " -l, --list Enumerate all (USB) FEL devices and exit\n" " -d, --dev bus:devnum Use specific USB bus and device number\n" " --sid SID Select device by SID key (exact match)\n" @@ -1790,10 +1790,11 @@ int main(int argc, char **argv) */ handle = feldev_open(busnum, devnum, AW_USB_VENDOR_ID, AW_USB_PRODUCT_ID); - fel_usb_high_speed_enabled = high_speed && - handle->soc_info->usb_musb_base != 0; + handle->verbose = verbose; + handle->usb_high_speed = high_speed && + handle->soc_info->usb_musb_base != 0; reenum_sid_arg = sid_arg; - if (fel_usb_high_speed_enabled && !reenum_sid_arg && + if (handle->usb_high_speed && !reenum_sid_arg && read_device_sid(handle, reenum_sid)) { reenum_sid_arg = reenum_sid; pr_info("Tracking FEL device by SID %s\n", reenum_sid_arg); @@ -1820,8 +1821,9 @@ int main(int argc, char **argv) handle = feldev_open(busnum, devnum, AW_USB_VENDOR_ID, AW_USB_PRODUCT_ID); - fel_usb_high_speed_enabled = high_speed && - handle->soc_info->usb_musb_base != 0; + handle->verbose = verbose; + handle->usb_high_speed = high_speed && + handle->soc_info->usb_musb_base != 0; } /* Some SoCs need the SMC workaround to enter secure state. */ diff --git a/fel_lib.c b/fel_lib.c index f2c30e136..4c3771614 100644 --- a/fel_lib.c +++ b/fel_lib.c @@ -22,6 +22,7 @@ **********************************************************************/ #include "portable_endian.h" +#include "common.h" #include "fel_lib.h" #include @@ -70,19 +71,25 @@ static void usb_error(int rc, const char *caption, int exitcode) * timeout, and "slow" transfers take place at approx. 64 KiB/sec - so we can * expect the maximum chunk being transmitted within 8 seconds or less. */ -static const int AW_USB_MAX_BULK_SEND = 512 * 1024; /* 512 KiB per bulk request */ +static const int AW_USB_MAX_BULK_SEND = 512 * 1024; +static const int AW_USB_PROGRESS_BULK_SEND = 128 * 1024; +static const int AW_USB_FAST_PROGRESS_BULK_SEND = 1024 * 1024; +#define DRAM_BASE 0x40000000 +#define DRAM_SIZE 0x80000000 +#define DRAM_END (DRAM_BASE + DRAM_SIZE) + +static uint32_t fel_rx_dma_thunk[] = { + #include "thunks/fel-rx-dma-thunk.h" +}; + +/* + * Keep progress updates frequent enough for slow BROM PIO transfers. Once an + * RX-DMA thunk is installed, 1 MiB chunks avoid host-side progress overhead. + */ static void usb_bulk_send(libusb_device_handle *usb, int ep, const void *data, - size_t length, bool progress) + size_t length, size_t max_chunk, bool progress) { - /* - * With no progress notifications, we'll use the maximum chunk size. - * Otherwise, it's useful to lower the size (have more chunks) to get - * more frequent status updates. 128 KiB per request seem suitable. - * (Worst case of "slow" transfers -> one update every two seconds.) - */ - size_t max_chunk = progress ? 128 * 1024 : AW_USB_MAX_BULK_SEND; - size_t chunk; int rc, sent; while (length > 0) { @@ -187,7 +194,7 @@ static void aw_send_usb_request(feldev_handle *dev, int type, int length) }; req.length2 = req.length; usb_bulk_send(dev->usb->handle, dev->usb->endpoint_out, - &req, sizeof(req), false); + &req, sizeof(req), AW_USB_MAX_BULK_SEND, false); } static bool aw_send_usb_request_may_disconnect(feldev_handle *dev, int type, @@ -239,9 +246,18 @@ static bool aw_usb_read_may_disconnect(feldev_handle *dev, void *data, static void aw_usb_write(feldev_handle *dev, const void *data, size_t len, bool progress) { + size_t max_chunk = AW_USB_MAX_BULK_SEND; + + if (progress) { + if (dev->rx_dma_patched) + max_chunk = AW_USB_FAST_PROGRESS_BULK_SEND; + else + max_chunk = AW_USB_PROGRESS_BULK_SEND; + } + aw_send_usb_request(dev, AW_USB_WRITE, len); usb_bulk_send(dev->usb->handle, dev->usb->endpoint_out, - data, len, progress); + data, len, max_chunk, progress); aw_read_usb_response(dev); } @@ -363,6 +379,49 @@ void aw_fel_execute_may_disconnect(feldev_handle *dev, uint32_t offset) (void)aw_read_fel_status_may_disconnect(dev); } +static void aw_fel_install_rx_dma_thunk(feldev_handle *dev) +{ + const fel_rx_dma_info *dma = &dev->soc_info->fel_rx_dma; + uint32_t params[6], param_offset; + size_t i; + + if (dev->rx_dma_patched || !dma->thunk_addr) + return; + + param_offset = fel_rx_dma_thunk[1]; + if (param_offset % sizeof(*fel_rx_dma_thunk) || + param_offset + sizeof(params) > sizeof(fel_rx_dma_thunk)) + pr_fatal("RX DMA thunk: bad parameter offset 0x%x\n", + param_offset); + + uint32_t thunk_buf[ARRAY_SIZE(fel_rx_dma_thunk)]; + + if (dev->verbose) + printf("Patching BROM RX FIFO copy to use DMA.\n"); + for (i = 0; i < sizeof(thunk_buf) / sizeof(*thunk_buf); i++) + thunk_buf[i] = htole32(fel_rx_dma_thunk[i]); + params[0] = htole32(dma->l1_tt_addr); + params[1] = htole32(dma->l2_tt_addr); + params[2] = htole32(dma->brom_hook_addr); + params[3] = htole32(dma->brom_hook_shadow_addr); + params[4] = htole32(dev->soc_info->usb_musb_base); + params[5] = htole32(dma->dma_max_len); + memcpy((uint8_t *)thunk_buf + param_offset, params, sizeof(params)); + + aw_fel_write(dev, thunk_buf, dma->thunk_addr, sizeof(thunk_buf)); + + aw_fel_execute(dev, dma->thunk_addr + fel_rx_dma_thunk[0]); + dev->rx_dma_patched = true; +} + +static void aw_fel_prepare_fast_dram_write(feldev_handle *dev, + uint32_t offset) +{ + if (dev->usb_high_speed && dev->dram_ready && + offset >= DRAM_BASE && offset < DRAM_END) + aw_fel_install_rx_dma_thunk(dev); +} + static void aw_disable_icache(feldev_handle *dev) { soc_info_t *soc_info = dev->soc_info; @@ -401,6 +460,7 @@ void aw_fel_write_buffer(feldev_handle *dev, const void *buf, uint32_t offset, if (len == 0) return; + aw_fel_prepare_fast_dram_write(dev, offset); aw_send_fel_request(dev, AW_FEL_1_WRITE, offset, len); aw_usb_write(dev, buf, len, progress); aw_read_fel_status(dev); diff --git a/fel_lib.h b/fel_lib.h index eabf8f775..6c3cf7c0b 100644 --- a/fel_lib.h +++ b/fel_lib.h @@ -35,6 +35,10 @@ typedef struct { soc_name_t soc_name; soc_info_t *soc_info; bool disconnected; + bool verbose; + bool usb_high_speed; + bool dram_ready; + bool rx_dma_patched; } feldev_handle; /* list_fel_devices() will return an array of this type */ diff --git a/soc_info.c b/soc_info.c index d0e9d426c..fa7b7e2d8 100644 --- a/soc_info.c +++ b/soc_info.c @@ -25,6 +25,8 @@ #include #include +#define FEL_RX_DMA_MAX_LEN 0x00030000 + /* * The FEL code from BROM in A10/A13/A20 sets up two stacks for itself. One * at 0x2000 (and growing down) for the IRQ handler. And another one at 0x7000 @@ -390,6 +392,14 @@ soc_info_t soc_info_table[] = { .sid_sections = generic_2k_sid_maps, .usb_musb_base= 0x01c19000, .fel_endpoint_table_addr = 0x00006ed8, + .fel_rx_dma = { + .thunk_addr = 0x0004e000, + .l1_tt_addr = 0x00048000, + .l2_tt_addr = 0x0004c000, + .brom_hook_addr = 0xffff2588, + .brom_hook_shadow_addr = 0x0004d000, + .dma_max_len = FEL_RX_DMA_MAX_LEN, + }, .watchdog = &wd_h3_compat, },{ .soc_id = 0x1689, /* Allwinner A64 */ @@ -405,6 +415,14 @@ soc_info_t soc_info_table[] = { .rvbar_reg = 0x017000A0, .usb_musb_base= 0x01c19000, .fel_endpoint_table_addr = 0x00016ecc, + .fel_rx_dma = { + .thunk_addr = 0x00022000, + .l1_tt_addr = 0x0002c000, + .l2_tt_addr = 0x00031000, + .brom_hook_addr = 0x00002670, + .brom_hook_shadow_addr = 0x00030000, + .dma_max_len = FEL_RX_DMA_MAX_LEN, + }, /* Check L.NOP in the OpenRISC reset vector */ .needs_smc_workaround_if_zero_word_at_addr = 0x40004, .smc_workaround = SMC_WORKAROUND_DIRECT_SMC, @@ -456,6 +474,14 @@ soc_info_t soc_info_table[] = { .sid_sections = h3_sid_maps, .usb_musb_base= 0x01c19000, .fel_endpoint_table_addr = 0x00006ecc, + .fel_rx_dma = { + .thunk_addr = 0x0000e000, + .l1_tt_addr = 0x00008000, + .l2_tt_addr = 0x0000c000, + .brom_hook_addr = 0xffff2680, + .brom_hook_shadow_addr = 0x0000d000, + .dma_max_len = FEL_RX_DMA_MAX_LEN, + }, /* Check L.NOP in the OpenRISC reset vector */ .needs_smc_workaround_if_zero_word_at_addr = 0x40004, .smc_workaround = SMC_WORKAROUND_DIRECT_SMC, @@ -497,6 +523,14 @@ soc_info_t soc_info_table[] = { .rvbar_reg = 0x017000A0, .usb_musb_base= 0x01c19000, .fel_endpoint_table_addr = 0x00016ecc, + .fel_rx_dma = { + .thunk_addr = 0x00022000, + .l1_tt_addr = 0x0002c000, + .l2_tt_addr = 0x00031000, + .brom_hook_addr = 0x0000205c, + .brom_hook_shadow_addr = 0x00030000, + .dma_max_len = FEL_RX_DMA_MAX_LEN, + }, /* Check L.NOP in the OpenRISC reset vector */ .needs_smc_workaround_if_zero_word_at_addr = 0x40004, .smc_workaround = SMC_WORKAROUND_DIRECT_SMC, @@ -526,6 +560,14 @@ soc_info_t soc_info_table[] = { .rvbar_reg = 0x09010040, .usb_musb_base= 0x05100000, .fel_endpoint_table_addr = 0x00026ecc, + .fel_rx_dma = { + .thunk_addr = 0x00022000, + .l1_tt_addr = 0x00038000, + .l2_tt_addr = 0x0003d000, + .brom_hook_addr = 0x000021ac, + .brom_hook_shadow_addr = 0x0003c000, + .dma_max_len = FEL_RX_DMA_MAX_LEN, + }, .watchdog = &wd_h6_compat, },{ .soc_id = 0x1728, /* Allwinner H6 */ @@ -541,6 +583,14 @@ soc_info_t soc_info_table[] = { .rvbar_reg = 0x09010040, .usb_musb_base= 0x05100000, .fel_endpoint_table_addr = 0x00026ecc, + .fel_rx_dma = { + .thunk_addr = 0x00022000, + .l1_tt_addr = 0x00038000, + .l2_tt_addr = 0x0003d000, + .brom_hook_addr = 0x00002300, + .brom_hook_shadow_addr = 0x0003c000, + .dma_max_len = FEL_RX_DMA_MAX_LEN, + }, /* Check L.NOP in the OpenRISC reset vector */ .needs_smc_workaround_if_zero_word_at_addr = 0x100004, .smc_workaround = SMC_WORKAROUND_DIRECT_SMC, @@ -585,6 +635,14 @@ soc_info_t soc_info_table[] = { .ver_reg = 0x03000024, .usb_musb_base= 0x05100000, .fel_endpoint_table_addr = 0x000550f0, + .fel_rx_dma = { + .thunk_addr = 0x00022000, + .l1_tt_addr = 0x0004c000, + .l2_tt_addr = 0x00051000, + .brom_hook_addr = 0x0000a17c, + .brom_hook_shadow_addr = 0x00050000, + .dma_max_len = FEL_RX_DMA_MAX_LEN, + }, .needs_smc_workaround_if_zero_word_at_addr = 0x03006240, .secure_boot_fuse_offset = 0xa0, .smc_workaround = SMC_WORKAROUND_SECURE_SVC_SMC_THUNK, @@ -617,6 +675,14 @@ soc_info_t soc_info_table[] = { .sid_sections = generic_2k_sid_maps, .usb_musb_base= 0x04100000, .fel_endpoint_table_addr = 0x000402e8, + .fel_rx_dma = { + .thunk_addr = 0x00022000, + .l1_tt_addr = 0x00038000, + .l2_tt_addr = 0x0003d000, + .brom_hook_addr = 0x0000aff0, + .brom_hook_shadow_addr = 0x0003c000, + .dma_max_len = FEL_RX_DMA_MAX_LEN, + }, .icache_fix = true, .watchdog = &wd_v853_compat, },{ @@ -645,6 +711,14 @@ soc_info_t soc_info_table[] = { .sid_sections = generic_2k_sid_maps, .usb_musb_base= 0x05100000, .fel_endpoint_table_addr = 0x00026efc, + .fel_rx_dma = { + .thunk_addr = 0x00022000, + .l1_tt_addr = 0x00038000, + .l2_tt_addr = 0x0003d000, + .brom_hook_addr = 0x000036ec, + .brom_hook_shadow_addr = 0x0003c000, + .dma_max_len = FEL_RX_DMA_MAX_LEN, + }, .watchdog = &wd_h6_compat, },{ .soc_id = 0x1890, /* Allwinner A523 */ @@ -660,6 +734,14 @@ soc_info_t soc_info_table[] = { .rvbar_reg = 0x08000040, .usb_musb_base= 0x04100000, .fel_endpoint_table_addr = 0x0005e6ec, + .fel_rx_dma = { + .thunk_addr = 0x00041000, + .l1_tt_addr = 0x00050000, + .l2_tt_addr = 0x00055000, + .brom_hook_addr = 0x00017574, + .brom_hook_shadow_addr = 0x00054000, + .dma_max_len = FEL_RX_DMA_MAX_LEN, + }, .icache_fix = true, .watchdog = &wd_a523_compat, },{ @@ -676,6 +758,14 @@ soc_info_t soc_info_table[] = { .rvbar_reg = 0x08100040, .usb_musb_base= 0x05100000, .fel_endpoint_table_addr = 0x00041ff0, + .fel_rx_dma = { + .thunk_addr = 0x00022000, + .l1_tt_addr = 0x00038000, + .l2_tt_addr = 0x0003d000, + .brom_hook_addr = 0x0000b698, + .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, .watchdog = &wd_h6_compat, diff --git a/soc_info.h b/soc_info.h index 3aaf4fc90..526618672 100644 --- a/soc_info.h +++ b/soc_info.h @@ -70,6 +70,15 @@ typedef struct { uint32_t size_bits; } sid_section; +typedef struct { + uint32_t thunk_addr; + uint32_t l1_tt_addr; + uint32_t l2_tt_addr; + uint32_t brom_hook_addr; + uint32_t brom_hook_shadow_addr; + uint32_t dma_max_len; +} fel_rx_dma_info; + #define SID_SECTION(_name, _offset, _size_bits) { \ .name = _name, \ .offset = _offset, \ @@ -149,6 +158,7 @@ typedef struct { 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 */ + fel_rx_dma_info fel_rx_dma; /* BROM RX FIFO copy DMA patch */ const watchdog_info *watchdog; /* Used for reset */ bool sid_fix; /* Use SID workaround (read via register) */ /* Use I$ workaround (disable I$ before first write to prevent stale thunk */ diff --git a/sunxi-fel.1 b/sunxi-fel.1 index 87d595307..3b9a35c70 100644 --- a/sunxi-fel.1 +++ b/sunxi-fel.1 @@ -42,7 +42,7 @@ Enable verbose logging. .sp .B \-\-no-high-speed .RS 4 -Disable automatic high-speed USB switching. +Disable automatic high-speed USB switching and post-SPL fast DRAM writes. .RE .sp .B \-l, \-\-list diff --git a/thunks/Makefile b/thunks/Makefile index fc7286004..97f0452cc 100644 --- a/thunks/Makefile +++ b/thunks/Makefile @@ -2,15 +2,16 @@ # build "preprocessed" .h files for inclusion of ARM scratch code # -SPL_THUNK := fel-to-spl-thunk.h -SPL_THUNK += fel-to-secure-svc-smc-thunk.h +FEL_EXEC_THUNKS := fel-to-spl-thunk.h +FEL_EXEC_THUNKS += fel-to-secure-svc-smc-thunk.h +FEL_EXEC_THUNKS += fel-rx-dma-thunk.h THUNKS := clrsetbits.h THUNKS += memcpy.h THUNKS += readl_writel.h THUNKS += rmr-thunk.h THUNKS += sid_read_root.h -all: $(SPL_THUNK) $(THUNKS) +all: $(FEL_EXEC_THUNKS) $(THUNKS) # clean up object files afterwards rm -f *.o @@ -25,9 +26,10 @@ OBJDUMP := $(CROSS_COMPILE)objdump AWK_O_TO_H := LC_ALL=C awk -f objdump_to_h.awk -# These thunks require the old output format. The "style" variable for -# awk controls this, and causes the htole32() conversion to be omitted. -$(SPL_THUNK): %.h: %.S FORCE +# These thunks are copied into a uint32_t buffer and byte-swapped by fel.c. +# The "style" variable for awk controls this, and causes the htole32() +# conversion to be omitted. +$(FEL_EXEC_THUNKS): %.h: %.S FORCE $(AS) -o $(subst .S,.o,$<) -march=armv5te $< $(OBJDUMP) -d $(subst .S,.o,$<) | $(AWK_O_TO_H) -v style=old > $@ diff --git a/thunks/README.md b/thunks/README.md index 4063bbd82..81a949828 100644 --- a/thunks/README.md +++ b/thunks/README.md @@ -8,10 +8,10 @@ usually via `sunxi-fel`. Normally you don't need to change or (re)build anything within this folder. Currently our main build process (via the parent directory's _Makefile_) -includes `fel-to-spl-thunk.h` and `fel-to-secure-svc-smc-thunk.h` directly. -Other _.h_ files are provided **just for reference**. The main purpose of this -folder is simply keeping track of _.S_ sources, to help with possible future -maintenance of the various code snippets. +includes `fel-to-spl-thunk.h`, `fel-to-secure-svc-smc-thunk.h`, and +`fel-rx-dma-thunk.h` directly. Other _.h_ files are provided **just for +reference**. The main purpose of this folder is simply keeping track of _.S_ +sources, to help with possible future maintenance of the various code snippets. Please note that any files lacking explicit license information are intended to be covered by the project's [overall license](../LICENSE.md) (GPLv2). diff --git a/thunks/fel-rx-dma-thunk.S b/thunks/fel-rx-dma-thunk.S new file mode 100644 index 000000000..97fcdb78a --- /dev/null +++ b/thunks/fel-rx-dma-thunk.S @@ -0,0 +1,338 @@ +/* + * FEL RX DMA thunk. + */ + +.arm + +.equ L1_TABLE_ENTRIES, 4096 +.equ L2_TABLE_ENTRIES, 256 +.equ BROM_PAGE_WORDS, 1024 +.equ L1_SECTION_FLAGS, 0x00000de2 +.equ L1_SECTION_NORMAL, 0x00001000 +.equ L1_COARSE_DESCRIPTOR_FLAGS, 0x1e1 +.equ L2_SMALL_PAGE_FLAGS, 0x72 + +.equ USB_INDEX, 0x0042 +.equ USB_VEND0, 0x0043 +.equ USB_RXCSR, 0x0086 +.equ USB_RXCSR_HI, 0x0087 +.equ USB_DMA_INTE, 0x0500 +.equ USB_DMA_INTS, 0x0504 +.equ USB_DMA_CHAN_CFN0, 0x0540 +.equ USB_DMA_SDRAM_ADD0, 0x0544 +.equ USB_DMA_BC0, 0x0548 +.equ USB_DMA_RESIDUAL_BC0, 0x054c +.equ USB_DMA_CHAN_ENABLE, 0x80000000 +.equ USB_DMA_RX_DIR, 0x00000010 +.equ USB_DMA_EP_LEN_512, 0x02000000 +.equ USB_DMA_CHAN_CFG_RX_512, USB_DMA_CHAN_ENABLE | USB_DMA_RX_DIR | USB_DMA_EP_LEN_512 +.equ USB_RXCSR_DMA_REQ_MODE_HI, 0x08 +.equ USB_RXCSR_DMA_REQ_EN_HI, 0x20 +.equ USB_RXCSR_AUTO_CLEAR_HI, 0x80 +.equ USB_RXCSR_DMA_BITS_HI, USB_RXCSR_DMA_REQ_MODE_HI | USB_RXCSR_DMA_REQ_EN_HI | USB_RXCSR_AUTO_CLEAR_HI +.equ USB_RXCSR_RX_PKT_READY, 0x01 + + .global fel_rx_dma_thunk +fel_rx_dma_thunk: + .word install_mmu_remap - fel_rx_dma_thunk + .word fel_rx_dma_params - fel_rx_dma_thunk + +install_mmu_remap: + @ Build a temporary first-level translation table. Most sections keep + @ the BROM's physical mapping, while the section containing the BROM + @ FIFO-copy helper is replaced with a second-level table. The helper's + @ page is shadowed, and a spare page in the same section is mapped to + @ this thunk so the patched branch is always in range. + adr r0, fel_rx_dma_params + ldr r4, [r0, #0x00] + ldr r5, [r0, #0x04] + ldr r9, [r0, #0x08] + ldr r7, [r0, #0x0c] + mov r6, r9, lsr #12 + mov r6, r6, lsl #12 + adr r10, brom_copy_from_fifo_dma_patch + + @ Encode an ARM branch from the BROM FIFO-copy helper to the patch. + @ Use virtual page 0xff in the hooked 1 MiB section, unless that is + @ the helper's own page. + mov r12, r9, lsr #20 + mov r12, r12, lsl #20 + mov r0, r9, lsr #12 + and r0, r0, #0xff + mov r1, #0xff + cmp r0, r1 + moveq r1, #0xfe + mov r2, r10, lsl #20 + mov r2, r2, lsr #20 + orr r0, r12, r1, lsl #12 + orr r0, r0, r2 + sub r0, r0, r9 + sub r0, r0, #8 + mov r0, r0, asr #2 + bic r0, r0, #0xff000000 + orr r11, r0, #0xea000000 + + mov r0, #0 + mov r1, r4 + ldr r2, l1_table_entries + ldr r3, l1_section_flags +1: + orr r8, r3, r0, lsl #20 + str r8, [r1], #4 + add r0, r0, #1 + subs r2, r2, #1 + bne 1b + + sub r1, r1, #4 + ldr r8, l1_last_section + str r8, [r1] + + @ Copy the hooked BROM page to SRAM, then overwrite only the helper + @ entry with the branch. The MMU remap below makes fetches from the + @ original BROM virtual address land in this shadow page. + mov r0, r6 + mov r1, r7 + mov r2, #BROM_PAGE_WORDS +2: + ldr r3, [r0], #4 + str r3, [r1], #4 + subs r2, r2, #1 + bne 2b + + sub r1, r9, r6 + str r11, [r7, r1] + + @ Identity-map the hooked 1 MiB section with small pages, except for + @ the hook page and the patch page. + mov r0, #0 + mov r1, r5 + mov r2, #L2_TABLE_ENTRIES + mov r3, #L2_SMALL_PAGE_FLAGS + mov r12, r9, lsr #20 + mov r12, r12, lsl #20 +3: + orr r8, r12, r0, lsl #12 + orr r8, r8, r3 + str r8, [r1], #4 + add r0, r0, #1 + subs r2, r2, #1 + bne 3b + + mov r6, r6, lsr #12 + and r6, r6, #0xff + orr r7, r7, r3 + str r7, [r5, r6, lsl #2] + + mov r1, #0xff + cmp r6, r1 + moveq r1, #0xfe + mov r8, r10, lsr #12 + mov r8, r8, lsl #12 + orr r8, r8, r3 + str r8, [r5, r1, lsl #2] + + ldr r3, l1_coarse_descriptor_flags + orr r0, r5, r3 + mov r1, r9, lsr #20 + str r0, [r4, r1, lsl #2] + + @ Install the new tables, invalidate stale address-translation state, + @ then enable the MMU, I-cache and branch prediction. The data cache is + @ intentionally left as the BROM had it. + ldr r0, dacr_value + mcr p15, 0, r0, c3, c0, 0 + + mov r0, #0 + mcr p15, 0, r0, c2, c0, 2 + + mcr p15, 0, r4, c2, c0, 0 + + mov r0, #0 + mcr p15, 0, r0, c8, c7, 0 + mcr p15, 0, r0, c7, c5, 0 + mcr p15, 0, r0, c7, c5, 6 + .inst 0xf57ff04f + .inst 0xf57ff06f + + mrc p15, 0, r0, c1, c0, 0 + orr r0, r0, #1 + orr r0, r0, #0x1800 + mcr p15, 0, r0, c1, c0, 0 + .inst 0xf57ff06f + bx lr + +dacr_value: + .word 0x55555555 +l1_table_entries: + .word L1_TABLE_ENTRIES +l1_section_flags: + .word L1_SECTION_FLAGS +l1_last_section: + .word (L1_TABLE_ENTRIES - 1) << 20 | L1_SECTION_FLAGS | L1_SECTION_NORMAL +l1_coarse_descriptor_flags: + .word L1_COARSE_DESCRIPTOR_FLAGS + + .balign 4 +brom_copy_from_fifo_dma_patch: + @ Hooked BROM helper arguments: + @ r0 = USB FIFO register address + @ r1 = destination buffer + @ r2 = BROM FIFO state + @ r3 = requested byte count + stmdb sp!, {r4-r10, lr} + mov r4, r0 + mov r5, r2 + + @ Keep PIO limited to the ROM-requested byte count, but let the DMA + @ path coalesce the remaining full packets in this AWUSB transfer. + @ The final short packet, if any, is copied below after DMA is stopped. + ldr r6, [r5, #0x04] + ldr r7, [r5, #0x10] + sub r6, r6, r7 + cmp r6, r3 + movcc r3, r6 + mov r0, r6 + mov r0, r0, lsr #9 + mov r0, r0, lsl #9 + sub r10, r6, r0 + ldr r7, dma_max_len + cmp r0, r7 + movhi r0, r7 + movhi r10, #0 + cmp r3, #512 + bne copy_pio + orr r6, r1, r0 + tst r6, #3 + bne copy_pio + + @ Use normal byte PIO unless the BROM asks for one aligned high-speed + @ packet. Then coalesce full packets with MUSB/USBC RX endpoint DMA. + ldr r6, usb_base + ldrb r8, [r6, #USB_INDEX] + ldrb r9, [r6, #USB_VEND0] + + @ Route the active RX endpoint's DRQ to the DMA bus. + mov r7, r8, lsl #1 + sub r7, r7, #1 + mov r7, r7, lsl #1 + orr r7, r7, #1 + strb r7, [r6, #USB_VEND0] + + @ Match the BSP's RX endpoint DMA programming sequence. + ldrb r7, [r6, #USB_RXCSR_HI] + orr r7, r7, #USB_RXCSR_DMA_REQ_MODE_HI + strb r7, [r6, #USB_RXCSR_HI] + orr r7, r7, #USB_RXCSR_AUTO_CLEAR_HI + orr r7, r7, #USB_RXCSR_DMA_REQ_EN_HI + strb r7, [r6, #USB_RXCSR_HI] + bic r7, r7, #USB_RXCSR_DMA_REQ_MODE_HI + strb r7, [r6, #USB_RXCSR_HI] + orr r7, r7, #USB_RXCSR_DMA_REQ_MODE_HI + strb r7, [r6, #USB_RXCSR_HI] + + mov r7, #1 + str r7, [r6, #USB_DMA_INTS] + str r7, [r6, #USB_DMA_INTE] + str r1, [r6, #USB_DMA_SDRAM_ADD0] + str r0, [r6, #USB_DMA_BC0] + ldr r7, usb_dma_chan_cfg_rx_512 + orr r7, r7, r8 + str r7, [r6, #USB_DMA_CHAN_CFN0] + + ldr r7, dma_timeout +copy_dma_wait: + ldr r8, [r6, #USB_DMA_INTS] + tst r8, #1 + bne copy_dma_done + ldr r8, [r6, #USB_DMA_RESIDUAL_BC0] + cmp r8, #0 + beq copy_dma_done + subs r7, r7, #1 + bne copy_dma_wait + ldr r7, [r6, #USB_DMA_CHAN_CFN0] + bic r7, r7, #USB_DMA_CHAN_ENABLE + str r7, [r6, #USB_DMA_CHAN_CFN0] + ldrb r7, [r6, #USB_RXCSR_HI] + bic r7, r7, #USB_RXCSR_DMA_BITS_HI + strb r7, [r6, #USB_RXCSR_HI] + strb r9, [r6, #USB_VEND0] + cmp r8, r0 + bne copy_dma_failed + b copy_pio + +copy_dma_done: + ldr r7, [r6, #USB_DMA_CHAN_CFN0] + bic r7, r7, #USB_DMA_CHAN_ENABLE + str r7, [r6, #USB_DMA_CHAN_CFN0] + mov r7, #1 + str r7, [r6, #USB_DMA_INTS] + ldrb r7, [r6, #USB_RXCSR_HI] + bic r7, r7, #USB_RXCSR_DMA_BITS_HI + strb r7, [r6, #USB_RXCSR_HI] + strb r9, [r6, #USB_VEND0] + ldr r7, [r5, #0x10] + add r7, r7, r0 + str r7, [r5, #0x10] + cmp r10, #0 + beq copy_done + add r1, r1, r0 + ldr r7, dma_timeout +copy_tail_wait: + ldrb r8, [r6, #USB_RXCSR] + tst r8, #USB_RXCSR_RX_PKT_READY + bne copy_tail + subs r7, r7, #1 + bne copy_tail_wait + b copy_done + +copy_tail: + ldr r7, [r5, #0x10] + add r7, r7, r10 + str r7, [r5, #0x10] + mov r3, r10 +copy_tail_loop: + subs r3, r3, #1 + bmi copy_done + ldrb r7, [r4] + strb r7, [r1], #1 + b copy_tail_loop + +copy_dma_failed: + mov r0, #0 + b copy_done + +copy_pio: + mov r0, r3 + ldr r7, [r5, #0x10] + add r7, r7, r3 + str r7, [r5, #0x10] +copy_loop: + subs r3, r3, #1 + bmi copy_done + ldrb r7, [r4] + strb r7, [r1], #1 + b copy_loop + +copy_done: + ldmia sp!, {r4-r10, pc} + +usb_dma_chan_cfg_rx_512: + .word USB_DMA_CHAN_CFG_RX_512 + +dma_timeout: + .word 0x10000000 + + .balign 4 +fel_rx_dma_params: +l1_tt_addr: + .word 0 +l2_tt_addr: + .word 0 +brom_hook_addr: + .word 0 +brom_hook_page_shadow: + .word 0 +usb_base: + .word 0 +dma_max_len: + .word 0 diff --git a/thunks/fel-rx-dma-thunk.h b/thunks/fel-rx-dma-thunk.h new file mode 100644 index 000000000..24ac4fe1e --- /dev/null +++ b/thunks/fel-rx-dma-thunk.h @@ -0,0 +1,238 @@ + /* : */ + 0x00000008, /* 0: .word 0x00000008 */ + 0x0000033c, /* 4: .word 0x0000033c */ + /* : */ + 0xe28f0fcb, /* 8: add r0, pc, #812 */ + 0xe5904000, /* c: ldr r4, [r0] */ + 0xe5905004, /* 10: ldr r5, [r0, #4] */ + 0xe5909008, /* 14: ldr r9, [r0, #8] */ + 0xe590700c, /* 18: ldr r7, [r0, #12] */ + 0xe1a06629, /* 1c: lsr r6, r9, #12 */ + 0xe1a06606, /* 20: lsl r6, r6, #12 */ + 0xe28faf55, /* 24: add sl, pc, #340 */ + 0xe1a0ca29, /* 28: lsr ip, r9, #20 */ + 0xe1a0ca0c, /* 2c: lsl ip, ip, #20 */ + 0xe1a00629, /* 30: lsr r0, r9, #12 */ + 0xe20000ff, /* 34: and r0, r0, #255 */ + 0xe3a010ff, /* 38: mov r1, #255 */ + 0xe1500001, /* 3c: cmp r0, r1 */ + 0x03a010fe, /* 40: moveq r1, #254 */ + 0xe1a02a0a, /* 44: lsl r2, sl, #20 */ + 0xe1a02a22, /* 48: lsr r2, r2, #20 */ + 0xe18c0601, /* 4c: orr r0, ip, r1, lsl #12 */ + 0xe1800002, /* 50: orr r0, r0, r2 */ + 0xe0400009, /* 54: sub r0, r0, r9 */ + 0xe2400008, /* 58: sub r0, r0, #8 */ + 0xe1a00140, /* 5c: asr r0, r0, #2 */ + 0xe3c004ff, /* 60: bic r0, r0, #-16777216 */ + 0xe380b4ea, /* 64: orr fp, r0, #-369098752 */ + 0xe3a00000, /* 68: mov r0, #0 */ + 0xe1a01004, /* 6c: mov r1, r4 */ + 0xe59f20f8, /* 70: ldr r2, [pc, #248] */ + 0xe59f30f8, /* 74: ldr r3, [pc, #248] */ + 0xe1838a00, /* 78: orr r8, r3, r0, lsl #20 */ + 0xe4818004, /* 7c: str r8, [r1], #4 */ + 0xe2800001, /* 80: add r0, r0, #1 */ + 0xe2522001, /* 84: subs r2, r2, #1 */ + 0x1afffffa, /* 88: bne 78 */ + 0xe2411004, /* 8c: sub r1, r1, #4 */ + 0xe59f80e0, /* 90: ldr r8, [pc, #224] */ + 0xe5818000, /* 94: str r8, [r1] */ + 0xe1a00006, /* 98: mov r0, r6 */ + 0xe1a01007, /* 9c: mov r1, r7 */ + 0xe3a02b01, /* a0: mov r2, #1024 */ + 0xe4903004, /* a4: ldr r3, [r0], #4 */ + 0xe4813004, /* a8: str r3, [r1], #4 */ + 0xe2522001, /* ac: subs r2, r2, #1 */ + 0x1afffffb, /* b0: bne a4 */ + 0xe0491006, /* b4: sub r1, r9, r6 */ + 0xe787b001, /* b8: str fp, [r7, r1] */ + 0xe3a00000, /* bc: mov r0, #0 */ + 0xe1a01005, /* c0: mov r1, r5 */ + 0xe3a02c01, /* c4: mov r2, #256 */ + 0xe3a03072, /* c8: mov r3, #114 */ + 0xe1a0ca29, /* cc: lsr ip, r9, #20 */ + 0xe1a0ca0c, /* d0: lsl ip, ip, #20 */ + 0xe18c8600, /* d4: orr r8, ip, r0, lsl #12 */ + 0xe1888003, /* d8: orr r8, r8, r3 */ + 0xe4818004, /* dc: str r8, [r1], #4 */ + 0xe2800001, /* e0: add r0, r0, #1 */ + 0xe2522001, /* e4: subs r2, r2, #1 */ + 0x1afffff9, /* e8: bne d4 */ + 0xe1a06626, /* ec: lsr r6, r6, #12 */ + 0xe20660ff, /* f0: and r6, r6, #255 */ + 0xe1877003, /* f4: orr r7, r7, r3 */ + 0xe7857106, /* f8: str r7, [r5, r6, lsl #2] */ + 0xe3a010ff, /* fc: mov r1, #255 */ + 0xe1560001, /* 100: cmp r6, r1 */ + 0x03a010fe, /* 104: moveq r1, #254 */ + 0xe1a0862a, /* 108: lsr r8, sl, #12 */ + 0xe1a08608, /* 10c: lsl r8, r8, #12 */ + 0xe1888003, /* 110: orr r8, r8, r3 */ + 0xe7858101, /* 114: str r8, [r5, r1, lsl #2] */ + 0xe59f305c, /* 118: ldr r3, [pc, #92] */ + 0xe1850003, /* 11c: orr r0, r5, r3 */ + 0xe1a01a29, /* 120: lsr r1, r9, #20 */ + 0xe7840101, /* 124: str r0, [r4, r1, lsl #2] */ + 0xe59f003c, /* 128: ldr r0, [pc, #60] */ + 0xee030f10, /* 12c: mcr 15, 0, r0, cr3, cr0, {0} */ + 0xe3a00000, /* 130: mov r0, #0 */ + 0xee020f50, /* 134: mcr 15, 0, r0, cr2, cr0, {2} */ + 0xee024f10, /* 138: mcr 15, 0, r4, cr2, cr0, {0} */ + 0xe3a00000, /* 13c: mov r0, #0 */ + 0xee080f17, /* 140: mcr 15, 0, r0, cr8, cr7, {0} */ + 0xee070f15, /* 144: mcr 15, 0, r0, cr7, cr5, {0} */ + 0xee070fd5, /* 148: mcr 15, 0, r0, cr7, cr5, {6} */ + 0xf57ff04f, /* 14c: dsb sy */ + 0xf57ff06f, /* 150: isb sy */ + 0xee110f10, /* 154: mrc 15, 0, r0, cr1, cr0, {0} */ + 0xe3800001, /* 158: orr r0, r0, #1 */ + 0xe3800b06, /* 15c: orr r0, r0, #6144 */ + 0xee010f10, /* 160: mcr 15, 0, r0, cr1, cr0, {0} */ + 0xf57ff06f, /* 164: isb sy */ + 0xe12fff1e, /* 168: bx lr */ + /* : */ + 0x55555555, /* 16c: .word 0x55555555 */ + /* : */ + 0x00001000, /* 170: .word 0x00001000 */ + /* : */ + 0x00000de2, /* 174: .word 0x00000de2 */ + /* : */ + 0xfff01de2, /* 178: .word 0xfff01de2 */ + /* : */ + 0x000001e1, /* 17c: .word 0x000001e1 */ + /* : */ + 0xe92d47f0, /* 180: push {r4, r5, r6, r7, r8, r9, sl, lr} */ + 0xe1a04000, /* 184: mov r4, r0 */ + 0xe1a05002, /* 188: mov r5, r2 */ + 0xe5956004, /* 18c: ldr r6, [r5, #4] */ + 0xe5957010, /* 190: ldr r7, [r5, #16] */ + 0xe0466007, /* 194: sub r6, r6, r7 */ + 0xe1560003, /* 198: cmp r6, r3 */ + 0x31a03006, /* 19c: movcc r3, r6 */ + 0xe1a00006, /* 1a0: mov r0, r6 */ + 0xe1a004a0, /* 1a4: lsr r0, r0, #9 */ + 0xe1a00480, /* 1a8: lsl r0, r0, #9 */ + 0xe046a000, /* 1ac: sub sl, r6, r0 */ + 0xe59f7198, /* 1b0: ldr r7, [pc, #408] */ + 0xe1500007, /* 1b4: cmp r0, r7 */ + 0x81a00007, /* 1b8: movhi r0, r7 */ + 0x83a0a000, /* 1bc: movhi sl, #0 */ + 0xe3530c02, /* 1c0: cmp r3, #512 */ + 0x1a000050, /* 1c4: bne 30c */ + 0xe1816000, /* 1c8: orr r6, r1, r0 */ + 0xe3160003, /* 1cc: tst r6, #3 */ + 0x1a00004d, /* 1d0: bne 30c */ + 0xe59f6170, /* 1d4: ldr r6, [pc, #368] */ + 0xe5d68042, /* 1d8: ldrb r8, [r6, #66] */ + 0xe5d69043, /* 1dc: ldrb r9, [r6, #67] */ + 0xe1a07088, /* 1e0: lsl r7, r8, #1 */ + 0xe2477001, /* 1e4: sub r7, r7, #1 */ + 0xe1a07087, /* 1e8: lsl r7, r7, #1 */ + 0xe3877001, /* 1ec: orr r7, r7, #1 */ + 0xe5c67043, /* 1f0: strb r7, [r6, #67] */ + 0xe5d67087, /* 1f4: ldrb r7, [r6, #135] */ + 0xe3877008, /* 1f8: orr r7, r7, #8 */ + 0xe5c67087, /* 1fc: strb r7, [r6, #135] */ + 0xe3877080, /* 200: orr r7, r7, #128 */ + 0xe3877020, /* 204: orr r7, r7, #32 */ + 0xe5c67087, /* 208: strb r7, [r6, #135] */ + 0xe3c77008, /* 20c: bic r7, r7, #8 */ + 0xe5c67087, /* 210: strb r7, [r6, #135] */ + 0xe3877008, /* 214: orr r7, r7, #8 */ + 0xe5c67087, /* 218: strb r7, [r6, #135] */ + 0xe3a07001, /* 21c: mov r7, #1 */ + 0xe5867504, /* 220: str r7, [r6, #1284] */ + 0xe5867500, /* 224: str r7, [r6, #1280] */ + 0xe5861544, /* 228: str r1, [r6, #1348] */ + 0xe5860548, /* 22c: str r0, [r6, #1352] */ + 0xe59f70fc, /* 230: ldr r7, [pc, #252] */ + 0xe1877008, /* 234: orr r7, r7, r8 */ + 0xe5867540, /* 238: str r7, [r6, #1344] */ + 0xe59f70f4, /* 23c: ldr r7, [pc, #244] */ + /* : */ + 0xe5968504, /* 240: ldr r8, [r6, #1284] */ + 0xe3180001, /* 244: tst r8, #1 */ + 0x1a00000e, /* 248: bne 288 */ + 0xe596854c, /* 24c: ldr r8, [r6, #1356] */ + 0xe3580000, /* 250: cmp r8, #0 */ + 0x0a00000b, /* 254: beq 288 */ + 0xe2577001, /* 258: subs r7, r7, #1 */ + 0x1afffff7, /* 25c: bne 240 */ + 0xe5967540, /* 260: ldr r7, [r6, #1344] */ + 0xe3c77102, /* 264: bic r7, r7, #-2147483648 */ + 0xe5867540, /* 268: str r7, [r6, #1344] */ + 0xe5d67087, /* 26c: ldrb r7, [r6, #135] */ + 0xe3c770a8, /* 270: bic r7, r7, #168 */ + 0xe5c67087, /* 274: strb r7, [r6, #135] */ + 0xe5c69043, /* 278: strb r9, [r6, #67] */ + 0xe1580000, /* 27c: cmp r8, r0 */ + 0x1a00001f, /* 280: bne 304 */ + 0xea000020, /* 284: b 30c */ + /* : */ + 0xe5967540, /* 288: ldr r7, [r6, #1344] */ + 0xe3c77102, /* 28c: bic r7, r7, #-2147483648 */ + 0xe5867540, /* 290: str r7, [r6, #1344] */ + 0xe3a07001, /* 294: mov r7, #1 */ + 0xe5867504, /* 298: str r7, [r6, #1284] */ + 0xe5d67087, /* 29c: ldrb r7, [r6, #135] */ + 0xe3c770a8, /* 2a0: bic r7, r7, #168 */ + 0xe5c67087, /* 2a4: strb r7, [r6, #135] */ + 0xe5c69043, /* 2a8: strb r9, [r6, #67] */ + 0xe5957010, /* 2ac: ldr r7, [r5, #16] */ + 0xe0877000, /* 2b0: add r7, r7, r0 */ + 0xe5857010, /* 2b4: str r7, [r5, #16] */ + 0xe35a0000, /* 2b8: cmp sl, #0 */ + 0x0a00001b, /* 2bc: beq 330 */ + 0xe0811000, /* 2c0: add r1, r1, r0 */ + 0xe59f706c, /* 2c4: ldr r7, [pc, #108] */ + /* : */ + 0xe5d68086, /* 2c8: ldrb r8, [r6, #134] */ + 0xe3180001, /* 2cc: tst r8, #1 */ + 0x1a000002, /* 2d0: bne 2e0 */ + 0xe2577001, /* 2d4: subs r7, r7, #1 */ + 0x1afffffa, /* 2d8: bne 2c8 */ + 0xea000013, /* 2dc: b 330 */ + /* : */ + 0xe5957010, /* 2e0: ldr r7, [r5, #16] */ + 0xe087700a, /* 2e4: add r7, r7, sl */ + 0xe5857010, /* 2e8: str r7, [r5, #16] */ + 0xe1a0300a, /* 2ec: mov r3, sl */ + /* : */ + 0xe2533001, /* 2f0: subs r3, r3, #1 */ + 0x4a00000d, /* 2f4: bmi 330 */ + 0xe5d47000, /* 2f8: ldrb r7, [r4] */ + 0xe4c17001, /* 2fc: strb r7, [r1], #1 */ + 0xeafffffa, /* 300: b 2f0 */ + /* : */ + 0xe3a00000, /* 304: mov r0, #0 */ + 0xea000008, /* 308: b 330 */ + /* : */ + 0xe1a00003, /* 30c: mov r0, r3 */ + 0xe5957010, /* 310: ldr r7, [r5, #16] */ + 0xe0877003, /* 314: add r7, r7, r3 */ + 0xe5857010, /* 318: str r7, [r5, #16] */ + /* : */ + 0xe2533001, /* 31c: subs r3, r3, #1 */ + 0x4a000002, /* 320: bmi 330 */ + 0xe5d47000, /* 324: ldrb r7, [r4] */ + 0xe4c17001, /* 328: strb r7, [r1], #1 */ + 0xeafffffa, /* 32c: b 31c */ + /* : */ + 0xe8bd87f0, /* 330: pop {r4, r5, r6, r7, r8, r9, sl, pc} */ + /* : */ + 0x82000010, /* 334: .word 0x82000010 */ + /* : */ + 0x10000000, /* 338: .word 0x10000000 */ + /* : */ + 0x00000000, /* 33c: .word 0x00000000 */ + /* : */ + 0x00000000, /* 340: .word 0x00000000 */ + /* : */ + 0x00000000, /* 344: .word 0x00000000 */ + /* : */ + 0x00000000, /* 348: .word 0x00000000 */ + /* : */ + 0x00000000, /* 34c: .word 0x00000000 */ + /* : */ + 0x00000000, /* 350: .word 0x00000000 */