From adcc8ef87bc1ed47c95f1f2d23072b2b916e1555 Mon Sep 17 00:00:00 2001 From: Tomas Vanek Date: Sun, 2 Apr 2023 17:23:46 +0200 Subject: [PATCH] target/adiv5: probe MEM-AP supported transfer sizes including large data Based on Daniel Goehring's [1] and Peter Collingbourne's [2] work. Probe for support of 8, 16 bit and if the large data extension is available also probe for 64, 128 and 256 bit operations. Probe for the ability of packing 8 and 16 bit data (formerly probed in mem_ap_init()). The probe is integrated to mem_ap_read/write() routines and takes place just before the first memory access of the specific size. Add 64, 128 and 256 bit MEM-AP read/writes. Introduce specific error codes for unsupported transfer size and for unsupported packing. Change-Id: I180c4ef17d2fc3189e8e2f14bafd22d857f29608 Link: 7191: target/adiv5: add MEM-AP 64-bit access support | https://review.openocd.org/c/openocd/+/7191 Link: 7436: arm_adi_v5: Support reads wider than 32 bits | https://review.openocd.org/c/openocd/+/7436 Signed-off-by: Tomas Vanek Reviewed-on: https://review.openocd.org/c/openocd/+/7576 Reviewed-by: Antonio Borneo Tested-by: jenkins --- src/target/arm_adi_v5.c | 366 ++++++++++++++++++++++++++-------------- src/target/arm_adi_v5.h | 26 ++- src/target/target.h | 2 + 3 files changed, 268 insertions(+), 126 deletions(-) diff --git a/src/target/arm_adi_v5.c b/src/target/arm_adi_v5.c index 0f1e648a9e..ff12658c83 100644 --- a/src/target/arm_adi_v5.c +++ b/src/target/arm_adi_v5.c @@ -50,7 +50,8 @@ /* * Relevant specifications from ARM include: * - * ARM(tm) Debug Interface v5 Architecture Specification ARM IHI 0031E + * ARM(tm) Debug Interface v5 Architecture Specification ARM IHI 0031F + * ARM(tm) Debug Interface v6 Architecture Specification ARM IHI 0074C * CoreSight(tm) v1.0 Architecture Specification ARM IHI 0029B * * CoreSight(tm) DAP-Lite TRM, ARM DDI 0316D @@ -164,6 +165,12 @@ static uint32_t mem_ap_get_tar_increment(struct adiv5_ap *ap) return 2; case CSW_32BIT: return 4; + case CSW_64BIT: + return 8; + case CSW_128BIT: + return 16; + case CSW_256BIT: + return 32; default: return 0; } @@ -319,12 +326,145 @@ int mem_ap_write_atomic_u32(struct adiv5_ap *ap, target_addr_t address, return dap_run(ap->dap); } +/** + * Queue transactions setting up transfer parameters for the + * currently selected MEM-AP. If transfer size or packing + * has not been probed, run the queue, read back CSW and check if the requested + * transfer mode is supported. + * + * @param ap The MEM-AP. + * @param size Transfer width in bytes. Corresponding CSW.Size will be set. + * @param address Transfer address, MEM-AP TAR will be set to this value. + * @param addrinc TAR will be autoincremented. + * @param pack Try to setup packed transfer. + * @param this_size Points to a variable set to the size of single transfer + * or to 4 when transferring packed bytes or halfwords + * + * @return ERROR_OK if the transaction was properly queued, else a fault code. + */ +static int mem_ap_setup_transfer_verify_size_packing(struct adiv5_ap *ap, + unsigned int size, target_addr_t address, + bool addrinc, bool pack, unsigned int *this_size) +{ + int retval; + uint32_t csw_size; + + switch (size) { + case 1: + csw_size = CSW_8BIT; + break; + case 2: + csw_size = CSW_16BIT; + break; + case 4: + csw_size = CSW_32BIT; + break; + case 8: + csw_size = CSW_64BIT; + break; + case 16: + csw_size = CSW_128BIT; + break; + case 32: + csw_size = CSW_256BIT; + break; + default: + LOG_ERROR("Size %u not supported", size); + return ERROR_TARGET_SIZE_NOT_SUPPORTED; + } + + if (!addrinc || size >= 4 + || (ap->packed_transfers_probed && !ap->packed_transfers_supported) + || max_tar_block_size(ap->tar_autoincr_block, address) < 4) + pack = false; + + uint32_t csw_addrinc = pack ? CSW_ADDRINC_PACKED : + addrinc ? CSW_ADDRINC_SINGLE : CSW_ADDRINC_OFF; + retval = mem_ap_setup_csw(ap, csw_size | csw_addrinc); + if (retval != ERROR_OK) + return retval; + + bool do_probe = !(ap->csw_size_probed_mask & size) + || (pack && !ap->packed_transfers_probed); + if (do_probe) { + uint32_t csw_readback; + retval = dap_queue_ap_read(ap, MEM_AP_REG_CSW(ap->dap), &csw_readback); + if (retval != ERROR_OK) + return retval; + + retval = dap_run(ap->dap); + if (retval != ERROR_OK) + return retval; + + bool size_supported = ((csw_readback & CSW_SIZE_MASK) == csw_size); + LOG_DEBUG("AP#0x%" PRIx64 " probed size %u: %s", ap->ap_num, size, + size_supported ? "supported" : "not supported"); + ap->csw_size_probed_mask |= size; + if (size_supported) { + ap->csw_size_supported_mask |= size; + if (pack && !ap->packed_transfers_probed) { + ap->packed_transfers_probed = true; + ap->packed_transfers_supported = + ((csw_readback & CSW_ADDRINC_MASK) == csw_addrinc); + LOG_DEBUG("probed packing: %s", + ap->packed_transfers_supported ? "supported" : "not supported"); + } + } + } + + if (!(ap->csw_size_supported_mask & size)) { + LOG_ERROR("Size %u not supported", size); + return ERROR_TARGET_SIZE_NOT_SUPPORTED; + } + + if (pack && !ap->packed_transfers_supported) + return ERROR_TARGET_PACKING_NOT_SUPPORTED; + + *this_size = pack ? 4 : size; + + return mem_ap_setup_tar(ap, address); +} + +/** + * Queue transactions setting up transfer parameters for the + * currently selected MEM-AP. If transfer size or packing + * has not been probed, run the queue, read back CSW and check if the requested + * transfer mode is supported. + * If packing is not supported fallback and prepare CSW for unpacked transfer. + * + * @param ap The MEM-AP. + * @param size Transfer width in bytes. Corresponding CSW.Size will be set. + * @param address Transfer address, MEM-AP TAR will be set to this value. + * @param addrinc TAR will be autoincremented. + * @param pack Try to setup packed transfer. + * @param this_size Points to a variable set to the size of single transfer + * or to 4 when transferring packed bytes or halfwords + * + * @return ERROR_OK if the transaction was properly queued, else a fault code. + */ +static int mem_ap_setup_transfer_verify_size_packing_fallback(struct adiv5_ap *ap, + unsigned int size, target_addr_t address, + bool addrinc, bool pack, unsigned int *this_size) +{ + int retval = mem_ap_setup_transfer_verify_size_packing(ap, + size, address, + addrinc, pack, this_size); + if (retval == ERROR_TARGET_PACKING_NOT_SUPPORTED) { + /* Retry without packing */ + retval = mem_ap_setup_transfer_verify_size_packing(ap, + size, address, + addrinc, false, this_size); + } + return retval; +} + /** * Synchronous write of a block of memory, using a specific access size. * * @param ap The MEM-AP to access. * @param buffer The data buffer to write. No particular alignment is assumed. - * @param size Which access size to use, in bytes. 1, 2 or 4. + * @param size Which access size to use, in bytes. 1, 2, or 4. + * If large data extension is available also accepts sizes 8, 16, 32. * @param count The number of writes to do (in size units, not bytes). * @param address Address to be written; it must be writable by the currently selected MEM-AP. * @param addrinc Whether the target address should be increased for each write or not. This @@ -336,8 +476,6 @@ static int mem_ap_write(struct adiv5_ap *ap, const uint8_t *buffer, uint32_t siz { struct adiv5_dap *dap = ap->dap; size_t nbytes = size * count; - const uint32_t csw_addrincr = addrinc ? CSW_ADDRINC_SINGLE : CSW_ADDRINC_OFF; - uint32_t csw_size; int retval = ERROR_OK; /* TI BE-32 Quirks mode: @@ -352,93 +490,85 @@ static int mem_ap_write(struct adiv5_ap *ap, const uint8_t *buffer, uint32_t siz * To make writes of size < 4 work as expected, we xor a value with the address before * setting the TAP, and we set the TAP after every transfer rather then relying on * address increment. */ - - target_addr_t ti_be_lane_xor = dap->ti_be_32_quirks ? 3 : 0; - target_addr_t ti_be_addr_xor; - if (size == 4) { - csw_size = CSW_32BIT; - ti_be_addr_xor = 0; - } else if (size == 2) { - csw_size = CSW_16BIT; - ti_be_addr_xor = dap->ti_be_32_quirks ? 2 : 0; - } else if (size == 1) { - csw_size = CSW_8BIT; - ti_be_addr_xor = dap->ti_be_32_quirks ? 3 : 0; - } else { - return ERROR_TARGET_UNALIGNED_ACCESS; + target_addr_t ti_be_addr_xor = 0; + target_addr_t ti_be_lane_xor = 0; + if (dap->ti_be_32_quirks) { + ti_be_lane_xor = 3; + switch (size) { + case 1: + ti_be_addr_xor = 3; + break; + case 2: + ti_be_addr_xor = 2; + break; + case 4: + break; + default: + LOG_ERROR("Write more than 32 bits not supported with ti_be_32_quirks"); + return ERROR_TARGET_SIZE_NOT_SUPPORTED; + } } if (ap->unaligned_access_bad && (address % size != 0)) return ERROR_TARGET_UNALIGNED_ACCESS; - while (nbytes > 0) { - uint32_t this_size = size; + /* Nuvoton NPCX quirks prevent packed writes */ + bool pack = !dap->nu_npcx_quirks; - /* Select packed transfer if possible */ - if (addrinc && ap->packed_transfers && nbytes >= 4 - && !dap->nu_npcx_quirks - && max_tar_block_size(ap->tar_autoincr_block, address) >= 4) { - this_size = 4; - retval = mem_ap_setup_csw(ap, csw_size | CSW_ADDRINC_PACKED); - } else { - retval = mem_ap_setup_csw(ap, csw_size | csw_addrincr); - } - - if (retval != ERROR_OK) - break; - - retval = mem_ap_setup_tar(ap, address ^ ti_be_addr_xor); + while (nbytes > 0) { + unsigned int this_size; + retval = mem_ap_setup_transfer_verify_size_packing_fallback(ap, + size, address ^ ti_be_addr_xor, + addrinc, pack && nbytes >= 4, &this_size); if (retval != ERROR_OK) return retval; /* How many source bytes each transfer will consume, and their location in the DRW, * depends on the type of transfer and alignment. See ARM document IHI0031C. */ - uint32_t outvalue = 0; uint32_t drw_byte_idx = address; - if (dap->nu_npcx_quirks && this_size <= 2) { - switch (this_size) { - case 2: - { - /* Alternate low and high byte to all byte lanes */ - uint32_t low = *buffer++; - uint32_t high = *buffer++; - outvalue |= low << 8 * (drw_byte_idx++ & 3); - outvalue |= high << 8 * (drw_byte_idx++ & 3); - outvalue |= low << 8 * (drw_byte_idx++ & 3); - outvalue |= high << 8 * (drw_byte_idx & 3); - } - break; - case 1: - { - /* Mirror output byte to all byte lanes */ - uint32_t data = *buffer++; - outvalue |= data; - outvalue |= data << 8; - outvalue |= data << 16; - outvalue |= data << 24; + unsigned int drw_ops = DIV_ROUND_UP(this_size, 4); + + while (drw_ops--) { + uint32_t outvalue = 0; + if (dap->nu_npcx_quirks && this_size <= 2) { + switch (this_size) { + case 2: + { + /* Alternate low and high byte to all byte lanes */ + uint32_t low = *buffer++; + uint32_t high = *buffer++; + outvalue |= low << 8 * (drw_byte_idx++ & 3); + outvalue |= high << 8 * (drw_byte_idx++ & 3); + outvalue |= low << 8 * (drw_byte_idx++ & 3); + outvalue |= high << 8 * (drw_byte_idx & 3); + } + break; + case 1: + { + /* Mirror output byte to all byte lanes */ + uint32_t data = *buffer++; + outvalue |= data; + outvalue |= data << 8; + outvalue |= data << 16; + outvalue |= data << 24; + } } + } else { + unsigned int drw_bytes = MIN(this_size, 4); + while (drw_bytes--) + outvalue |= (uint32_t)*buffer++ << + 8 * ((drw_byte_idx++ & 3) ^ ti_be_lane_xor); } - } else { - switch (this_size) { - case 4: - outvalue |= (uint32_t)*buffer++ << 8 * ((drw_byte_idx++ & 3) ^ ti_be_lane_xor); - outvalue |= (uint32_t)*buffer++ << 8 * ((drw_byte_idx++ & 3) ^ ti_be_lane_xor); - /* fallthrough */ - case 2: - outvalue |= (uint32_t)*buffer++ << 8 * ((drw_byte_idx++ & 3) ^ ti_be_lane_xor); - /* fallthrough */ - case 1: - outvalue |= (uint32_t)*buffer++ << 8 * ((drw_byte_idx & 3) ^ ti_be_lane_xor); - } - } - nbytes -= this_size; - - retval = dap_queue_ap_write(ap, MEM_AP_REG_DRW(dap), outvalue); + retval = dap_queue_ap_write(ap, MEM_AP_REG_DRW(dap), outvalue); + if (retval != ERROR_OK) + break; + } if (retval != ERROR_OK) break; mem_ap_update_tar_cache(ap); + nbytes -= this_size; if (addrinc) address += this_size; } @@ -463,7 +593,8 @@ static int mem_ap_write(struct adiv5_ap *ap, const uint8_t *buffer, uint32_t siz * * @param ap The MEM-AP to access. * @param buffer The data buffer to receive the data. No particular alignment is assumed. - * @param size Which access size to use, in bytes. 1, 2 or 4. + * @param size Which access size to use, in bytes. 1, 2, or 4. + * If large data extension is available also accepts sizes 8, 16, 32. * @param count The number of reads to do (in size units, not bytes). * @param adr Address to be read; it must be readable by the currently selected MEM-AP. * @param addrinc Whether the target address should be increased after each read or not. This @@ -475,8 +606,6 @@ static int mem_ap_read(struct adiv5_ap *ap, uint8_t *buffer, uint32_t size, uint { struct adiv5_dap *dap = ap->dap; size_t nbytes = size * count; - const uint32_t csw_addrincr = addrinc ? CSW_ADDRINC_SINGLE : CSW_ADDRINC_OFF; - uint32_t csw_size; target_addr_t address = adr; int retval = ERROR_OK; @@ -487,14 +616,10 @@ static int mem_ap_read(struct adiv5_ap *ap, uint8_t *buffer, uint32_t size, uint * Also, packed 8-bit and 16-bit transfers seem to sometimes return garbage in some bytes, * so avoid them (ap->packed_transfers is forced to false in mem_ap_init). */ - if (size == 4) - csw_size = CSW_32BIT; - else if (size == 2) - csw_size = CSW_16BIT; - else if (size == 1) - csw_size = CSW_8BIT; - else - return ERROR_TARGET_UNALIGNED_ACCESS; + if (dap->ti_be_32_quirks && size > 4) { + LOG_ERROR("Read more than 32 bits not supported with ti_be_32_quirks"); + return ERROR_TARGET_SIZE_NOT_SUPPORTED; + } if (ap->unaligned_access_bad && (adr % size != 0)) return ERROR_TARGET_UNALIGNED_ACCESS; @@ -502,7 +627,8 @@ static int mem_ap_read(struct adiv5_ap *ap, uint8_t *buffer, uint32_t size, uint /* Allocate buffer to hold the sequence of DRW reads that will be made. This is a significant * over-allocation if packed transfers are going to be used, but determining the real need at * this point would be messy. */ - uint32_t *read_buf = calloc(count, sizeof(uint32_t)); + uint32_t *read_buf = calloc(count, MAX(sizeof(uint32_t), size)); + /* Multiplication count * sizeof(uint32_t) may overflow, calloc() is safe */ uint32_t *read_ptr = read_buf; if (!read_buf) { @@ -514,26 +640,20 @@ static int mem_ap_read(struct adiv5_ap *ap, uint8_t *buffer, uint32_t size, uint * useful bytes it contains, and their location in the word, depends on the type of transfer * and alignment. */ while (nbytes > 0) { - uint32_t this_size = size; - - /* Select packed transfer if possible */ - if (addrinc && ap->packed_transfers && nbytes >= 4 - && max_tar_block_size(ap->tar_autoincr_block, address) >= 4) { - this_size = 4; - retval = mem_ap_setup_csw(ap, csw_size | CSW_ADDRINC_PACKED); - } else { - retval = mem_ap_setup_csw(ap, csw_size | csw_addrincr); - } + unsigned int this_size; + retval = mem_ap_setup_transfer_verify_size_packing_fallback(ap, + size, address, + addrinc, nbytes >= 4, &this_size); if (retval != ERROR_OK) break; - retval = mem_ap_setup_tar(ap, address); - if (retval != ERROR_OK) - break; - retval = dap_queue_ap_read(ap, MEM_AP_REG_DRW(dap), read_ptr++); - if (retval != ERROR_OK) - break; + unsigned int drw_ops = DIV_ROUND_UP(this_size, 4); + while (drw_ops--) { + retval = dap_queue_ap_read(ap, MEM_AP_REG_DRW(dap), read_ptr++); + if (retval != ERROR_OK) + break; + } nbytes -= this_size; if (addrinc) @@ -552,7 +672,9 @@ static int mem_ap_read(struct adiv5_ap *ap, uint8_t *buffer, uint32_t size, uint /* If something failed, read TAR to find out how much data was successfully read, so we can * at least give the caller what we have. */ - if (retval != ERROR_OK) { + if (retval == ERROR_TARGET_SIZE_NOT_SUPPORTED) { + nbytes = 0; + } else if (retval != ERROR_OK) { target_addr_t tar; if (mem_ap_read_tar(ap, &tar) == ERROR_OK) { /* TAR is incremented after failed transfer on some devices (eg Cortex-M4) */ @@ -569,11 +691,12 @@ static int mem_ap_read(struct adiv5_ap *ap, uint8_t *buffer, uint32_t size, uint /* Replay loop to populate caller's buffer from the correct word and byte lane */ while (nbytes > 0) { - uint32_t this_size = size; + /* Convert transfers longer than 32-bit on word-at-a-time basis */ + unsigned int this_size = MIN(size, 4); - if (addrinc && ap->packed_transfers && nbytes >= 4 + if (size < 4 && addrinc && ap->packed_transfers_supported && nbytes >= 4 && max_tar_block_size(ap->tar_autoincr_block, address) >= 4) { - this_size = 4; + this_size = 4; /* Packed read of 4 bytes or 2 halfwords */ } switch (this_size) { @@ -764,7 +887,7 @@ int dap_dp_init_or_reconnect(struct adiv5_dap *dap) int mem_ap_init(struct adiv5_ap *ap) { /* check that we support packed transfers */ - uint32_t csw, cfg; + uint32_t cfg; int retval; struct adiv5_dap *dap = ap->dap; @@ -781,30 +904,23 @@ int mem_ap_init(struct adiv5_ap *ap) ap->cfg_reg = cfg; ap->tar_valid = false; ap->csw_value = 0; /* force csw and tar write */ - retval = mem_ap_setup_transfer(ap, CSW_8BIT | CSW_ADDRINC_PACKED, 0); - if (retval != ERROR_OK) - return retval; - retval = dap_queue_ap_read(ap, MEM_AP_REG_CSW(dap), &csw); - if (retval != ERROR_OK) - return retval; + /* CSW 32-bit size must be supported (IHI 0031F and 0074D). */ + ap->csw_size_supported_mask = BIT(CSW_32BIT); + ap->csw_size_probed_mask = BIT(CSW_32BIT); - retval = dap_run(dap); - if (retval != ERROR_OK) - return retval; + /* Suppress probing sizes longer than 32 bit if AP has no large data extension */ + if (!(cfg & MEM_AP_REG_CFG_LD)) + ap->csw_size_probed_mask |= BIT(CSW_64BIT) | BIT(CSW_128BIT) | BIT(CSW_256BIT); - if (csw & CSW_ADDRINC_PACKED) - ap->packed_transfers = true; - else - ap->packed_transfers = false; - - /* Packed transfers on TI BE-32 processors do not work correctly in + /* Both IHI 0031F and 0074D state: Implementations that support transfers + * smaller than a word must support packed transfers. Unfortunately at least + * Cortex-M0 and Cortex-M0+ do not comply with this rule. + * Probe for packed transfers except we know they are broken. + * Packed transfers on TI BE-32 processors do not work correctly in * many cases. */ - if (dap->ti_be_32_quirks) - ap->packed_transfers = false; - - LOG_DEBUG("MEM_AP Packed Transfers: %s", - ap->packed_transfers ? "enabled" : "disabled"); + ap->packed_transfers_supported = false; + ap->packed_transfers_probed = dap->ti_be_32_quirks ? true : false; /* The ARM ADI spec leaves implementation-defined whether unaligned * memory accesses work, only work partially, or cause a sticky error. diff --git a/src/target/arm_adi_v5.h b/src/target/arm_adi_v5.h index fc7fdafd84..60c161f3ca 100644 --- a/src/target/arm_adi_v5.h +++ b/src/target/arm_adi_v5.h @@ -165,6 +165,9 @@ #define CSW_8BIT 0 #define CSW_16BIT 1 #define CSW_32BIT 2 +#define CSW_64BIT 3 +#define CSW_128BIT 4 +#define CSW_256BIT 5 #define CSW_ADDRINC_MASK (3UL << 4) #define CSW_ADDRINC_OFF 0UL #define CSW_ADDRINC_SINGLE (1UL << 4) @@ -269,6 +272,26 @@ struct adiv5_ap { */ uint32_t csw_value; + /** + * Save the supported CSW.Size data types for the MEM-AP. + * Each bit corresponds to a data type. + * 0b1 = Supported data size. 0b0 = Not supported. + * Bit 0 = Byte (8-bits) + * Bit 1 = Halfword (16-bits) + * Bit 2 = Word (32-bits) - always supported by spec. + * Bit 3 = Doubleword (64-bits) + * Bit 4 = 128-bits + * Bit 5 = 256-bits + */ + uint32_t csw_size_supported_mask; + /** + * Probed CSW.Size data types for the MEM-AP. + * Each bit corresponds to a data type. + * 0b1 = Data size has been probed. 0b0 = Not yet probed. + * Bits assigned to sizes same way as above. + */ + uint32_t csw_size_probed_mask; + /** * Cache for (MEM-AP) AP_REG_TAR register value This is written to * configure the address being read or written @@ -286,7 +309,8 @@ struct adiv5_ap { uint32_t tar_autoincr_block; /* true if packed transfers are supported by the MEM-AP */ - bool packed_transfers; + bool packed_transfers_supported; + bool packed_transfers_probed; /* true if unaligned memory access is not supported by the MEM-AP */ bool unaligned_access_bad; diff --git a/src/target/target.h b/src/target/target.h index 6caf598874..f69ee77ac4 100644 --- a/src/target/target.h +++ b/src/target/target.h @@ -796,6 +796,8 @@ int target_profiling_default(struct target *target, uint32_t *samples, uint32_t #define ERROR_TARGET_NOT_EXAMINED (-311) #define ERROR_TARGET_DUPLICATE_BREAKPOINT (-312) #define ERROR_TARGET_ALGO_EXIT (-313) +#define ERROR_TARGET_SIZE_NOT_SUPPORTED (-314) +#define ERROR_TARGET_PACKING_NOT_SUPPORTED (-315) extern bool get_target_reset_nag(void); -- 2.30.2