/*************************************************************************** * * * Copyright (C) 2018 by Bohdan Tymkiv * * bohdan.tymkiv@cypress.com bohdan200@gmail.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include "imp.h" #include "target/target.h" #include "target/cortex_m.h" #include "target/breakpoints.h" #include "target/target_type.h" #include "time_support.h" #include "target/algorithm.h" /************************************************************************************************** * PSoC6 device definitions *************************************************************************************************/ #define MFLASH_SECTOR_SIZE (256u * 1024u) #define WFLASH_SECTOR_SIZE (32u * 1024u) #define MEM_BASE_MFLASH 0x10000000u #define MEM_BASE_WFLASH 0x14000000u #define MEM_WFLASH_SIZE 32768u #define MEM_BASE_SFLASH 0x16000000u #define RAM_STACK_WA_SIZE 2048u #define PSOC6_SPCIF_GEOMETRY 0x4025F00Cu #define PROTECTION_UNKNOWN 0x00u #define PROTECTION_VIRGIN 0x01u #define PROTECTION_NORMAL 0x02u #define PROTECTION_SECURE 0x03u #define PROTECTION_DEAD 0x04u #define MEM_BASE_IPC 0x40230000u #define IPC_STRUCT_SIZE 0x20u #define MEM_IPC(n) (MEM_BASE_IPC + (n) * IPC_STRUCT_SIZE) #define MEM_IPC_ACQUIRE(n) (MEM_IPC(n) + 0x00u) #define MEM_IPC_NOTIFY(n) (MEM_IPC(n) + 0x08u) #define MEM_IPC_DATA(n) (MEM_IPC(n) + 0x0Cu) #define MEM_IPC_LOCK_STATUS(n) (MEM_IPC(n) + 0x10u) #define MEM_BASE_IPC_INTR 0x40231000u #define IPC_INTR_STRUCT_SIZE 0x20u #define MEM_IPC_INTR(n) (MEM_BASE_IPC_INTR + (n) * IPC_INTR_STRUCT_SIZE) #define MEM_IPC_INTR_MASK(n) (MEM_IPC_INTR(n) + 0x08u) #define IPC_ACQUIRE_SUCCESS_MSK 0x80000000u #define IPC_LOCK_ACQUIRED_MSK 0x80000000u #define IPC_ID 2u #define IPC_INTR_ID 0u #define IPC_TIMEOUT_MS 1000 #define SROMAPI_SIID_REQ 0x00000001u #define SROMAPI_SIID_REQ_FAMILY_REVISION (SROMAPI_SIID_REQ | 0x000u) #define SROMAPI_SIID_REQ_SIID_PROTECTION (SROMAPI_SIID_REQ | 0x100u) #define SROMAPI_WRITEROW_REQ 0x05000100u #define SROMAPI_PROGRAMROW_REQ 0x06000100u #define SROMAPI_ERASESECTOR_REQ 0x14000100u #define SROMAPI_ERASEALL_REQ 0x0A000100u #define SROMAPI_ERASEROW_REQ 0x1C000100u #define SROMAPI_STATUS_MSK 0xF0000000u #define SROMAPI_STAT_SUCCESS 0xA0000000u #define SROMAPI_DATA_LOCATION_MSK 0x00000001u #define SROMAPI_CALL_TIMEOUT_MS 1500 struct psoc6_target_info { uint32_t silicon_id; uint8_t protection; uint32_t main_flash_sz; uint32_t row_sz; bool is_probed; }; struct timeout { int64_t start_time; long timeout_ms; }; struct row_region { uint32_t addr; size_t size; }; static const struct row_region safe_sflash_regions[] = { {0x16000800, 0x800}, /* SFLASH: User Data */ {0x16001A00, 0x200}, /* SFLASH: NAR */ {0x16005A00, 0xC00}, /* SFLASH: Public Key */ {0x16007C00, 0x400}, /* SFLASH: TOC2 */ }; #define SFLASH_NUM_REGIONS (sizeof(safe_sflash_regions) / sizeof(safe_sflash_regions[0])) static struct working_area *g_stack_area; static struct armv7m_algorithm g_armv7m_info; /** *********************************************************************************************** * @brief Initializes `struct timeout` structure with given timeout value * @param to pointer to `struct timeout` structure * @param timeout_ms timeout, in milliseconds *************************************************************************************************/ static void timeout_init(struct timeout *to, long timeout_ms) { to->start_time = timeval_ms(); to->timeout_ms = timeout_ms; } /** *********************************************************************************************** * @brief Returns true if given `struct timeout` structure has expired * @param to pointer to `struct timeout` structure * @return true if timeout expired *************************************************************************************************/ static bool timeout_expired(struct timeout *to) { return (timeval_ms() - to->start_time) > to->timeout_ms; } /** *********************************************************************************************** * @brief Starts pseudo flash algorithm and leaves it running. Function allocates working area for * algorithm code and CPU stack, adjusts stack pointer, uploads and starts the algorithm. * Algorithm (a basic infinite loop) runs asynchronously while driver performs Flash operations. * * @param target target for the algorithm * @return ERROR_OK in case of success, ERROR_XXX code otherwise *************************************************************************************************/ static int sromalgo_prepare(struct target *target) { int hr; /* Initialize Vector Table Offset register (in case FW modified it) */ hr = target_write_u32(target, 0xE000ED08, 0x00000000); if (hr != ERROR_OK) return hr; /* Restore THUMB bit in xPSR register */ const struct armv7m_common *cm = target_to_armv7m(target); hr = cm->store_core_reg_u32(target, ARMV7M_xPSR, 0x01000000); if (hr != ERROR_OK) return hr; /* Allocate Working Area for Stack and Flash algorithm */ hr = target_alloc_working_area(target, RAM_STACK_WA_SIZE, &g_stack_area); if (hr != ERROR_OK) return hr; g_armv7m_info.common_magic = ARMV7M_COMMON_MAGIC; g_armv7m_info.core_mode = ARM_MODE_THREAD; struct reg_param reg_params; init_reg_param(®_params, "sp", 32, PARAM_OUT); buf_set_u32(reg_params.value, 0, 32, g_stack_area->address + g_stack_area->size); /* Write basic infinite loop algorithm to target RAM */ hr = target_write_u32(target, g_stack_area->address, 0xFEE7FEE7); if (hr != ERROR_OK) goto destroy_rp_free_wa; hr = target_start_algorithm(target, 0, NULL, 1, ®_params, g_stack_area->address, 0, &g_armv7m_info); if (hr != ERROR_OK) goto destroy_rp_free_wa; destroy_reg_param(®_params); return hr; destroy_rp_free_wa: /* Something went wrong, do some cleanup */ destroy_reg_param(®_params); if (g_stack_area) { target_free_working_area(target, g_stack_area); g_stack_area = NULL; } return hr; } /** *********************************************************************************************** * @brief Stops running flash algorithm and releases associated resources. * This function is also used for cleanup in case of errors so g_stack_area may be NULL. * These cases have to be handled gracefully. * * @param target current target *************************************************************************************************/ static void sromalgo_release(struct target *target) { int hr = ERROR_OK; if (g_stack_area) { /* Stop flash algorithm if it is running */ if (target->running_alg) { hr = target_halt(target); if (hr != ERROR_OK) goto exit_free_wa; hr = target_wait_algorithm(target, 0, NULL, 0, NULL, 0, IPC_TIMEOUT_MS, &g_armv7m_info); if (hr != ERROR_OK) goto exit_free_wa; } exit_free_wa: /* Free Stack/Flash algorithm working area */ target_free_working_area(target, g_stack_area); g_stack_area = NULL; } } /** *********************************************************************************************** * @brief Waits for expected IPC lock status. PSoC6 uses IPC structures for inter-core * communication. Same IPCs are used to invoke SROM API. IPC structure must be locked prior to * invoking any SROM API. This ensures nothing else in the system will use same IPC thus corrupting * our data. Locking is performed by ipc_acquire(), this function ensures that IPC is actually * in expected state * * @param target current target * @param ipc_id IPC index to poll. IPC #2 is dedicated for DAP access * @param lock_expected expected lock status * @return ERROR_OK in case of success, ERROR_XXX code otherwise *************************************************************************************************/ static int ipc_poll_lock_stat(struct target *target, uint32_t ipc_id, bool lock_expected) { int hr; uint32_t reg_val; struct timeout to; timeout_init(&to, IPC_TIMEOUT_MS); while (!timeout_expired(&to)) { /* Process any server requests */ keep_alive(); /* Read IPC Lock status */ hr = target_read_u32(target, MEM_IPC_LOCK_STATUS(ipc_id), ®_val); if (hr != ERROR_OK) { LOG_ERROR("Unable to read IPC Lock Status register"); return hr; } bool is_locked = (reg_val & IPC_LOCK_ACQUIRED_MSK) != 0; if (lock_expected == is_locked) return ERROR_OK; } if (target->coreid) { LOG_WARNING("SROM API calls via CM4 target are supported on single-core PSoC6 devices only. " "Please perform all Flash-related operations via CM0+ target on dual-core devices."); } LOG_ERROR("Timeout polling IPC Lock Status"); return ERROR_TARGET_TIMEOUT; } /** *********************************************************************************************** * @brief Acquires IPC structure. PSoC6 uses IPC structures for inter-core communication. * Same IPCs are used to invoke SROM API. IPC structure must be locked prior to invoking any SROM API. * This ensures nothing else in the system will use same IPC thus corrupting our data. * This function locks the IPC. * * @param target current target * @param ipc_id ipc_id IPC index to acquire. IPC #2 is dedicated for DAP access * @return ERROR_OK in case of success, ERROR_XXX code otherwise *************************************************************************************************/ static int ipc_acquire(struct target *target, char ipc_id) { int hr = ERROR_OK; bool is_acquired = false; uint32_t reg_val; struct timeout to; timeout_init(&to, IPC_TIMEOUT_MS); while (!timeout_expired(&to)) { keep_alive(); hr = target_write_u32(target, MEM_IPC_ACQUIRE(ipc_id), IPC_ACQUIRE_SUCCESS_MSK); if (hr != ERROR_OK) { LOG_ERROR("Unable to write to IPC Acquire register"); return hr; } /* Check if data is written on first step */ hr = target_read_u32(target, MEM_IPC_ACQUIRE(ipc_id), ®_val); if (hr != ERROR_OK) { LOG_ERROR("Unable to read IPC Acquire register"); return hr; } is_acquired = (reg_val & IPC_ACQUIRE_SUCCESS_MSK) != 0; if (is_acquired) { /* If IPC structure is acquired, the lock status should be set */ hr = ipc_poll_lock_stat(target, ipc_id, true); break; } } if (!is_acquired) LOG_ERROR("Timeout acquiring IPC structure"); return hr; } /** *********************************************************************************************** * @brief Invokes SROM API functions which are responsible for Flash operations * * @param target current target * @param req_and_params requect id of the function to invoke * @param working_area address of memory buffer in target's memory space for SROM API parameters * @param data_out pointer to variable which will be populated with execution status * @return ERROR_OK in case of success, ERROR_XXX code otherwise *************************************************************************************************/ static int call_sromapi(struct target *target, uint32_t req_and_params, uint32_t working_area, uint32_t *data_out) { int hr; bool is_data_in_ram = (req_and_params & SROMAPI_DATA_LOCATION_MSK) == 0; hr = ipc_acquire(target, IPC_ID); if (hr != ERROR_OK) return hr; if (is_data_in_ram) hr = target_write_u32(target, MEM_IPC_DATA(IPC_ID), working_area); else hr = target_write_u32(target, MEM_IPC_DATA(IPC_ID), req_and_params); if (hr != ERROR_OK) return hr; /* Enable notification interrupt of IPC_INTR_STRUCT0(CM0+) for IPC_STRUCT2 */ hr = target_write_u32(target, MEM_IPC_INTR_MASK(IPC_INTR_ID), 1u << (16 + IPC_ID)); if (hr != ERROR_OK) return hr; hr = target_write_u32(target, MEM_IPC_NOTIFY(IPC_ID), 1); if (hr != ERROR_OK) return hr; /* Poll lock status */ hr = ipc_poll_lock_stat(target, IPC_ID, false); if (hr != ERROR_OK) return hr; /* Poll Data byte */ if (is_data_in_ram) hr = target_read_u32(target, working_area, data_out); else hr = target_read_u32(target, MEM_IPC_DATA(IPC_ID), data_out); if (hr != ERROR_OK) { LOG_ERROR("Error reading SROM API Status location"); return hr; } bool is_success = (*data_out & SROMAPI_STATUS_MSK) == SROMAPI_STAT_SUCCESS; if (!is_success) { LOG_ERROR("SROM API execution failed. Status: 0x%08X", (uint32_t)*data_out); return ERROR_TARGET_FAILURE; } return ERROR_OK; } /** *********************************************************************************************** * @brief Retrieves SiliconID and Protection status of the target device * @param target current target * @param si_id pointer to variable, will be populated with SiliconID * @param protection pointer to variable, will be populated with protection status * @return ERROR_OK in case of success, ERROR_XXX code otherwise *************************************************************************************************/ static int get_silicon_id(struct target *target, uint32_t *si_id, uint8_t *protection) { int hr; uint32_t family_rev, siid_prot; hr = sromalgo_prepare(target); if (hr != ERROR_OK) goto exit; /* Read FamilyID and Revision */ hr = call_sromapi(target, SROMAPI_SIID_REQ_FAMILY_REVISION, 0, &family_rev); if (hr != ERROR_OK) goto exit; /* Read SiliconID and Protection */ hr = call_sromapi(target, SROMAPI_SIID_REQ_SIID_PROTECTION, 0, &siid_prot); if (hr != ERROR_OK) goto exit; *si_id = (siid_prot & 0x0000FFFF) << 16; *si_id |= (family_rev & 0x00FF0000) >> 8; *si_id |= (family_rev & 0x000000FF) >> 0; *protection = (siid_prot & 0x000F0000) >> 0x10; exit: sromalgo_release(target); return ERROR_OK; } /** *********************************************************************************************** * @brief Translates Protection status to openocd-friendly boolean value * @param bank current flash bank * @return ERROR_OK in case of success, ERROR_XXX code otherwise *************************************************************************************************/ static int psoc6_protect_check(struct flash_bank *bank) { int is_protected; struct psoc6_target_info *psoc6_info = bank->driver_priv; int hr = get_silicon_id(bank->target, &psoc6_info->silicon_id, &psoc6_info->protection); if (hr != ERROR_OK) return hr; switch (psoc6_info->protection) { case PROTECTION_VIRGIN: case PROTECTION_NORMAL: is_protected = 0; break; case PROTECTION_UNKNOWN: case PROTECTION_SECURE: case PROTECTION_DEAD: default: is_protected = 1; break; } for (int i = 0; i < bank->num_sectors; i++) bank->sectors[i].is_protected = is_protected; return ERROR_OK; } /** *********************************************************************************************** * @brief Dummy function, Life Cycle transition is not currently supported * @return ERROR_OK always *************************************************************************************************/ static int psoc6_protect(struct flash_bank *bank, int set, int first, int last) { (void)bank; (void)set; (void)first; (void)last; LOG_WARNING("Life Cycle transition for PSoC6 is not supported"); return ERROR_OK; } /** *********************************************************************************************** * @brief Translates Protection status to string * @param protection protection value * @return pointer to const string describintg protection status *************************************************************************************************/ static const char *protection_to_str(uint8_t protection) { switch (protection) { case PROTECTION_VIRGIN: return "VIRGIN"; break; case PROTECTION_NORMAL: return "NORMAL"; break; case PROTECTION_SECURE: return "SECURE"; break; case PROTECTION_DEAD: return "DEAD"; break; case PROTECTION_UNKNOWN: default: return "UNKNOWN"; break; } } /** *********************************************************************************************** * @brief psoc6_get_info Displays human-readable information about acquired device * @param bank current flash bank * @param buf pointer to buffer for human-readable text * @param buf_size size of the buffer * @return ERROR_OK in case of success, ERROR_XXX code otherwise *************************************************************************************************/ static int psoc6_get_info(struct flash_bank *bank, char *buf, int buf_size) { struct psoc6_target_info *psoc6_info = bank->driver_priv; if (psoc6_info->is_probed == false) return ERROR_FAIL; int hr = get_silicon_id(bank->target, &psoc6_info->silicon_id, &psoc6_info->protection); if (hr != ERROR_OK) return hr; snprintf(buf, buf_size, "PSoC6 Silicon ID: 0x%08X\n" "Protection: %s\n" "Main Flash size: %d kB\n" "Work Flash size: 32 kB\n", psoc6_info->silicon_id, protection_to_str(psoc6_info->protection), psoc6_info->main_flash_sz / 1024); return ERROR_OK; } /** *********************************************************************************************** * @brief Checks if given flash bank belongs to Supervisory Flash * @param bank current flash bank * @return true if flash bank belongs to Supervisory Flash *************************************************************************************************/ static bool is_sflash_bank(struct flash_bank *bank) { for (size_t i = 0; i < SFLASH_NUM_REGIONS; i++) { if (bank->base == safe_sflash_regions[i].addr) return true; } return false; } /** *********************************************************************************************** * @brief Checks if given flash bank belongs to Work Flash * @param bank current flash bank * @return true if flash bank belongs to Work Flash *************************************************************************************************/ static inline bool is_wflash_bank(struct flash_bank *bank) { return (bank->base == MEM_BASE_WFLASH); } /** *********************************************************************************************** * @brief Checks if given flash bank belongs to Main Flash * @param bank current flash bank * @return true if flash bank belongs to Main Flash *************************************************************************************************/ static inline bool is_mflash_bank(struct flash_bank *bank) { return (bank->base == MEM_BASE_MFLASH); } /** *********************************************************************************************** * @brief Probes the device and populates related data structures with target flash geometry data. * This is done in non-intrusive way, no SROM API calls are involved so GDB can safely attach to a * running target. Function assumes that size of Work Flash is 32kB (true for all current part numbers) * * @param bank current flash bank * @return ERROR_OK in case of success, ERROR_XXX code otherwise *************************************************************************************************/ static int psoc6_probe(struct flash_bank *bank) { struct target *target = bank->target; struct psoc6_target_info *psoc6_info = bank->driver_priv; int hr = ERROR_OK; /* Retrieve data from SPCIF_GEOMATRY */ uint32_t geom; target_read_u32(target, PSOC6_SPCIF_GEOMETRY, &geom); uint32_t row_sz_lg2 = (geom & 0xF0) >> 4; uint32_t row_sz = (0x01 << row_sz_lg2); uint32_t row_cnt = 1 + ((geom & 0x00FFFF00) >> 8); uint32_t bank_cnt = 1 + ((geom & 0xFF000000) >> 24); /* Calculate size of Main Flash*/ uint32_t flash_sz_bytes = bank_cnt * row_cnt * row_sz; if (bank->sectors) { free(bank->sectors); bank->sectors = NULL; } size_t bank_size = 0; if (is_mflash_bank(bank)) bank_size = flash_sz_bytes; else if (is_wflash_bank(bank)) bank_size = MEM_WFLASH_SIZE; else if (is_sflash_bank(bank)) { for (size_t i = 0; i < SFLASH_NUM_REGIONS; i++) { if (safe_sflash_regions[i].addr == bank->base) { bank_size = safe_sflash_regions[i].size; break; } } } if (bank_size == 0) { LOG_ERROR("Invalid Flash Bank base address in config file"); return ERROR_FLASH_BANK_INVALID; } size_t num_sectors = bank_size / row_sz; bank->size = bank_size; bank->chip_width = 4; bank->bus_width = 4; bank->erased_value = 0; bank->default_padded_value = 0; bank->num_sectors = num_sectors; bank->sectors = calloc(num_sectors, sizeof(struct flash_sector)); for (size_t i = 0; i < num_sectors; i++) { bank->sectors[i].size = row_sz; bank->sectors[i].offset = i * row_sz; bank->sectors[i].is_erased = -1; bank->sectors[i].is_protected = -1; } psoc6_info->is_probed = true; psoc6_info->main_flash_sz = flash_sz_bytes; psoc6_info->row_sz = row_sz; return hr; } /** *********************************************************************************************** * @brief Probes target device only if it hasn't been probed yet * @param bank current flash bank * @return ERROR_OK in case of success, ERROR_XXX code otherwise *************************************************************************************************/ static int psoc6_auto_probe(struct flash_bank *bank) { struct psoc6_target_info *psoc6_info = bank->driver_priv; int hr; if (psoc6_info->is_probed) hr = ERROR_OK; else hr = psoc6_probe(bank); return hr; } /** *********************************************************************************************** * @brief Erases single sector (256k) on target device * @param bank current flash bank * @param wa working area for SROM API parameters * @param addr starting address of the sector * @return ERROR_OK in case of success, ERROR_XXX code otherwise *************************************************************************************************/ static int psoc6_erase_sector(struct flash_bank *bank, struct working_area *wa, uint32_t addr) { struct target *target = bank->target; LOG_DEBUG("Erasing SECTOR @%08X", addr); int hr = target_write_u32(target, wa->address, SROMAPI_ERASESECTOR_REQ); if (hr != ERROR_OK) return hr; hr = target_write_u32(target, wa->address + 0x04, addr); if (hr != ERROR_OK) return hr; uint32_t data_out; hr = call_sromapi(target, SROMAPI_ERASESECTOR_REQ, wa->address, &data_out); if (hr != ERROR_OK) LOG_ERROR("SECTOR @%08X not erased!", addr); return hr; } /** *********************************************************************************************** * @brief Erases single row (512b) on target device * @param bank current flash bank * @param wa working area for SROM API parameters * @param addr starting address of the flash row * @return ERROR_OK in case of success, ERROR_XXX code otherwise *************************************************************************************************/ static int psoc6_erase_row(struct flash_bank *bank, struct working_area *wa, uint32_t addr) { struct target *target = bank->target; LOG_DEBUG("Erasing ROW @%08X", addr); int hr = target_write_u32(target, wa->address, SROMAPI_ERASEROW_REQ); if (hr != ERROR_OK) return hr; hr = target_write_u32(target, wa->address + 0x04, addr); if (hr != ERROR_OK) return hr; uint32_t data_out; hr = call_sromapi(target, SROMAPI_ERASEROW_REQ, wa->address, &data_out); if (hr != ERROR_OK) LOG_ERROR("ROW @%08X not erased!", addr); return hr; } /** *********************************************************************************************** * @brief Performs Erase operation. Function will try to use biggest erase block possible to * speedup the operation. * * @param bank current flash bank * @param first first sector to erase * @param last last sector to erase * @return ERROR_OK in case of success, ERROR_XXX code otherwise *************************************************************************************************/ static int psoc6_erase(struct flash_bank *bank, int first, int last) { struct target *target = bank->target; struct psoc6_target_info *psoc6_info = bank->driver_priv; const uint32_t sector_size = is_wflash_bank(bank) ? WFLASH_SECTOR_SIZE : MFLASH_SECTOR_SIZE; int hr; struct working_area *wa; if (is_sflash_bank(bank)) { LOG_INFO("Erase operation on Supervisory Flash is not required, skipping"); return ERROR_OK; } hr = sromalgo_prepare(target); if (hr != ERROR_OK) goto exit; hr = target_alloc_working_area(target, psoc6_info->row_sz + 32, &wa); if (hr != ERROR_OK) goto exit; /* Number of rows in single sector */ const int rows_in_sector = sector_size / psoc6_info->row_sz; while (last >= first) { /* Erase Sector if we are on sector boundary and erase size covers whole sector */ if ((first % rows_in_sector) == 0 && (last - first + 1) >= rows_in_sector) { hr = psoc6_erase_sector(bank, wa, bank->base + first * psoc6_info->row_sz); if (hr != ERROR_OK) goto exit_free_wa; for (int i = first; i < first + rows_in_sector; i++) bank->sectors[i].is_erased = 1; first += rows_in_sector; } else { /* Perform Row Erase otherwise */ hr = psoc6_erase_row(bank, wa, bank->base + first * psoc6_info->row_sz); if (hr != ERROR_OK) goto exit_free_wa; bank->sectors[first].is_erased = 1; first += 1; } } exit_free_wa: target_free_working_area(target, wa); exit: sromalgo_release(target); return hr; } /** *********************************************************************************************** * @brief Programs single Flash Row * @param bank current flash bank * @param addr address of the flash row * @param buffer pointer to the buffer with data * @param is_sflash true if current flash bank belongs to Supervisory Flash * @return ERROR_OK in case of success, ERROR_XXX code otherwise *************************************************************************************************/ static int psoc6_program_row(struct flash_bank *bank, uint32_t addr, const uint8_t *buffer, bool is_sflash) { struct target *target = bank->target; struct psoc6_target_info *psoc6_info = bank->driver_priv; struct working_area *wa; const uint32_t sromapi_req = is_sflash ? SROMAPI_WRITEROW_REQ : SROMAPI_PROGRAMROW_REQ; uint32_t data_out; int hr = ERROR_OK; LOG_DEBUG("Programming ROW @%08X", addr); hr = target_alloc_working_area(target, psoc6_info->row_sz + 32, &wa); if (hr != ERROR_OK) goto exit; hr = target_write_u32(target, wa->address, sromapi_req); if (hr != ERROR_OK) goto exit_free_wa; hr = target_write_u32(target, wa->address + 0x04, 0x106); if (hr != ERROR_OK) goto exit_free_wa; hr = target_write_u32(target, wa->address + 0x08, addr); if (hr != ERROR_OK) goto exit_free_wa; hr = target_write_u32(target, wa->address + 0x0C, wa->address + 0x10); if (hr != ERROR_OK) goto exit_free_wa; hr = target_write_buffer(target, wa->address + 0x10, psoc6_info->row_sz, buffer); if (hr != ERROR_OK) goto exit_free_wa; hr = call_sromapi(target, sromapi_req, wa->address, &data_out); exit_free_wa: target_free_working_area(target, wa); exit: return hr; } /** *********************************************************************************************** * @brief Performs Program operation * @param bank current flash bank * @param buffer pointer to the buffer with data * @param offset starting offset in falsh bank * @param count number of bytes in buffer * @return ERROR_OK in case of success, ERROR_XXX code otherwise *************************************************************************************************/ static int psoc6_program(struct flash_bank *bank, const uint8_t *buffer, uint32_t offset, uint32_t count) { struct target *target = bank->target; struct psoc6_target_info *psoc6_info = bank->driver_priv; const bool is_sflash = is_sflash_bank(bank); int hr; uint8_t page_buf[psoc6_info->row_sz]; hr = sromalgo_prepare(target); if (hr != ERROR_OK) goto exit; while (count) { uint32_t row_offset = offset % psoc6_info->row_sz; uint32_t aligned_addr = bank->base + offset - row_offset; uint32_t row_bytes = MIN(psoc6_info->row_sz - row_offset, count); memset(page_buf, 0, sizeof(page_buf)); memcpy(&page_buf[row_offset], buffer, row_bytes); hr = psoc6_program_row(bank, aligned_addr, page_buf, is_sflash); if (hr != ERROR_OK) { LOG_ERROR("Failed to program Flash at address 0x%08X", aligned_addr); goto exit; } buffer += row_bytes; offset += row_bytes; count -= row_bytes; } exit: sromalgo_release(target); return hr; } /** *********************************************************************************************** * @brief Performs Mass Erase operation * @param bank flash bank index to erase * @return ERROR_OK in case of success, ERROR_XXX code otherwise *************************************************************************************************/ COMMAND_HANDLER(psoc6_handle_mass_erase_command) { if (CMD_ARGC != 1) return ERROR_COMMAND_SYNTAX_ERROR; struct flash_bank *bank; int hr = CALL_COMMAND_HANDLER(flash_command_get_bank, 0, &bank); if (hr != ERROR_OK) return hr; hr = psoc6_erase(bank, 0, bank->num_sectors - 1); return hr; } /** *********************************************************************************************** * @brief Simulates broken Vector Catch * Function will try to determine entry point of user application. If it succeeds it will set HW * breakpoint at that address, issue SW Reset and remove the breakpoint afterwards. * In case of CM0, SYSRESETREQ is used. This allows to reset all peripherals. Boot code will * reset CM4 anyway, so using SYSRESETREQ is safe here. * In case of CM4, VECTRESET is used instead of SYSRESETREQ to not disturb CM0 core. * * @param target current target * @return ERROR_OK in case of success, ERROR_XXX code otherwise *************************************************************************************************/ int handle_reset_halt(struct target *target) { int hr; uint32_t reset_addr; bool is_cm0 = (target->coreid == 0); /* Halt target device */ if (target->state != TARGET_HALTED) { hr = target_halt(target); if (hr != ERROR_OK) return hr; target_wait_state(target, TARGET_HALTED, IPC_TIMEOUT_MS); if (hr != ERROR_OK) return hr; } /* Read Vector Offset register */ uint32_t vt_base; const uint32_t vt_offset_reg = is_cm0 ? 0x402102B0 : 0x402102C0; hr = target_read_u32(target, vt_offset_reg, &vt_base); if (hr != ERROR_OK) return ERROR_OK; /* Invalid value means flash is empty */ vt_base &= 0xFFFFFF00; if ((vt_base == 0) || (vt_base == 0xFFFFFF00)) return ERROR_OK; /* Read Reset Vector value*/ hr = target_read_u32(target, vt_base + 4, &reset_addr); if (hr != ERROR_OK) return hr; /* Invalid value means flash is empty */ if ((reset_addr == 0) || (reset_addr == 0xFFFFFF00)) return ERROR_OK; /* Set breakpoint at User Application entry point */ hr = breakpoint_add(target, reset_addr, 2, BKPT_HARD); if (hr != ERROR_OK) return hr; const struct armv7m_common *cm = target_to_armv7m(target); /* PSoC6 reboots immediatelly after issuing SYSRESETREQ / VECTRESET * this disables SWD/JTAG pins momentarily and may break communication * Ignoring return value of mem_ap_write_atomic_u32 seems to be ok here */ if (is_cm0) { /* Reset the CM0 by asserting SYSRESETREQ. This will also reset CM4 */ LOG_INFO("psoc6.cm0: bkpt @0x%08X, issuing SYSRESETREQ", reset_addr); mem_ap_write_atomic_u32(cm->debug_ap, NVIC_AIRCR, AIRCR_VECTKEY | AIRCR_SYSRESETREQ); } else { LOG_INFO("psoc6.cm4: bkpt @0x%08X, issuing VECTRESET", reset_addr); mem_ap_write_atomic_u32(cm->debug_ap, NVIC_AIRCR, AIRCR_VECTKEY | AIRCR_VECTRESET); } /* Wait 100ms for bootcode and reinitialize DAP */ usleep(100000); dap_dp_init(cm->debug_ap->dap); target_wait_state(target, TARGET_HALTED, IPC_TIMEOUT_MS); /* Remove the break point */ breakpoint_remove(target, reset_addr); return ERROR_OK; } /** *********************************************************************************************** * @brief Simulates broken Vector Catch * Function will try to determine entry point of user application. If it succeeds it will set HW * breakpoint at that address, issue SW Reset and remove the breakpoint afterwards. * In case of CM0, SYSRESETREQ is used. This allows to reset all peripherals. Boot code will * reset CM4 anyway, so using SYSRESETREQ is safe here. * In case of CM4, VECTRESET is used instead of SYSRESETREQ to not disturb CM0 core. * * @return ERROR_OK in case of success, ERROR_XXX code otherwise *************************************************************************************************/ COMMAND_HANDLER(psoc6_handle_reset_halt) { if (CMD_ARGC) return ERROR_COMMAND_SYNTAX_ERROR; struct target *target = get_current_target(CMD_CTX); return handle_reset_halt(target); } FLASH_BANK_COMMAND_HANDLER(psoc6_flash_bank_command) { struct psoc6_target_info *psoc6_info; int hr = ERROR_OK; if (CMD_ARGC < 6) hr = ERROR_COMMAND_SYNTAX_ERROR; else { psoc6_info = calloc(1, sizeof(struct psoc6_target_info)); psoc6_info->is_probed = false; bank->driver_priv = psoc6_info; } return hr; } static const struct command_registration psoc6_exec_command_handlers[] = { { .name = "mass_erase", .handler = psoc6_handle_mass_erase_command, .mode = COMMAND_EXEC, .usage = "bank", .help = "Erases entire Main Flash", }, { .name = "reset_halt", .handler = psoc6_handle_reset_halt, .mode = COMMAND_EXEC, .usage = NULL, .help = "Tries to simulate broken Vector Catch", }, COMMAND_REGISTRATION_DONE }; static const struct command_registration psoc6_command_handlers[] = { { .name = "psoc6", .mode = COMMAND_ANY, .help = "PSoC 6 flash command group", .usage = "", .chain = psoc6_exec_command_handlers, }, COMMAND_REGISTRATION_DONE }; struct flash_driver psoc6_flash = { .name = "psoc6", .commands = psoc6_command_handlers, .flash_bank_command = psoc6_flash_bank_command, .erase = psoc6_erase, .protect = psoc6_protect, .write = psoc6_program, .read = default_flash_read, .probe = psoc6_probe, .auto_probe = psoc6_auto_probe, .erase_check = default_flash_blank_check, .protect_check = psoc6_protect_check, .info = psoc6_get_info, .free_driver_priv = default_flash_free_driver_priv, };