diff --git a/Makefile b/Makefile index fd1c15e4b..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 $(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 79a7c24d0..03a9c7d6b 100644 --- a/fel.c +++ b/fel.c @@ -37,6 +37,9 @@ 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; +#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(...) \ do { if (verbose) printf(__VA_ARGS__); } while (0); @@ -76,6 +79,49 @@ 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 + +#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; + + 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). @@ -358,6 +404,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 @@ -415,9 +465,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) { @@ -435,9 +585,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) @@ -564,36 +713,111 @@ 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); } -/* - * 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 bool aw_fel_needs_smc_workaround(feldev_handle *dev) { soc_info_t *soc_info = dev->soc_info; uint32_t val; + + 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; + aw_fel_read(dev, soc_info->needs_smc_workaround_if_zero_word_at_addr, + &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(0xe1600070), /* smc #0 */ - htole32(0xe12fff1e), /* bx lr */ + htole32(0xe12fff1e), /* bx lr */ }; + uint32_t *thunk_buf, *p; + size_t thunk_size; + size_t i; - /* Return if the SoC does not need this workaround */ - if (!soc_info->needs_smc_workaround_if_zero_word_at_addr) - return; + if (!handler) + pr_fatal("FEL thunk: missing monitor SMC handler info\n"); - /* 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)); + for (i = 0; swap_buffers[i].size; i++) + ; - /* Return if the workaround is not needed or has been already applied */ - if (val != 0) + 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); + + 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); +} + +/* + * 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; + + /* 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"); } @@ -775,7 +999,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) @@ -893,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; } @@ -970,6 +1195,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; @@ -1001,7 +1332,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); @@ -1111,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}*/ @@ -1129,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 */ @@ -1138,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"); } @@ -1202,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); @@ -1222,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) { @@ -1246,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 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" @@ -1299,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]); @@ -1314,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; @@ -1368,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); @@ -1381,7 +1790,43 @@ 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 */ + handle->verbose = verbose; + handle->usb_high_speed = high_speed && + handle->soc_info->usb_musb_base != 0; + reenum_sid_arg = 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); + } + 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); + 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. */ aw_apply_smc_workaround(handle); /* Handle command-style arguments, in order of appearance */ @@ -1505,8 +1950,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..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 @@ -32,6 +33,7 @@ #include #define USB_TIMEOUT 10000 /* 10 seconds */ +#define USB_DISCONNECT_TIMEOUT 250 /* 250 milliseconds */ static bool fel_lib_initialized = false; @@ -69,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) { @@ -112,6 +120,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; @@ -147,7 +194,22 @@ 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, + 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) @@ -158,15 +220,58 @@ 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) { + 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); } +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 +290,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 +325,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 +373,55 @@ 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_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; @@ -270,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); @@ -670,7 +861,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 +872,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 +887,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 +912,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 +948,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 +1010,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 +1034,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 +1082,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..6c3cf7c0b 100644 --- a/fel_lib.h +++ b/fel_lib.h @@ -34,6 +34,11 @@ typedef struct { struct aw_fel_version soc_version; 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 */ @@ -49,6 +54,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 +69,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 4cb6bcc44..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 @@ -156,6 +158,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 @@ -382,6 +390,16 @@ 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, + .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 */ @@ -395,8 +413,19 @@ 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, + .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, .watchdog = &wd_h3_compat, },{ .soc_id = 0x1639, /* Allwinner A80 */ @@ -443,8 +472,19 @@ 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, + .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, .watchdog = &wd_h3_compat, },{ .soc_id = 0x1681, /* Allwinner V3s */ @@ -481,8 +521,19 @@ 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, + .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, .watchdog = &wd_h3_compat, },{ .soc_id = 0x1701, /* Allwinner R40 */ @@ -507,6 +558,16 @@ 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, + .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 */ @@ -520,8 +581,19 @@ 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, + .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, .watchdog = &wd_h6_compat, },{ .soc_id = 0x1816, /* Allwinner V536 */ @@ -561,6 +633,20 @@ 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, + .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, + .monitor_smc_handler = &h616_monitor_smc_handler, .watchdog = &wd_h6_compat, },{ .soc_id = 0x1851, /* Allwinner R329 */ @@ -587,6 +673,16 @@ 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, + .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, },{ @@ -613,6 +709,16 @@ 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, + .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 */ @@ -626,6 +732,16 @@ 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, + .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, },{ @@ -640,7 +756,18 @@ 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, + .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, },{ .swap_buffers = NULL /* End of the table */ diff --git a/soc_info.h b/soc_info.h index 508f29dab..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, \ @@ -107,13 +116,31 @@ 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. 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 */ @@ -129,12 +156,20 @@ 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 */ + 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 */ 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/sunxi-fel.1 b/sunxi-fel.1 index 3923bc0e2..3b9a35c70 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 and post-SPL fast DRAM writes. +.RE +.sp .B \-l, \-\-list .RS 4 Enumerate all (USB) FEL devices and exit. diff --git a/thunks/Makefile b/thunks/Makefile index 27ba55d0e..97f0452cc 100644 --- a/thunks/Makefile +++ b/thunks/Makefile @@ -2,14 +2,16 @@ # build "preprocessed" .h files for inclusion of ARM scratch code # -SPL_THUNK := fel-to-spl-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 @@ -24,9 +26,10 @@ 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 -# awk controls this, and causes the htole32() conversion to be omitted. -fel-to-spl-thunk.h: fel-to-spl-thunk.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 e9ca949f2..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_) -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`, `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 */ 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 */