#include "general.h"
#include "exception.h"
#include "adiv5.h"
#include "swd.h"
#include "target.h"
#include "target_internal.h"
uint8_t make_packet_request(uint8_t RnW, uint16_t addr)
{
bool APnDP = addr & ADIV5_APnDP;
addr &= 0xffU;
uint8_t request = 0x81U;
if (APnDP)
request ^= 0x22U;
if (RnW)
request ^= 0x24U;
addr &= 0xcU;
request |= (addr << 1U) & 0x18U;
if (addr == 4U || addr == 8U)
request ^= 0x20U;
return request;
}
static void swd_line_reset_sequence(const bool idle_cycles)
{
swd_proc.seq_out(0xffffffffU, 32U);
swd_proc.seq_out(0x0fffffffU, idle_cycles ? 32U : 28U);
}
static void dormant_to_swd_sequence()
{
DEBUG_INFO("Switching out of dormant state into SWD\n");
swd_line_reset_sequence(false);
swd_proc.seq_out(ADIV5_SELECTION_ALERT_SEQUENCE_0, 32U);
swd_proc.seq_out(ADIV5_SELECTION_ALERT_SEQUENCE_1, 32U);
swd_proc.seq_out(ADIV5_SELECTION_ALERT_SEQUENCE_2, 32U);
swd_proc.seq_out(ADIV5_SELECTION_ALERT_SEQUENCE_3, 32U);
swd_proc.seq_out(ADIV5_ACTIVATION_CODE_ARM_SWD_DP << 4U, 12U);
swd_line_reset_sequence(true);
}
static void jtag_to_swd_sequence()
{
DEBUG_WARN("Deprecated TAG-to-SWD sequence\n");
swd_line_reset_sequence(false);
swd_proc.seq_out(ADIV5_JTAG_TO_SWD_SELECT_SEQUENCE, 16U);
swd_line_reset_sequence(true);
}
bool firmware_dp_low_write(const uint16_t addr, const uint32_t data)
{
const uint8_t request = make_packet_request(ADIV5_LOW_WRITE, addr);
swd_proc.seq_out(request, 8U);
const uint8_t res = swd_proc.seq_in(3U);
swd_proc.seq_out_parity(data, 32U);
swd_proc.seq_out(0, 8U);
return res != SWDP_ACK_OK;
}
static uint32_t firmware_dp_low_read(const uint16_t addr)
{
const uint8_t request = make_packet_request(ADIV5_LOW_READ, addr);
swd_proc.seq_out(request, 8U);
const uint8_t res = swd_proc.seq_in(3U);
uint32_t data = 0;
swd_proc.seq_in_parity(&data, 32U);
return res == SWDP_ACK_OK ? data : 0;
}
bool adiv5_swd_scan(const uint32_t targetid)
{
target_list_free();
adiv5_debug_port_s *dp = calloc(1, sizeof(*dp));
if (!dp) {
DEBUG_ERROR("calloc: failed in %s\n", __func__);
return false;
}
dp->dp_low_write = firmware_dp_low_write;
dp->error = firmware_swdp_error;
dp->dp_read = firmware_swdp_read;
dp->low_access = firmware_swdp_low_access;
dp->abort = firmware_swdp_abort;
#if PC_HOSTED == 0
swdptap_init();
#else
if (!bmda_swd_dp_init(dp)) {
free(dp);
return false;
}
#endif
platform_target_clk_output_enable(true);
dormant_to_swd_sequence();
uint32_t dp_targetid = targetid;
if (!dp_targetid) {
uint32_t dpidr = 0;
bool tried_jtag_to_swd = false;
while (true) {
dpidr = adiv5_dp_read_dpidr(dp);
if (dpidr != 0)
break;
if (!tried_jtag_to_swd) {
jtag_to_swd_sequence();
dp->fault = 0;
tried_jtag_to_swd = true;
continue;
}
DEBUG_ERROR("No usable DP found\n");
free(dp);
return false;
}
dp->version = (dpidr & ADIV5_DP_DPIDR_VERSION_MASK) >> ADIV5_DP_DPIDR_VERSION_OFFSET;
if (dp->version >= 2U) {
adiv5_dp_write(dp, ADIV5_DP_SELECT, ADIV5_DP_BANK2);
dp_targetid = adiv5_dp_read(dp, ADIV5_DP_TARGETID);
adiv5_dp_write(dp, ADIV5_DP_SELECT, ADIV5_DP_BANK0);
}
}
#if PC_HOSTED == 0
const
#endif
bool scan_multidrop = targetid || dp->version >= 2U;
#if PC_HOSTED == 1
if (scan_multidrop && !dp->dp_low_write) {
DEBUG_WARN("Discovered multi-drop enabled target but CMSIS_DAP < v1.2 cannot handle multi-drop\n");
scan_multidrop = false;
}
#endif
if (scan_multidrop)
adiv5_swd_multidrop_scan(dp, dp_targetid);
else {
adiv5_dp_abort(dp, ADIV5_DP_ABORT_STKERRCLR);
adiv5_dp_init(dp);
}
return target_list != NULL;
}
void adiv5_swd_multidrop_scan(adiv5_debug_port_s *const dp, const uint32_t targetid)
{
DEBUG_INFO("Handling swd multi-drop, TARGETID 0x%08" PRIx32 "\n", targetid);
for (size_t instance = 0; instance < 16U; instance++) {
swd_line_reset_sequence(true);
dp->fault = 0;
dp->dp_low_write(ADIV5_DP_TARGETSEL,
instance << ADIV5_DP_TARGETSEL_TINSTANCE_OFFSET |
(targetid & (ADIV5_DP_TARGETID_TDESIGNER_MASK | ADIV5_DP_TARGETID_TPARTNO_MASK)) | 1U);
if (adiv5_dp_read_dpidr(dp) == 0)
continue;
adiv5_debug_port_s *const target_dp = calloc(1, sizeof(*dp));
if (!dp) {
DEBUG_ERROR("calloc: failed in %s\n", __func__);
break;
}
memcpy(target_dp, dp, sizeof(*dp));
target_dp->instance = instance;
adiv5_dp_abort(target_dp, ADIV5_DP_ABORT_STKERRCLR);
adiv5_dp_init(target_dp);
}
free(dp);
}
uint32_t firmware_swdp_read(adiv5_debug_port_s *dp, uint16_t addr)
{
if (addr & ADIV5_APnDP) {
adiv5_dp_recoverable_access(dp, ADIV5_LOW_READ, addr, 0);
return adiv5_dp_low_access(dp, ADIV5_LOW_READ, ADIV5_DP_RDBUFF, 0);
}
return adiv5_dp_recoverable_access(dp, ADIV5_LOW_READ, addr, 0);
}
uint32_t firmware_swdp_error(adiv5_debug_port_s *dp, const bool protocol_recovery)
{
if ((dp->version >= 2U && dp->fault) || protocol_recovery) {
swd_line_reset_sequence(true);
if (dp->version >= 2U)
firmware_dp_low_write(ADIV5_DP_TARGETSEL, dp->targetsel);
firmware_dp_low_read(ADIV5_DP_DPIDR);
}
const uint32_t err = firmware_dp_low_read(ADIV5_DP_CTRLSTAT) &
(ADIV5_DP_CTRLSTAT_STICKYORUN | ADIV5_DP_CTRLSTAT_STICKYCMP | ADIV5_DP_CTRLSTAT_STICKYERR |
ADIV5_DP_CTRLSTAT_WDATAERR);
uint32_t clr = 0;
if (err & ADIV5_DP_CTRLSTAT_STICKYORUN)
clr |= ADIV5_DP_ABORT_ORUNERRCLR;
if (err & ADIV5_DP_CTRLSTAT_STICKYCMP)
clr |= ADIV5_DP_ABORT_STKCMPCLR;
if (err & ADIV5_DP_CTRLSTAT_STICKYERR)
clr |= ADIV5_DP_ABORT_STKERRCLR;
if (err & ADIV5_DP_CTRLSTAT_WDATAERR)
clr |= ADIV5_DP_ABORT_WDERRCLR;
if (clr)
firmware_dp_low_write(ADIV5_DP_ABORT, clr);
dp->fault = 0;
return err;
}
uint32_t firmware_swdp_low_access(adiv5_debug_port_s *dp, const uint8_t RnW, const uint16_t addr, const uint32_t value)
{
if ((addr & ADIV5_APnDP) && dp->fault)
return 0;
const uint8_t request = make_packet_request(RnW, addr);
uint32_t response = 0;
uint8_t ack = SWDP_ACK_WAIT;
platform_timeout_s timeout;
platform_timeout_set(&timeout, 250U);
do {
swd_proc.seq_out(request, 8U);
ack = swd_proc.seq_in(3U);
if (ack == SWDP_ACK_FAULT) {
DEBUG_ERROR("SWD access resulted in fault, retrying\n");
adiv5_dp_write(dp, ADIV5_DP_ABORT,
ADIV5_DP_ABORT_ORUNERRCLR | ADIV5_DP_ABORT_WDERRCLR | ADIV5_DP_ABORT_STKERRCLR |
ADIV5_DP_ABORT_STKCMPCLR);
}
} while ((ack == SWDP_ACK_WAIT || ack == SWDP_ACK_FAULT) && !platform_timeout_is_expired(&timeout));
if (ack == SWDP_ACK_WAIT) {
DEBUG_ERROR("SWD access resulted in wait, aborting\n");
dp->abort(dp, ADIV5_DP_ABORT_DAPABORT);
dp->fault = ack;
return 0;
}
if (ack == SWDP_ACK_FAULT) {
DEBUG_ERROR("SWD access resulted in fault\n");
dp->fault = ack;
return 0;
}
if (ack == SWDP_ACK_NO_RESPONSE) {
DEBUG_ERROR("SWD access resulted in no response\n");
dp->fault = ack;
return 0;
}
if (ack != SWDP_ACK_OK) {
DEBUG_ERROR("SWD access has invalid ack %x\n", ack);
raise_exception(EXCEPTION_ERROR, "SWD invalid ACK");
}
if (RnW) {
if (swd_proc.seq_in_parity(&response, 32U)) {
dp->fault = 1U;
DEBUG_ERROR("SWD access resulted in parity error\n");
raise_exception(EXCEPTION_ERROR, "SWD parity error");
}
} else {
swd_proc.seq_out_parity(value, 32U);
swd_proc.seq_out(0, 8U);
}
return response;
}
void firmware_swdp_abort(adiv5_debug_port_s *dp, uint32_t abort)
{
adiv5_dp_write(dp, ADIV5_DP_ABORT, abort);
}