#include "general.h"
#include "gdb_if.h"
#include "target.h"
#include "buffer_utils.h"
#include <assert.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include "ftdi_bmp.h"
#include <ftdi.h>
#if defined(USE_USB_VERSION_BIT)
typedef struct ftdi_transfer_control ftdi_transfer_control_s;
#endif
#define BUF_SIZE 4096U
static uint8_t outbuf[BUF_SIZE];
static uint16_t bufptr = 0;
cable_desc_s active_cable;
data_desc_s active_state;
const cable_desc_s cable_desc[] = {
{
.vendor = 0x0403U,
.product = 0x6014U,
.interface = INTERFACE_A,
.bb_swdio_in_port_cmd = GET_BITS_LOW,
.bb_swdio_in_pin = MPSSE_CS,
.description = "UM232H",
.name = "um232h",
},
{
.vendor = 0x0403U,
.product = 0x6010U,
.interface = INTERFACE_A,
.init.data_low = PIN6,
.bb_swdio_in_port_cmd = GET_BITS_LOW,
.bb_swdio_in_pin = MPSSE_CS,
.assert_nrst.data_low = ~PIN6,
.assert_nrst.ddr_low = PIN6,
.deassert_nrst.data_low = PIN6,
.deassert_nrst.ddr_low = ~PIN6,
.description = "FLOSS-JTAG",
.name = "flossjtag",
},
{
.vendor = 0x0403U,
.product = 0x6010U,
.interface = INTERFACE_B,
.init.data_low = PIN4,
.init.ddr_low = PIN4,
.mpsse_swd_read.set_data_low = MPSSE_DO,
.mpsse_swd_write.set_data_low = MPSSE_DO,
.assert_nrst.data_low = ~PIN6,
.assert_nrst.ddr_low = PIN6,
.deassert_nrst.data_low = PIN6,
.deassert_nrst.ddr_low = ~PIN6,
.target_voltage_cmd = GET_BITS_LOW,
.target_voltage_pin = PIN4,
.description = "USBMATE",
.name = "usbmate",
},
{
.vendor = 0x0403U,
.product = 0x6014U,
.interface = INTERFACE_A,
.mpsse_swd_read.set_data_low = MPSSE_DO,
.mpsse_swd_write.set_data_low = MPSSE_DO,
.name = "ft232h_resistor_swd",
},
{
.vendor = 0x0403U,
.product = 0x6010U,
.interface = INTERFACE_A,
.init.ddr_low = PIN4,
.init.data_high = PIN4 | PIN3 | PIN2,
.init.ddr_high = PIN4 | PIN3 | PIN2 | PIN1 | PIN0,
.assert_nrst.data_high = ~PIN3,
.deassert_nrst.data_high = PIN3,
.nrst_get_port_cmd = GET_BITS_LOW,
.nrst_get_pin = PIN6,
.description = "FTDIJTAG",
.name = "ftdijtag",
},
{
.vendor = 0x0403U,
.product = 0x6010U,
.interface = INTERFACE_B,
.init.data_low = PIN6 | PIN5,
.init.ddr_low = PIN6 | PIN5,
.init.data_high = PIN1 | PIN2,
.assert_nrst.data_high = ~PIN1,
.assert_nrst.ddr_high = PIN1,
.deassert_nrst.data_high = PIN1,
.deassert_nrst.ddr_high = ~PIN1,
.mpsse_swd_read.clr_data_low = PIN5 | PIN6,
.mpsse_swd_write.set_data_low = PIN5,
.mpsse_swd_write.clr_data_low = PIN6,
.jtag.set_data_low = PIN6,
.target_voltage_cmd = GET_BITS_HIGH,
.target_voltage_pin = ~PIN2,
.name = "ftdiswd",
.description = "FTDISWD",
},
{
.vendor = 0x15b1U,
.product = 0x0003U,
.interface = INTERFACE_A,
.init.ddr_low = PIN5,
.name = "olimex",
},
{
.vendor = 0x0403U,
.product = 0xbdc8U,
.interface = INTERFACE_A,
.init.data_low = 0,
.init.ddr_low = PIN6 | PIN5 | PIN4,
.init.ddr_high = PIN2,
.assert_nrst.data_low = PIN6,
.deassert_nrst.data_low = ~PIN6,
.nrst_get_port_cmd = GET_BITS_HIGH,
.nrst_get_pin = PIN0,
.name = "turtelizer",
.description = "Turtelizer JTAG/RS232 Adapter",
},
{
.vendor = 0x0403U,
.product = 0xbdc8U,
.interface = INTERFACE_A,
.name = "jtaghs1",
},
{
.vendor = 0x0403U,
.product = 0xbdc8U,
.interface = INTERFACE_A,
.init.data_low = MPSSE_CS | MPSSE_DO | MPSSE_DI,
.init.ddr_low = MPSSE_CS | MPSSE_DO | MPSSE_SK,
.bb_swdio_in_port_cmd = GET_BITS_LOW,
.bb_swdio_in_pin = MPSSE_CS,
.name = "ftdi",
},
{
.vendor = 0x0403U,
.product = 0x6014U,
.interface = INTERFACE_A,
.init.data_low = PIN7,
.init.ddr_low = PIN7,
.init.data_high = PIN5,
.init.ddr_high = PIN5 | PIN4 | PIN3 | PIN2 | PIN1 | PIN0,
.name = "digilent",
},
{
.vendor = 0x0403U,
.product = 0x6014U,
.interface = INTERFACE_A,
.init.data_low = MPSSE_CS | MPSSE_DO | MPSSE_DI,
.init.ddr_low = MPSSE_CS | MPSSE_DO | MPSSE_SK,
.bb_swdio_in_port_cmd = GET_BITS_LOW,
.bb_swdio_in_pin = MPSSE_CS,
.name = "ft232h",
},
{
.vendor = 0x0403U,
.product = 0x6011U,
.interface = INTERFACE_A,
.mpsse_swd_read.set_data_low = MPSSE_DI,
.mpsse_swd_write.set_data_low = MPSSE_DO,
.description = "FT4232H-56Q MiniModule",
.name = "ft4232h",
},
{
.vendor = 0x15baU,
.product = 0x002bU,
.interface = INTERFACE_A,
.init.ddr_low = PIN4,
.init.data_high = PIN3 | PIN1 | PIN0,
.init.ddr_high = PIN4 | PIN3 | PIN1 | PIN0,
.name = "arm-usb-ocd-h",
},
{
.vendor = 0x0403U,
.product = 0x6010U,
.interface = INTERFACE_A,
.name = "esp-prog",
},
{
.vendor = 0x0403U,
.product = 0x6010U,
.interface = INTERFACE_B,
.init.data_high = PIN4 | PIN5,
.init.ddr_high = PIN4 | PIN5,
.assert_nrst.data_low = ~PIN5,
.assert_nrst.ddr_low = PIN5,
.deassert_nrst.data_low = PIN5,
.deassert_nrst.ddr_low = PIN5,
.nrst_get_pin = ~PIN5,
.target_voltage_cmd = GET_BITS_LOW,
.bb_swdio_in_port_cmd = GET_BITS_LOW,
.bb_swdio_in_pin = MPSSE_DI,
.mpsse_swd_read.set_data_low = MPSSE_DI,
.mpsse_swd_write.set_data_low = MPSSE_DO,
.description = "Tigard",
.name = "tigard",
},
{
.vendor = 0x0403U,
.product = 0x6010U,
.interface = INTERFACE_A,
.assert_nrst.data_low = ~PIN5,
.assert_nrst.ddr_low = PIN5,
.deassert_nrst.data_low = PIN5,
.deassert_nrst.ddr_low = ~PIN5,
.bb_swdio_in_port_cmd = GET_BITS_LOW,
.bb_swdio_in_pin = MPSSE_CS,
.name = "hifive1",
},
{
.vendor = 0x15b1U,
.product = 0x002aU,
.interface = INTERFACE_A,
.init.data_low = PIN4,
.init.ddr_low = PIN4 | PIN5,
.init.data_high = PIN2 | PIN4,
.init.ddr_high = PIN4,
.assert_nrst.data_high = ~PIN2,
.assert_nrst.ddr_high = PIN2,
.deassert_nrst.data_high = PIN2,
.deassert_nrst.ddr_high = ~PIN2,
.name = "arm-usb-tiny-h",
.description = "Olimex OpenOCD JTAG ARM-USB-TINY-H",
},
{0},
};
bool ftdi_lookup_adapter_from_vid_pid(bmda_cli_options_s *const cl_opts, const probe_info_s *const probe)
{
if (cl_opts->opt_serial && strstr(cl_opts->opt_serial, probe->serial))
return true;
if (cl_opts->opt_cable)
return true;
size_t adapter_count = 0;
const cable_desc_s *selection = NULL;
for (const cable_desc_s *cable = &cable_desc[0]; cable->vendor; ++cable) {
if (cable->vendor == probe->vid && cable->product == probe->pid) {
++adapter_count;
selection = cable;
}
}
if (adapter_count == 1)
cl_opts->opt_cable = selection->name;
return adapter_count == 1;
}
bool ftdi_lookup_cable_by_product(bmda_cli_options_s *cl_opts, const char *product)
{
if (cl_opts->opt_cable)
return false;
for (const cable_desc_s *cable = &cable_desc[0]; cable->vendor; ++cable) {
if (cable->description && strstr(product, cable->description) != 0) {
cl_opts->opt_cable = cable->name;
return true;
}
}
return false;
}
bool ftdi_lookup_adaptor_descriptor(bmda_cli_options_s *cl_opts, const probe_info_s *probe)
{
return ftdi_lookup_cable_by_product(cl_opts, probe->product);
}
bool ftdi_bmp_init(bmda_cli_options_s *const cl_opts)
{
int err;
const cable_desc_s *cable = cable_desc;
for (; cable->name; cable++) {
if (strncmp(cable->name, cl_opts->opt_cable, strlen(cable->name)) == 0)
break;
}
if (!cable->name) {
DEBUG_ERROR("No adaptor matching found for %s\n", cl_opts->opt_cable);
return false;
}
active_cable = *cable;
memcpy(&active_state, &active_cable.init, sizeof(data_desc_s));
if (active_cable.description && memcmp(active_cable.description, "Tigard", 7) == 0)
active_cable.description = NULL;
if (cl_opts->external_resistor_swd && active_cable.mpsse_swd_read.set_data_low == 0 &&
active_cable.mpsse_swd_read.clr_data_low == 0 && active_cable.mpsse_swd_read.set_data_high == 0 &&
active_cable.mpsse_swd_read.clr_data_high == 0 && active_cable.mpsse_swd_write.set_data_low == 0 &&
active_cable.mpsse_swd_write.clr_data_low == 0 && active_cable.mpsse_swd_write.set_data_high == 0 &&
active_cable.mpsse_swd_write.clr_data_high == 0) {
DEBUG_INFO("Using external resistor SWD\n");
active_cable.mpsse_swd_read.set_data_low = MPSSE_DO;
active_cable.mpsse_swd_write.set_data_low = MPSSE_DO;
} else if (!ftdi_swd_possible() && cl_opts->opt_scanmode != BMP_SCAN_JTAG) {
DEBUG_WARN("SWD with adaptor not possible, trying JTAG\n");
cl_opts->opt_scanmode = BMP_SCAN_JTAG;
}
ftdi_context_s *ctx = ftdi_new();
if (ctx == NULL) {
DEBUG_ERROR("ftdi_new: %s\n", ftdi_get_error_string(ctx));
abort();
}
err = ftdi_set_interface(ctx, active_cable.interface);
if (err != 0) {
DEBUG_ERROR("ftdi_set_interface: %d: %s\n", err, ftdi_get_error_string(ctx));
goto error_1;
}
err = ftdi_usb_open_desc(
ctx, active_cable.vendor, active_cable.product, active_cable.description, cl_opts->opt_serial);
if (err != 0) {
DEBUG_ERROR("unable to open ftdi device: %d (%s)\n", err, ftdi_get_error_string(ctx));
goto error_1;
}
err = ftdi_set_latency_timer(ctx, 1);
if (err != 0) {
DEBUG_ERROR("ftdi_set_latency_timer: %d: %s\n", err, ftdi_get_error_string(ctx));
goto error_2;
}
err = ftdi_set_baudrate(ctx, 1000000);
if (err != 0) {
DEBUG_ERROR("ftdi_set_baudrate: %d: %s\n", err, ftdi_get_error_string(ctx));
goto error_2;
}
err = ftdi_write_data_set_chunksize(ctx, BUF_SIZE);
if (err != 0) {
DEBUG_ERROR("ftdi_write_data_set_chunksize: %d: %s\n", err, ftdi_get_error_string(ctx));
goto error_2;
}
assert(ctx != NULL);
#ifdef _Ftdi_Pragma
err = ftdi_tcioflush(ctx);
#else
err = ftdi_usb_purge_buffers(ctx);
#endif
if (err != 0) {
DEBUG_ERROR("ftdi_tcioflush(ftdi_usb_purge_buffer): %d: %s\n", err, ftdi_get_error_string(ctx));
goto error_2;
}
err = ftdi_set_bitmode(ctx, 0, BITMODE_RESET);
if (err != 0) {
DEBUG_ERROR("ftdi_set_bitmode: %d: %s\n", err, ftdi_get_error_string(ctx));
goto error_2;
}
err = ftdi_set_bitmode(ctx, 0, BITMODE_MPSSE);
if (err != 0) {
DEBUG_ERROR("ftdi_set_bitmode: %d: %s\n", err, ftdi_get_error_string(ctx));
goto error_2;
}
uint8_t ftdi_init[16];
int garbage = ftdi_read_data(ctx, ftdi_init, sizeof(ftdi_init));
if (garbage > 0) {
DEBUG_WARN("FTDI init garbage at start:");
for (int i = 0; i < garbage; i++)
DEBUG_WARN(" %02x", ftdi_init[i]);
DEBUG_WARN("\n");
}
size_t index = 0;
ftdi_init[index++] = LOOPBACK_END;
switch (ctx->type) {
case TYPE_2232H:
case TYPE_4232H:
case TYPE_232H:
ftdi_init[index++] = DIS_DIV_5;
break;
case TYPE_2232C:
break;
default:
DEBUG_ERROR("FTDI Chip has no MPSSE\n");
goto error_2;
}
bmda_probe_info.ftdi_ctx = ctx;
ftdi_init[index++] = TCK_DIVISOR;
ftdi_init[index++] = 1;
ftdi_init[index++] = 0;
ftdi_init[index++] = SET_BITS_LOW;
ftdi_init[index++] = active_state.data_low;
ftdi_init[index++] = active_state.ddr_low;
ftdi_init[index++] = SET_BITS_HIGH;
ftdi_init[index++] = active_state.data_high;
ftdi_init[index++] = active_state.ddr_high;
ftdi_buffer_write(ftdi_init, index);
ftdi_buffer_flush();
garbage = ftdi_read_data(ctx, ftdi_init, sizeof(ftdi_init));
if (garbage > 0) {
DEBUG_WARN("FTDI init garbage at end:");
for (int i = 0; i < garbage; i++)
DEBUG_WARN(" %02x", ftdi_init[i]);
DEBUG_WARN("\n");
}
return true;
error_2:
ftdi_usb_close(ctx);
error_1:
ftdi_free(ctx);
return false;
}
static void libftdi_set_data(data_desc_s *data)
{
uint8_t cmd[6];
size_t index = 0;
if (data->data_low || data->ddr_low) {
if (data->data_low && !(data->data_low & 0x8000U))
active_state.data_low |= data->data_low & 0xffU;
else if (data->data_low & 0x8000U)
active_state.data_low &= data->data_low & 0xffU;
if (data->ddr_low && !(data->ddr_low & 0x8000U))
active_state.ddr_low |= data->ddr_low & 0xffU;
else if (data->ddr_low & 0x8000U)
active_state.ddr_low &= data->ddr_low & 0xffU;
cmd[index + 0U] = SET_BITS_LOW;
cmd[index + 1U] = active_state.data_low;
cmd[index + 2U] = active_state.ddr_low;
index += 3U;
}
if (data->data_high || data->ddr_high) {
if (data->data_high && !(data->data_high & 0x8000U))
active_state.data_high |= data->data_high & 0xffU;
else if (data->data_high & 0x8000U)
active_state.data_high &= data->data_high & 0xffU;
if (data->ddr_high && !(data->ddr_high & 0x8000U))
active_state.ddr_high |= data->ddr_high & 0xffU;
else if (data->ddr_high & 0x8000U)
active_state.ddr_high &= data->ddr_high & 0xffU;
cmd[index + 0U] = SET_BITS_HIGH;
cmd[index + 1U] = active_state.data_high;
cmd[index + 2U] = active_state.ddr_high;
index += 3U;
}
if (index) {
ftdi_buffer_write(cmd, index);
ftdi_buffer_flush();
}
}
void libftdi_nrst_set_val(bool assert)
{
if (assert)
libftdi_set_data(&active_cable.assert_nrst);
else
libftdi_set_data(&active_cable.deassert_nrst);
}
bool ftdi_nrst_get_val(void)
{
uint8_t cmd;
uint8_t pin = 0;
if (active_cable.nrst_get_port_cmd && active_cable.nrst_get_pin) {
cmd = active_cable.nrst_get_port_cmd;
pin = active_cable.nrst_get_pin;
} else if (active_cable.assert_nrst.data_low && active_cable.assert_nrst.ddr_low) {
cmd = GET_BITS_LOW;
pin = active_cable.assert_nrst.data_low;
} else if (active_cable.assert_nrst.data_high && active_cable.assert_nrst.ddr_high) {
cmd = GET_BITS_HIGH;
pin = active_cable.assert_nrst.data_high;
} else
return false;
uint8_t data;
ftdi_buffer_write_val(cmd);
ftdi_buffer_read_val(data);
bool res = false;
if (pin < 0x7fU || pin == PIN7)
res = data & pin;
else
res = !(data & ~pin);
return res;
}
#if defined(USE_USB_VERSION_BIT)
static ftdi_transfer_control_s *tc_write = NULL;
#endif
void ftdi_buffer_flush(void)
{
if (!bufptr)
return;
DEBUG_WIRE("%s: %u bytes\n", __func__, bufptr);
#if defined(USE_USB_VERSION_BIT)
if (tc_write)
ftdi_transfer_data_done(tc_write);
tc_write = ftdi_write_data_submit(bmda_probe_info.ftdi_ctx, outbuf, bufptr);
#else
assert(ftdi_write_data(bmda_probe_info.ftdi_ctx, outbuf, bufptr) == bufptr);
#endif
bufptr = 0;
}
size_t ftdi_buffer_write(const void *const buffer, const size_t size)
{
if ((bufptr + size) / BUF_SIZE > 0)
ftdi_buffer_flush();
const uint8_t *const data = (const uint8_t *)buffer;
DEBUG_WIRE("%s: %zu bytes:", __func__, size);
for (size_t i = 0; i < size; i++) {
DEBUG_WIRE(" %02x", data[i]);
if (i && (i & 0xfU) == 0xfU)
DEBUG_WIRE("\n\t");
}
DEBUG_WIRE("\n");
memcpy(outbuf + bufptr, buffer, size);
bufptr += size;
return size;
}
size_t ftdi_buffer_read(void *const buffer, const size_t size)
{
if (bufptr) {
const uint8_t cmd = SEND_IMMEDIATE;
ftdi_buffer_write(&cmd, 1);
ftdi_buffer_flush();
}
uint8_t *const data = (uint8_t *)buffer;
#if defined(USE_USB_VERSION_BIT)
ftdi_transfer_control_s *transfer = ftdi_read_data_submit(bmda_probe_info.ftdi_ctx, data, (int)size);
ftdi_transfer_data_done(transfer);
#else
for (size_t index = 0; index < size;)
index += ftdi_read_data(bmda_probe_info.ftdi_ctx, data + index, size - index);
#endif
DEBUG_WIRE("%s: %zu bytes:", __func__, size);
for (size_t i = 0; i < size; i++) {
DEBUG_WIRE(" %02x", data[i]);
if ((i & 0xfU) == 0xfU)
DEBUG_WIRE("\n\t");
}
DEBUG_WIRE("\n");
return size;
}
void ftdi_jtag_tdi_tdo_seq(uint8_t *data_out, const bool final_tms, const uint8_t *data_in, size_t clock_cycles)
{
if (!clock_cycles || (!data_in && !data_out))
return;
DEBUG_PROBE("%s: %s %zu clock cycles\n", __func__,
data_in && data_out ? "read/write" :
data_in ? "write" :
"read",
clock_cycles);
const size_t bytes = (clock_cycles - (final_tms ? 1U : 0U)) >> 3U;
size_t bits = clock_cycles & 7U;
if (!bits && final_tms)
bits = 8U;
const size_t final_byte = (clock_cycles - 1U) >> 3U;
const size_t final_bit = (clock_cycles - 1U) & 7U;
const uint8_t cmd =
(data_out ? MPSSE_DO_READ : 0U) | (data_in ? (MPSSE_DO_WRITE | MPSSE_WRITE_NEG) : 0U) | MPSSE_LSB;
if (bytes) {
ftdi_mpsse_cmd_s command = {cmd};
write_le2(command.length, 0, bytes - 1U);
ftdi_buffer_write_val(command);
if (data_in)
ftdi_buffer_write(data_in, bytes);
}
if (bits - (final_tms ? 1U : 0U)) {
const ftdi_mpsse_cmd_bits_s command = {cmd | MPSSE_BITMODE, bits - (final_tms ? 2U : 1U)};
ftdi_buffer_write_val(command);
if (data_in)
ftdi_buffer_write_val(data_in[bytes]);
}
if (final_tms) {
ftdi_mpsse_cmd_bits_s command = {
MPSSE_WRITE_TMS | (data_out ? MPSSE_DO_READ : 0) | MPSSE_LSB | MPSSE_BITMODE | MPSSE_WRITE_NEG};
ftdi_buffer_write_val(command);
uint8_t data = 1U;
if (data_in) {
const uint8_t value = (data_in[final_byte] >> final_bit) & 1U;
data |= value << 7U;
}
ftdi_buffer_write_val(data);
}
if (data_out) {
if (bytes)
ftdi_buffer_read(data_out, bytes);
if (bits) {
ftdi_buffer_read_val(data_out[bytes]);
const size_t shift = bits - (final_tms ? 1U : 0U);
data_out[bytes] >>= 8U - shift;
}
if (final_tms) {
uint8_t value = 0;
ftdi_buffer_read_val(value);
data_out[final_byte] |= (value & 0x80U) >> (7U - final_bit);
}
}
}
const char *ftdi_target_voltage(void)
{
uint8_t pin = active_cable.target_voltage_pin;
if (active_cable.target_voltage_cmd && pin) {
ftdi_buffer_write(&active_cable.target_voltage_cmd, 1);
uint8_t data[1];
ftdi_buffer_read(data, 1);
bool res = false;
if (pin < 0x7fU || pin == PIN7)
res = data[0] & pin;
else
res = !(data[0] & ~pin);
if (res)
return "Present";
return "Absent";
}
return NULL;
}
static uint16_t divisor;
void ftdi_max_frequency_set(uint32_t freq)
{
uint32_t clock;
if (bmda_probe_info.ftdi_ctx->type == TYPE_2232C)
clock = 12U * 1000U * 1000U;
else
clock = 60U * 1000U * 1000U;
uint32_t div = (clock + 2U * freq - 1U) / freq;
if (div < 4U && bmda_probe_info.ftdi_ctx->type == TYPE_2232C)
div = 4U;
divisor = div / 2U - 1U;
uint8_t buf[3];
buf[0] = TCK_DIVISOR;
buf[1] = divisor & 0xffU;
buf[2] = (divisor >> 8U) & 0xffU;
ftdi_buffer_write_arr(buf);
}
uint32_t libftdi_max_frequency_get(void)
{
uint32_t clock;
if (bmda_probe_info.ftdi_ctx->type == TYPE_2232C)
clock = 12U * 1000U * 1000U;
else
clock = 60U * 1000U * 1000U;
return clock / (2U * (divisor + 1U));
}