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 <vanekt@fbl.cz>
Reviewed-on: https://review.openocd.org/c/openocd/+/7576
Reviewed-by: Antonio Borneo <borneo.antonio@gmail.com>
Tested-by: jenkins
This commit is contained in:
Tomas Vanek 2023-04-02 17:23:46 +02:00 committed by Antonio Borneo
parent ffdcec938f
commit adcc8ef87b
3 changed files with 270 additions and 128 deletions

View File

@ -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;
/* Nuvoton NPCX quirks prevent packed writes */
bool pack = !dap->nu_npcx_quirks;
while (nbytes > 0) {
uint32_t this_size = size;
/* 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);
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);
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);
}
retval = dap_queue_ap_write(ap, MEM_AP_REG_DRW(dap), outvalue);
if (retval != ERROR_OK)
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 {
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);
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;
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;
/* 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 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;
}
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;
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.

View File

@ -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;

View File

@ -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);