#include "general.h"
#include <assert.h>
#include <stdlib.h>
#include <ftdi.h>
#include "ftdi_bmp.h"
#include "buffer_utils.h"
typedef enum swdio_status {
SWDIO_STATUS_DRIVE,
SWDIO_STATUS_FLOAT,
} swdio_status_e;
static swdio_status_e olddir;
static bool do_mpsse;
static bool direct_bb_swd;
#define MPSSE_MASK (MPSSE_DO | MPSSE_DI | MPSSE_CS)
#define MPSSE_TD_MASK (MPSSE_DO | MPSSE_DI)
#define MPSSE_TMS_SHIFT (MPSSE_WRITE_TMS | MPSSE_LSB | MPSSE_BITMODE | MPSSE_WRITE_NEG)
#define MPSSE_TDO_SHIFT (MPSSE_DO_WRITE | MPSSE_LSB | MPSSE_BITMODE | MPSSE_WRITE_NEG)
static bool ftdi_swd_seq_in_parity(uint32_t *res, size_t clock_cycles);
static uint32_t ftdi_swd_seq_in(size_t clock_cycles);
static void ftdi_swd_seq_out(uint32_t tms_states, size_t clock_cycles);
static void ftdi_swd_seq_out_parity(uint32_t tms_states, size_t clock_cycles);
bool ftdi_swd_possible(void)
{
const bool swd_read = active_cable.mpsse_swd_read.set_data_low || active_cable.mpsse_swd_read.clr_data_low ||
active_cable.mpsse_swd_read.set_data_high || active_cable.mpsse_swd_read.clr_data_high;
const bool swd_write = active_cable.mpsse_swd_write.set_data_low || active_cable.mpsse_swd_write.clr_data_low ||
active_cable.mpsse_swd_write.set_data_high || active_cable.mpsse_swd_write.clr_data_high;
do_mpsse = swd_read && swd_write;
if (do_mpsse)
return true;
const bool bb_swd_read = active_cable.bb_swd_read.set_data_low || active_cable.bb_swd_read.clr_data_low ||
active_cable.bb_swd_read.set_data_high || active_cable.bb_swd_read.clr_data_high;
const bool bb_swd_write = active_cable.bb_swd_write.set_data_low || active_cable.bb_swd_write.clr_data_low ||
active_cable.bb_swd_write.set_data_high || active_cable.bb_swd_write.clr_data_high;
const bool bb_direct_possible =
active_cable.bb_swdio_in_port_cmd == GET_BITS_LOW && active_cable.bb_swdio_in_pin == MPSSE_CS;
if (!bb_swd_read && !bb_swd_write && !bb_direct_possible)
return false;
direct_bb_swd = true;
return true;
}
bool ftdi_swd_init(void)
{
if (!ftdi_swd_possible()) {
DEBUG_ERROR("SWD not possible or missing item in adaptor description.\n");
return false;
}
DEBUG_PROBE("%s\n", __func__);
active_state.data_low &= ~MPSSE_SK;
active_state.data_low |= MPSSE_CS | MPSSE_DI | MPSSE_DO;
active_state.ddr_low &= ~(MPSSE_CS | MPSSE_DI | MPSSE_DO);
active_state.ddr_low |= MPSSE_SK;
if (do_mpsse) {
DEBUG_INFO("Using genuine MPSSE for SWD.\n");
active_state.data_low |= active_cable.mpsse_swd_read.set_data_low;
active_state.data_low &= ~active_cable.mpsse_swd_read.clr_data_low;
active_state.data_high |= active_cable.mpsse_swd_read.set_data_high;
active_state.data_high &= ~active_cable.mpsse_swd_read.clr_data_high;
} else if (direct_bb_swd) {
DEBUG_INFO("Using direct bitbang with SWDIO %cBUS%d.\n",
(active_cable.bb_swdio_in_port_cmd == GET_BITS_LOW) ? 'C' : 'D',
__builtin_ctz(active_cable.bb_swdio_in_pin));
} else {
DEBUG_INFO("Using switched bitbang for SWD.\n");
active_state.data_low |= active_cable.bb_swd_read.set_data_low;
active_state.data_low &= (~active_cable.bb_swd_read.clr_data_low);
active_state.data_high |= active_cable.bb_swd_read.set_data_high;
active_state.data_high &= (~active_cable.bb_swd_read.clr_data_high);
active_state.ddr_low |= MPSSE_CS;
if (active_cable.bb_swdio_in_port_cmd == GET_BITS_LOW)
active_state.ddr_low &= ~active_cable.bb_swdio_in_pin;
else if (active_cable.bb_swdio_in_port_cmd == GET_BITS_HIGH)
active_state.ddr_high &= ~active_cable.bb_swdio_in_pin;
}
const uint8_t cmd_write[6] = {
SET_BITS_LOW,
active_state.data_low,
active_state.ddr_low,
SET_BITS_HIGH,
active_state.data_high,
active_state.ddr_high,
};
ftdi_buffer_write_arr(cmd_write);
ftdi_buffer_flush();
olddir = SWDIO_STATUS_FLOAT;
swd_proc.seq_in = ftdi_swd_seq_in;
swd_proc.seq_in_parity = ftdi_swd_seq_in_parity;
swd_proc.seq_out = ftdi_swd_seq_out;
swd_proc.seq_out_parity = ftdi_swd_seq_out_parity;
return true;
}
static void ftdi_swd_turnaround_mpsse(const swdio_status_e dir)
{
if (dir == SWDIO_STATUS_FLOAT) {
active_state.data_low |= active_cable.mpsse_swd_read.set_data_low | MPSSE_DO;
active_state.data_low &= ~active_cable.mpsse_swd_read.clr_data_low;
active_state.ddr_low &= ~MPSSE_DO;
active_state.data_high |= active_cable.mpsse_swd_read.set_data_high;
active_state.data_high &= ~active_cable.mpsse_swd_read.clr_data_high;
const uint8_t cmd_read[6] = {
SET_BITS_LOW,
active_state.data_low,
active_state.ddr_low,
SET_BITS_HIGH,
active_state.data_high,
active_state.ddr_high,
};
ftdi_buffer_write_arr(cmd_read);
}
const ftdi_mpsse_cmd_s cmd = {MPSSE_TDO_SHIFT, {0}};
ftdi_buffer_write_val(cmd);
if (dir == SWDIO_STATUS_DRIVE) {
active_state.data_low |= active_cable.mpsse_swd_write.set_data_low | MPSSE_DO;
active_state.data_low &= ~active_cable.mpsse_swd_write.clr_data_low;
active_state.ddr_low |= MPSSE_DO;
active_state.data_high |= active_cable.mpsse_swd_write.set_data_high;
active_state.data_high &= ~active_cable.mpsse_swd_write.clr_data_high;
const uint8_t cmd_write[6] = {
SET_BITS_LOW,
active_state.data_low,
active_state.ddr_low,
SET_BITS_HIGH,
active_state.data_high,
active_state.ddr_high,
};
ftdi_buffer_write_arr(cmd_write);
}
}
static void ftdi_swd_turnaround_raw(const swdio_status_e dir)
{
uint8_t cmd[9];
if (dir == SWDIO_STATUS_FLOAT) {
if (direct_bb_swd) {
active_state.data_low |= MPSSE_CS;
active_state.ddr_low &= ~MPSSE_CS;
} else {
active_state.data_low |= active_cable.bb_swd_read.set_data_low;
active_state.data_low &= ~active_cable.bb_swd_read.clr_data_low;
active_state.data_high |= active_cable.bb_swd_read.set_data_high;
active_state.data_high &= ~active_cable.bb_swd_read.clr_data_high;
}
cmd[0] = SET_BITS_LOW;
cmd[1] = active_state.data_low;
cmd[2] = active_state.ddr_low;
cmd[3] = SET_BITS_HIGH;
cmd[4] = active_state.data_high;
cmd[5] = active_state.ddr_high;
cmd[6] = MPSSE_TMS_SHIFT;
cmd[7] = 0;
cmd[8] = 0;
} else if (dir == SWDIO_STATUS_DRIVE) {
cmd[0] = MPSSE_TMS_SHIFT;
cmd[1] = 0;
cmd[2] = 0;
if (direct_bb_swd) {
active_state.data_low |= MPSSE_CS;
active_state.ddr_low |= MPSSE_CS;
} else {
active_state.data_low |= active_cable.bb_swd_write.set_data_low;
active_state.data_low &= ~active_cable.bb_swd_write.clr_data_low;
active_state.data_high |= active_cable.bb_swd_write.set_data_high;
active_state.data_high &= ~active_cable.bb_swd_write.clr_data_high;
}
cmd[3] = SET_BITS_LOW;
cmd[4] = active_state.data_low;
cmd[5] = active_state.ddr_low;
cmd[6] = SET_BITS_HIGH;
cmd[7] = active_state.data_high;
cmd[8] = active_state.ddr_high;
}
ftdi_buffer_write_arr(cmd);
}
static void ftdi_swd_turnaround(const swdio_status_e dir)
{
if (dir == olddir)
return;
olddir = dir;
DEBUG_PROBE("%s: %s\n", __func__, dir == SWDIO_STATUS_FLOAT ? "float" : "drive");
if (do_mpsse)
ftdi_swd_turnaround_mpsse(dir);
else
ftdi_swd_turnaround_raw(dir);
}
static bool ftdi_swd_seq_in_parity_mpsse(uint32_t *const result, const size_t clock_cycles)
{
uint8_t data_out[5U] = {0};
ftdi_jtag_tdi_tdo_seq(data_out, false, NULL, clock_cycles + 1U);
const uint32_t data = read_le4(data_out, 0);
uint8_t parity = __builtin_parity(data & ((UINT64_C(1) << clock_cycles) - 1U));
parity ^= data_out[4] & 1U;
DEBUG_PROBE("%s %zu clock_cycles: %08" PRIx32 " %s\n", __func__, clock_cycles, data, parity ? "ERR" : "OK");
*result = data;
return parity;
}
static bool ftdi_swd_seq_in_parity_raw(uint32_t *const result, const size_t clock_cycles)
{
const uint8_t cmd[4] = {
active_cable.bb_swdio_in_port_cmd,
MPSSE_TMS_SHIFT,
0,
0,
};
for (size_t clock_cycle = 0; clock_cycle <= clock_cycles; ++clock_cycle)
ftdi_buffer_write_arr(cmd);
uint8_t raw_data[33];
ftdi_buffer_read(raw_data, clock_cycles + 1U);
uint8_t parity = (raw_data[clock_cycles] & active_cable.bb_swdio_in_pin) ? 1 : 0;
uint32_t data = 0;
for (size_t clock_cycle = 0; clock_cycle < clock_cycles; ++clock_cycle) {
if (raw_data[clock_cycle] & active_cable.bb_swdio_in_pin) {
parity ^= 1U;
data |= 1U << clock_cycle;
}
}
DEBUG_PROBE("%s %zu clock_cycles: %08" PRIx32 " %s\n", __func__, clock_cycles, data, parity ? "ERR" : "OK");
*result = data;
return parity;
}
static bool ftdi_swd_seq_in_parity(uint32_t *const result, const size_t clock_cycles)
{
if (clock_cycles > 32U)
return false;
ftdi_swd_turnaround(SWDIO_STATUS_FLOAT);
if (do_mpsse)
return ftdi_swd_seq_in_parity_mpsse(result, clock_cycles);
return ftdi_swd_seq_in_parity_raw(result, clock_cycles);
}
static uint32_t ftdi_swd_seq_in_mpsse(const size_t clock_cycles)
{
uint8_t data_out[4];
ftdi_jtag_tdi_tdo_seq(data_out, false, NULL, clock_cycles);
size_t bytes = clock_cycles >> 3U;
if (clock_cycles & 7U)
bytes++;
uint32_t result = 0U;
for (size_t i = 0U; i < bytes; i++)
result |= data_out[i] << (8U * i);
DEBUG_PROBE("%s %zu clock_cycles: %08" PRIx32 "\n", __func__, clock_cycles, result);
return result;
}
static uint32_t ftdi_swd_seq_in_raw(const size_t clock_cycles)
{
const uint8_t cmd[4] = {
active_cable.bb_swdio_in_port_cmd,
MPSSE_TMS_SHIFT,
0U,
0U,
};
for (size_t clock_cycle = 0U; clock_cycle < clock_cycles; ++clock_cycle)
ftdi_buffer_write_arr(cmd);
uint8_t data[32];
ftdi_buffer_read(data, clock_cycles);
uint32_t result = 0U;
for (size_t clock_cycle = 0U; clock_cycle < clock_cycles; ++clock_cycle) {
if (data[clock_cycle] & active_cable.bb_swdio_in_pin)
result |= (1U << clock_cycle);
}
DEBUG_PROBE("%s %zu clock_cycles: %08" PRIx32 "\n", __func__, clock_cycles, result);
return result;
}
static uint32_t ftdi_swd_seq_in(size_t clock_cycles)
{
if (!clock_cycles || clock_cycles > 32U)
return 0;
ftdi_swd_turnaround(SWDIO_STATUS_FLOAT);
if (do_mpsse)
return ftdi_swd_seq_in_mpsse(clock_cycles);
return ftdi_swd_seq_in_raw(clock_cycles);
}
static void ftdi_swd_seq_out_mpsse(const uint32_t tms_states, const size_t clock_cycles)
{
DEBUG_PROBE("%s %zu clock_cycles: %08" PRIx32 "\n", __func__, clock_cycles, tms_states);
uint8_t data_in[4] = {0};
write_le4(data_in, 0, tms_states);
ftdi_jtag_tdi_tdo_seq(NULL, false, data_in, clock_cycles);
}
static void ftdi_swd_seq_out_raw(uint32_t tms_states, const size_t clock_cycles)
{
DEBUG_PROBE("%s %zu clock_cycles: %08" PRIx32 "\n", __func__, clock_cycles, tms_states);
uint8_t cmd[15U] = {0};
size_t offset = 0U;
for (size_t cycle = 0U; cycle < clock_cycles; cycle += 7U, offset += 3U) {
const size_t cycles = MIN(7U, clock_cycles - cycle);
cmd[offset] = MPSSE_TMS_SHIFT;
cmd[offset + 1U] = cycles - 1U;
cmd[offset + 2U] = tms_states & 0x7fU;
}
ftdi_buffer_write(cmd, offset);
}
static void ftdi_swd_seq_out(const uint32_t tms_states, const size_t clock_cycles)
{
if (!clock_cycles || clock_cycles > 32U)
return;
ftdi_swd_turnaround(SWDIO_STATUS_DRIVE);
if (do_mpsse)
ftdi_swd_seq_out_mpsse(tms_states, clock_cycles);
else
ftdi_swd_seq_out_raw(tms_states, clock_cycles);
}
static void ftdi_swd_seq_out_parity_mpsse(const uint32_t tms_states, const uint8_t parity, const size_t clock_cycles)
{
DEBUG_PROBE("%s %zu clock_cycles: %08" PRIx32 "\n", __func__, clock_cycles, tms_states);
uint8_t data_in[6] = {0};
write_le4(data_in, 0, tms_states);
const size_t parity_offset = clock_cycles >> 3U;
const size_t parity_shift = clock_cycles & 7U;
data_in[parity_offset] = parity << parity_shift;
ftdi_jtag_tdi_tdo_seq(NULL, false, data_in, clock_cycles + 9U);
}
static void ftdi_swd_seq_out_parity_raw(const uint32_t tms_states, const uint8_t parity, const size_t clock_cycles)
{
DEBUG_PROBE("%s %zu clock_cycles: %08" PRIx32 "\n", __func__, clock_cycles, tms_states);
uint8_t cmd[18U] = {0};
size_t offset = 0;
for (size_t cycle = 0U; cycle < clock_cycles; cycle += 7U, offset += 3U) {
const size_t cycles = MIN(7U, clock_cycles - cycle);
cmd[offset] = MPSSE_TMS_SHIFT;
cmd[offset + 1U] = cycles - 1U;
cmd[offset + 2U] = tms_states & 0x7fU;
}
const div_t parity_index = div((int32_t)clock_cycles, 7U);
const uint32_t parity_offset = parity_index.quot * 3U;
cmd[parity_offset] = MPSSE_TMS_SHIFT;
cmd[parity_offset + 1U] = 6U;
cmd[parity_offset + 2U] |= (parity << parity_index.rem);
size_t idle_remaining = parity_index.rem + 2U;
const size_t idle_cycles = MIN(7U, idle_remaining);
cmd[offset] = MPSSE_TMS_SHIFT;
cmd[offset + 1U] = idle_cycles - 1U;
cmd[offset + 2U] = 0U;
offset += 3U;
if (idle_remaining == 8U) {
cmd[offset] = MPSSE_TMS_SHIFT;
cmd[offset + 1U] = 0U;
cmd[offset + 2U] = 0U;
offset += 3U;
}
ftdi_buffer_write(cmd, offset);
}
static void ftdi_swd_seq_out_parity(uint32_t tms_states, size_t clock_cycles)
{
if (clock_cycles > 32U)
return;
const uint8_t parity = __builtin_parity(tms_states) & 1U;
ftdi_swd_turnaround(SWDIO_STATUS_DRIVE);
if (do_mpsse)
ftdi_swd_seq_out_parity_mpsse(tms_states, parity, clock_cycles);
else
ftdi_swd_seq_out_parity_raw(tms_states, parity, clock_cycles);
}