Если вдруг вам потребуется отлаживать несколько микроконтроллеров/микропроцессоров в Крыму, попивая смузи из душного офиса в Химках. Если микропроцессорная плата находится на подвижном объекте и нет возможности дотянуть до нее JTAG отладчик (плата находится на воздушном шарике/квадрокоптере). Если вдруг просто требуется гальваническая изоляция между хостом и отлаживаемой платой (допустим, высоковольтное устройство). И хорошо, что бы еще дешево, сердито и универсально к производителю(STM,Broadcom, Xilinx, etc) или архитектуре(ARM, MIPS, FPGA, etc). Тогда вам потребуется роутер, да-да, просто роутер, допустим, вот такой.

image
Картинка с сайта sagemcom.ru

Заглянем внутрь:

image
wiki.openwrt.org

Итак, это Sagem F@ST2704 V2, распространяемый Ростелекомом по всей стране. Имеем SoC BCM6328 ядро MIPS архитектуры, 320 MHz, пара распаянных USB портов [1]. Есть wifi и ethernet. И самое приятное — это релиз openwrt на эту модель. Всё, что нужно из оборудования для поставленных выше целей.

Сразу возникает мысль подцепить st-link и попробовать пробросить USB через сеть. Выглядит костыльно, скорее всего обещает работать не быстро и не очень стабильно, оверхед получается громадный. Смотрим дальше, что можно сделать.

Можно портировать openocd на openwrt, подцепить st-link или ftdi-микросхему и запустить gdb-сервер. Благо в openwrt уже портировали openocd. Вроде достаточно уже остановиться на этом варианте. Но хочется посмотреть еще какие варианты нам дает openocd. И тут в документации попадается интерфейс sysfsgpio. То, что нужно, возможно управлять сигналами tck, tdi, tdo, штатными средствами OS linux через /sys/class/gpio на распаянных пинах чипа.

Пробуем. Для начала собираем openwrt (использую ветку chaos_calmer) вместе c openocd. По умолчанию на распаянных GPIO закреплены функции световой индикации, а так же опрос кнопок для выполнения некоторых команд (rfkill,reset и wpsc ). Чтобы они не мешались, я их выключил, убрав из сборки соответствующие модули ядра.

$cat target/linux/brcm63xx/config-3.18 b/target/linux/brcm63xx/config-3.18
...
# CONFIG_NEW_LEDS is not set
…
$cat .config
… 
# CONFIG_PACKAGE_kmod-input-gpio-keys-polled is not set
...
# CONFIG_PACKAGE_kmod-input-polldev is not set
...
CONFIG_PACKAGE_openocd=y

сама сборка:

./scripts/feeds update -a
./scripts/feeds install -a
make V=s 

Прошивка:

mtd -q write openwrt-brcm63xx-generic-F@ST2704V2-squashfs-cfe.bin linux

Для теста sysfsgpio составляем конфиг:

root@OpenWrt:~# cat sysfs.cfg.2.11

interface sysfsgpio
transport select swd

sysfsgpio_swclk_num 482
sysfsgpio_swdio_num 491

source [find target/stm32f1x.cfg]

Подсоединяем как на фото:



Запускаем:

root@OpenWrt:~# openocd -f sysfs.cfg.2.11
Open On-Chip Debugger 0.10.0+dev-00085-gfced6ac6-dirty (2017-03-xx-21:49)
Licensed under GNU GPL v2
For bug reports, read
    http://openocd.org/doc/doxygen/bugs.html
SysfsGPIO num: swclk = 482
SysfsGPIO num: swdio = 491
SysfsGPIO num: trst = 481
adapter speed: 1000 kHz
adapter_nsrst_delay: 100
none separate
cortex_m reset_config sysresetreq
Info : SysfsGPIO JTAG/SWD bitbang driver
Info : SWD only mode enabled (specify tck, tms, tdi and tdo gpios to add JTAG mode)
Warn : gpio 482 is already exported
Warn : gpio 491 is already exported
Info : This adapter doesn't support configurable speed
Info : SWD DPIDR 0x1ba01477
Info : stm32f1x.cpu: hardware has 6 breakpoints, 4 watchpoints

Запускаем дебаг в IDE, все работает.



Только очень медленно.

Пробуем оценить количественно скорость, заходим по телнет на роутер:

telnet 10.65.9.239 4444

Делаем дамп памяти.

> dump_image dump.bin 0x08000000 0x1ffff
dumped 131071 bytes in 55.013523s (2.327 KiB/s)

Мда, к примеру st-linkv2 у меня на хосте выдает скорость порядка 45 KiB/s. 20 раз разница!



Дело, конечно же, из-за медленной работы с файлами в /sys/class/gpio. Ковыряемся дальше в openocd. Находим интерфейсный драйвер для RaspberryPi (src/jtag/drivers/bcm2835gpio.c). Судя по тестам [5], скорость у него должна быть как примерно у st-link. Это достигнуто, во многом, благодаря прямому обращению к регистрам GPIO. Сделаем тоже самое и для нашего SoC, а так же это будет справедливо для всего семейства чипов bcm63xx.

получился вот такой интерфейс
/**
 * @file
 * This driver implements a bitbang jtag interface using gpio lines via
 * router ob BCM63XX SoC.
 * The aim of this driver implementation is use system GPIOs but avoid the
 * need for a additional kernel driver.
 * (Note memory mapped IO is another option, however it doesn't mix well with
 * the kernel gpiolib driver - which makes sense I guess.)
 *
 * A gpio is required for tck, tms, tdi and tdo. One or both of srst and trst
 * must be also be specified. The required jtag gpios are specified via the
 * bcm63xx_gpio_jtag_nums command or the relevant bcm63xx_gpio_XXX_num commang.
 * The srst and trst gpios are set via the bcm63xx_gpio_srst_num and
 * bcm63xx_gpio_trst_num respectively. GPIO numbering follows the kernel
 * convention of starting from 0.
 *
 * The gpios should not be in use by another entity, and must not be requested
 * by a kernel driver without also being exported by it (otherwise they can't
 * be exported by bcm63xx_).
 *
 * The bcm63xx gpio interface can only manipulate one gpio at a time, so the
 * bitbang write handler remembers the last state for tck, tms, tdi to avoid
 * superfluous writes.
 * For speed the bcm63xx "value" entry is opened at init and held open.
 * This results in considerable gains over open-write-close (45s vs 900s)
 *
 * Further work could address:
 *  -srst and trst open drain/ push pull
 *  -configurable active high/low for srst & trst
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <jtag/interface.h>
#include "bitbang.h"

#include <sys/mman.h>

/*
 * Helper func to determine if gpio number valid
 *
 * Assume here that there will be less than 1000 gpios on a system
 */
static int is_gpio_valid(int gpio)
{
	return gpio >= 0 && gpio < 32;
}

off_t address_dir = NULL;
off_t address_val = NULL;

static int dev_mem_fd = -1;
static volatile uint32_t *pio_base = NULL;
static volatile uint32_t *pval_base = NULL;
static volatile uint32_t *pads_base = NULL;
static unsigned int jtag_delay = 0;


static void set_dir_gpio(const int gpio, const int direction)
{
	if(direction)
		*pio_base |= 1 << gpio;
	else
		*pio_base &= ~(1 << gpio);
}


static void set_value_gpio(const int gpio, const int value)
{
	if(value)
		*pval_base |= 1 << gpio;
	else
		*pval_base &= ~(1 << gpio);

	for (unsigned int i = 0; i < jtag_delay; i++)
		asm volatile ("");
}

static int read_gpio(const int gpio)
{
	uint32_t val = *pval_base & (1 << gpio);
	val = val ? 1 : 0;
	return val;
}

static int setup_bcm63xx_gpio(int gpio, int is_output, int init_high)
{
	char buf[40];
	char gpiostr[4];
	int ret;

	if (!is_gpio_valid(gpio))
		return ERROR_OK;

	if((address_dir == NULL) || (address_val == NULL)){
		perror("address of gpio register don't set");
		return ERROR_FAIL; 
	}


	if( dev_mem_fd < 0 )
	{

		dev_mem_fd = open("/dev/mem", O_RDWR | O_SYNC);
		if (dev_mem_fd < 0) {
			perror("open");
			return ERROR_FAIL;
		}

		const uint32_t mapped_size = getpagesize();

		const off_t target_mmap = address_dir & ~(off_t)(mapped_size - 1);

		pads_base = mmap(NULL, mapped_size, PROT_READ | PROT_WRITE,
					MAP_SHARED, dev_mem_fd, target_mmap);

		if (pads_base == MAP_FAILED) {
			perror("mmap. Check correct register address.");
			close(dev_mem_fd);
			return ERROR_FAIL;
		}

		pio_base 	= (char*)pads_base + (unsigned)(address_dir -  target_mmap);
		pval_base 	= (char*)pads_base + (unsigned)(address_val - target_mmap);
	}

	set_dir_gpio(gpio, is_output);
	set_value_gpio(gpio, init_high);

	return 0;
}

/* gpio numbers for each gpio. Negative values are invalid */
static int tck_gpio = -1;
static int tms_gpio = -1;
static int tdi_gpio = -1;
static int tdo_gpio = -1;
static int trst_gpio = -1;
static int srst_gpio = -1;
static int swclk_gpio = -1;
static int swdio_gpio = -1;

/*
 * file descriptors for /sys/class/gpio/gpioXX/value
 * Set up during init.
 */
static int tck_fd = -1;
static int tms_fd = -1;
static int tdi_fd = -1;
static int tdo_fd = -1;
static int trst_fd = -1;
static int srst_fd = -1;
static int swclk_fd = -1;
static int swdio_fd = -1;

static int last_swclk;
static int last_swdio;
static bool last_stored;
static bool swdio_input;

static void bcm63xx_gpio_swdio_drive(bool is_output)
{
	set_dir_gpio(swdio_gpio, is_output ? 1 : 0);

	last_stored = false;
	swdio_input = !is_output;
}

static int bcm63xx_gpio_swdio_read(void)
{
	return read_gpio(swdio_gpio);
}

static void bcm63xx_gpio_swdio_write(int swclk, int swdio)
{

	const char one[] = "1";
	const char zero[] = "0";

	size_t bytes_written;

	if (!swdio_input) {
		if (!last_stored || (swdio != last_swdio)) {
			set_value_gpio(swdio_gpio, swdio ? 1 : 0);
		}

	}

	/* write swclk last */
	if (!last_stored || (swclk != last_swclk)) {
			set_value_gpio(swclk_gpio, swclk ? 1 : 0);
	}

	last_swdio = swdio;
	last_swclk = swclk;
	last_stored = true;
}

/*
 * Bitbang interface read of TDO
 *
 * The bcm63xx value will read back either '0' or '1'. The trick here is to call
 * lseek to bypass buffering in the bcm63xx kernel driver.
 */
static int bcm63xx_gpio_read(void)
{
	return read_gpio(tdo_gpio);
}

/*
 * Bitbang interface write of TCK, TMS, TDI
 *
 * Seeing as this is the only function where the outputs are changed,
 * we can cache the old value to avoid needlessly writing it.
 */
static void bcm63xx_gpio_write(int tck, int tms, int tdi)
{
	if (swd_mode) {
		bcm63xx_gpio_swdio_write(tck, tdi);
		return;
	}

	const char one[] = "1";
	const char zero[] = "0";

	static int last_tck;
	static int last_tms;
	static int last_tdi;

	static int first_time;
	size_t bytes_written;

	if (!first_time) {
		last_tck = !tck;
		last_tms = !tms;
		last_tdi = !tdi;
		first_time = 1;
	}

	if (tdi != last_tdi) {
		set_value_gpio(tdi_gpio,tdi);
	}

	if (tms != last_tms) {
		set_value_gpio(tms_gpio,tms);
	}

	/* write clk last */
	if (tck != last_tck) {
		set_value_gpio(tck_gpio,tck);

	}

	last_tdi = tdi;
	last_tms = tms;
	last_tck = tck;
}

/*
 * Bitbang interface to manipulate reset lines SRST and TRST
 *
 * (1) assert or (0) deassert reset lines
 */
static void bcm63xx_gpio_reset(int trst, int srst)
{
	LOG_DEBUG("bcm63xx_gpio_reset");
	const char one[] = "1";
	const char zero[] = "0";
	size_t bytes_written;

	/* assume active low */
	if (srst_fd >= 0) {
		set_value_gpio(srst_gpio,srst);
	}

	/* assume active low */
	if (trst_fd >= 0) {
		set_value_gpio(trst_gpio,trst);
	}
}

COMMAND_HANDLER(bcm63xx_gpio_handle_jtag_gpionums)
{
	if (CMD_ARGC == 4) {
		COMMAND_PARSE_NUMBER(int, CMD_ARGV[0], tck_gpio);
		COMMAND_PARSE_NUMBER(int, CMD_ARGV[1], tms_gpio);
		COMMAND_PARSE_NUMBER(int, CMD_ARGV[2], tdi_gpio);
		COMMAND_PARSE_NUMBER(int, CMD_ARGV[3], tdo_gpio);
	} else if (CMD_ARGC != 0) {
		return ERROR_COMMAND_SYNTAX_ERROR;
	}

	command_print(CMD_CTX,
			"bcm63xx_GPIO nums: tck = %d, tms = %d, tdi = %d, tdo = %d",
			tck_gpio, tms_gpio, tdi_gpio, tdo_gpio);

	return ERROR_OK;
}

COMMAND_HANDLER(bcm63xx_gpio_handle_jtag_gpionum_tck)
{
	if (CMD_ARGC == 1)
		COMMAND_PARSE_NUMBER(int, CMD_ARGV[0], tck_gpio);

	command_print(CMD_CTX, "bcm63xx_GPIO num: tck = %d", tck_gpio);
	return ERROR_OK;
}

COMMAND_HANDLER(bcm63xx_gpio_handle_jtag_gpionum_tms)
{
	if (CMD_ARGC == 1)
		COMMAND_PARSE_NUMBER(int, CMD_ARGV[0], tms_gpio);

	command_print(CMD_CTX, "bcm63xx_GPIO num: tms = %d", tms_gpio);
	return ERROR_OK;
}

COMMAND_HANDLER(bcm63xx_gpio_handle_jtag_gpionum_tdo)
{
	if (CMD_ARGC == 1)
		COMMAND_PARSE_NUMBER(int, CMD_ARGV[0], tdo_gpio);

	command_print(CMD_CTX, "bcm63xx_GPIO num: tdo = %d", tdo_gpio);
	return ERROR_OK;
}

COMMAND_HANDLER(bcm63xx_gpio_handle_jtag_gpionum_tdi)
{
	if (CMD_ARGC == 1)
		COMMAND_PARSE_NUMBER(int, CMD_ARGV[0], tdi_gpio);

	command_print(CMD_CTX, "bcm63xx_GPIO num: tdi = %d", tdi_gpio);
	return ERROR_OK;
}

COMMAND_HANDLER(bcm63xx_gpio_handle_jtag_gpionum_srst)
{
	if (CMD_ARGC == 1)
		COMMAND_PARSE_NUMBER(int, CMD_ARGV[0], srst_gpio);

	command_print(CMD_CTX, "bcm63xx_GPIO num: srst = %d", srst_gpio);
	return ERROR_OK;
}

COMMAND_HANDLER(bcm63xx_gpio_handle_jtag_gpionum_trst)
{
	if (CMD_ARGC == 1)
		COMMAND_PARSE_NUMBER(int, CMD_ARGV[0], trst_gpio);

	command_print(CMD_CTX, "bcm63xx_GPIO num: trst = %d", trst_gpio);
	return ERROR_OK;
}

COMMAND_HANDLER(bcm63xx_gpio_handle_swd_gpionums)
{
	if (CMD_ARGC == 2) {
		COMMAND_PARSE_NUMBER(int, CMD_ARGV[0], swclk_gpio);
		COMMAND_PARSE_NUMBER(int, CMD_ARGV[1], swdio_gpio);
	} else if (CMD_ARGC != 0) {
		return ERROR_COMMAND_SYNTAX_ERROR;
	}

	command_print(CMD_CTX,
			"bcm63xx_GPIO nums: swclk = %d, swdio = %d",
			swclk_gpio, swdio_gpio);

	return ERROR_OK;
}

COMMAND_HANDLER(bcm63xx_gpio_handle_swd_gpionum_swclk)
{
	if (CMD_ARGC == 1)
		COMMAND_PARSE_NUMBER(int, CMD_ARGV[0], swclk_gpio);

	command_print(CMD_CTX, "bcm63xx_GPIO num: swclk = %d", swclk_gpio);
	return ERROR_OK;
}

COMMAND_HANDLER(bcm63xx_gpio_handle_swd_gpionum_swdio)
{
	if (CMD_ARGC == 1)
		COMMAND_PARSE_NUMBER(int, CMD_ARGV[0], swdio_gpio);

	command_print(CMD_CTX, "bcm63xx_GPIO num: swdio = %d", swdio_gpio);
	return ERROR_OK;
}

COMMAND_HANDLER(bcm63xx_gpio_jtag_delay)
{
	if (CMD_ARGC == 1)
		COMMAND_PARSE_NUMBER(int, CMD_ARGV[0], jtag_delay);

	command_print(CMD_CTX, "bcm63xx_GPIO jtag_delay:= %d tics", jtag_delay);
	return ERROR_OK;
}

COMMAND_HANDLER(bcm63xx_gpio_adresses)
{
	if (CMD_ARGC == 2) {
		COMMAND_PARSE_NUMBER(u32, CMD_ARGV[0], address_dir);
		COMMAND_PARSE_NUMBER(u32, CMD_ARGV[1], address_val);
	} else if (CMD_ARGC != 0) {
		return ERROR_COMMAND_SYNTAX_ERROR;
	}

	command_print(CMD_CTX,
			"bcm63xx_GPIO address: direction = %x, value = %x",
			address_dir, address_val);

	return ERROR_OK;
}


static const struct command_registration bcm63xx_gpio_command_handlers[] = {
	{
		.name = "bcm63xx_gpio_jtag_nums",
		.handler = &bcm63xx_gpio_handle_jtag_gpionums,
		.mode = COMMAND_CONFIG,
		.help = "gpio numbers for tck, tms, tdi, tdo. (in that order)",
		.usage = "(tck tms tdi tdo)* ",
	},
	{
		.name = "bcm63xx_gpio_tck_num",
		.handler = &bcm63xx_gpio_handle_jtag_gpionum_tck,
		.mode = COMMAND_CONFIG,
		.help = "gpio number for tck.",
	},
	{
		.name = "bcm63xx_gpio_tms_num",
		.handler = &bcm63xx_gpio_handle_jtag_gpionum_tms,
		.mode = COMMAND_CONFIG,
		.help = "gpio number for tms.",
	},
	{
		.name = "bcm63xx_gpio_tdo_num",
		.handler = &bcm63xx_gpio_handle_jtag_gpionum_tdo,
		.mode = COMMAND_CONFIG,
		.help = "gpio number for tdo.",
	},
	{
		.name = "bcm63xx_gpio_tdi_num",
		.handler = &bcm63xx_gpio_handle_jtag_gpionum_tdi,
		.mode = COMMAND_CONFIG,
		.help = "gpio number for tdi.",
	},
	{
		.name = "bcm63xx_gpio_srst_num",
		.handler = &bcm63xx_gpio_handle_jtag_gpionum_srst,
		.mode = COMMAND_CONFIG,
		.help = "gpio number for srst.",
	},
	{
		.name = "bcm63xx_gpio_trst_num",
		.handler = &bcm63xx_gpio_handle_jtag_gpionum_trst,
		.mode = COMMAND_CONFIG,
		.help = "gpio number for trst.",
	},
	{
		.name = "bcm63xx_gpio_swd_nums",
		.handler = &bcm63xx_gpio_handle_swd_gpionums,
		.mode = COMMAND_CONFIG,
		.help = "gpio numbers for swclk, swdio. (in that order)",
		.usage = "(swclk swdio)* ",
	},
	{
		.name = "bcm63xx_gpio_swclk_num",
		.handler = &bcm63xx_gpio_handle_swd_gpionum_swclk,
		.mode = COMMAND_CONFIG,
		.help = "gpio number for swclk.",
	},
	{
		.name = "bcm63xx_gpio_swdio_num",
		.handler = &bcm63xx_gpio_handle_swd_gpionum_swdio,
		.mode = COMMAND_CONFIG,
		.help = "gpio number for swdio.",
	},
	{
		.name = "bcm63xx_gpio_jtag_delay",
		.handler = &bcm63xx_gpio_jtag_delay,
		.mode = COMMAND_CONFIG,
		.help = "qty tics gpio delay.",
	},
	{
		.name = "bcm63xx_gpio_adresses",
		.handler = &bcm63xx_gpio_adresses,
		.mode = COMMAND_CONFIG,
		.help = "addresses for direction and value setup. (in that order)",
		.usage = "(address_dir address_val)* ",
	},
	COMMAND_REGISTRATION_DONE
};

static int bcm63xx_gpio_init(void);
static int bcm63xx_gpio_quit(void);

static const char * const bcm63xx_gpio_transports[] = { "jtag", "swd", NULL };

struct jtag_interface bcm63xxgpio_interface = {
	.name = "bcm63xxgpio",
	.supported = DEBUG_CAP_TMS_SEQ,
	.execute_queue = bitbang_execute_queue,
	.transports = bcm63xx_gpio_transports,
	.swd = &bitbang_swd,
	.commands = bcm63xx_gpio_command_handlers,
	.init = bcm63xx_gpio_init,
	.quit = bcm63xx_gpio_quit,
};

static struct bitbang_interface bcm63xx_gpio_bitbang = {
	.read = bcm63xx_gpio_read,
	.write = bcm63xx_gpio_write,
	.reset = bcm63xx_gpio_reset,
	.swdio_read = bcm63xx_gpio_swdio_read,
	.swdio_drive = bcm63xx_gpio_swdio_drive,
	.blink = 0
};

static void unusing_all_gpio(void)
{
	munmap(pads_base, sysconf(_SC_PAGE_SIZE));
	close(dev_mem_fd);
	LOG_INFO("unusing_all_gpio\n");
}

static bool bcm63xx_gpio_jtag_mode_possible(void)
{
	if (!is_gpio_valid(tck_gpio))
		return 0;
	if (!is_gpio_valid(tms_gpio))
		return 0;
	if (!is_gpio_valid(tdi_gpio))
		return 0;
	if (!is_gpio_valid(tdo_gpio))
		return 0;
	return 1;
}

static bool bcm63xx_gpio_swd_mode_possible(void)
{
	if (!is_gpio_valid(swclk_gpio))
		return 0;
	if (!is_gpio_valid(swdio_gpio))
		return 0;
	return 1;
}

static int bcm63xx_gpio_init(void)
{
	bitbang_interface = &bcm63xx_gpio_bitbang;

	LOG_INFO("bcm63xx_gpio JTAG/SWD bitbang driver");

	if (bcm63xx_gpio_jtag_mode_possible()) {
		if (bcm63xx_gpio_swd_mode_possible())
			LOG_INFO("JTAG and SWD modes enabled");
		else
			LOG_INFO("JTAG only mode enabled (specify swclk and swdio gpio to add SWD mode)");
		if (!is_gpio_valid(trst_gpio) && !is_gpio_valid(srst_gpio)) {
			LOG_ERROR("Require at least one of trst or srst gpios to be specified");
			return ERROR_JTAG_INIT_FAILED;
		}
	} else if (bcm63xx_gpio_swd_mode_possible()) {
		LOG_INFO("SWD only mode enabled (specify tck, tms, tdi and tdo gpios to add JTAG mode)");
	} else {
		LOG_ERROR("Require tck, tms, tdi and tdo gpios for JTAG mode and/or swclk and swdio gpio for SWD mode");
		return ERROR_JTAG_INIT_FAILED;
	}


	/*
	 * Configure TDO as an input, and TDI, TCK, TMS, TRST, SRST
	 * as outputs.  Drive TDI and TCK low, and TMS/TRST/SRST high.
	 * For SWD, SWCLK and SWDIO are configures as output high.
	 */
	if (tck_gpio >= 0) {
		tck_fd = setup_bcm63xx_gpio(tck_gpio, 1, 0);
		if (tck_fd < 0)
			goto out_error;
	}

	if (tms_gpio >= 0) {
		tms_fd = setup_bcm63xx_gpio(tms_gpio, 1, 1);
		if (tms_fd < 0)
			goto out_error;
	}

	if (tdi_gpio >= 0) {
		tdi_fd = setup_bcm63xx_gpio(tdi_gpio, 1, 0);
		if (tdi_fd < 0)
			goto out_error;
	}

	if (tdo_gpio >= 0) {
		tdo_fd = setup_bcm63xx_gpio(tdo_gpio, 0, 0);
		if (tdo_fd < 0)
			goto out_error;
	}

	/* assume active low*/
	if (trst_gpio >= 0) {
		trst_fd = setup_bcm63xx_gpio(trst_gpio, 1, 1);
		if (trst_fd < 0)
			goto out_error;
	}

	/* assume active low*/
	if (srst_gpio >= 0) {
		srst_fd = setup_bcm63xx_gpio(srst_gpio, 1, 1);
		if (srst_fd < 0)
			goto out_error;
	}

	if (swclk_gpio >= 0) {
		swclk_fd = setup_bcm63xx_gpio(swclk_gpio, 1, 0);
		if (swclk_fd < 0)
			goto out_error;
	}

	if (swdio_gpio >= 0) {
		swdio_fd = setup_bcm63xx_gpio(swdio_gpio, 1, 0);
		if (swdio_fd < 0)
			goto out_error;
	}

	if (bcm63xx_gpio_swd_mode_possible()) {
		if (swd_mode)
			bitbang_swd_switch_seq(JTAG_TO_SWD);
		else
			bitbang_swd_switch_seq(SWD_TO_JTAG);
	}

	return ERROR_OK;

out_error:
	unusing_all_gpio();
	return ERROR_JTAG_INIT_FAILED;
}

static int bcm63xx_gpio_quit(void)
{
	unusing_all_gpio();
	return ERROR_OK;
}


По сравнению с sysfsgpio, добавил пару опций:

  • bcm63xx_gpio_jtag_delay
  • bcm63xx_gpio_adresses

Первая настройка задает задержку между переключениями пинов, является косвенным аналогом bcm2835gpio_speed_coeffs для RaspberryPi драйвера, которая задает частоту работы jtag. К примеру, при нулевой задержке частота переключений была примерно один мегагерц, все работало вполне стабильно, но для надежности лучше иметь возможность задавать этот параметр.

А вторая опция — аналог bcm2835gpio_peripheral_base, только для нее требуется прописать два адреса для регистра, который задает функцию входа/выхода пинов, и регистра, который отвечает за входное/выходное логическое значение на gpio. В начале брал значения регистров из заголовочных файлов ядра. Но с этими значениями ничего не работало. Оказалось, что регистры периферии нельзя напрямую обращаться из userspace, т.е. должен быть сделан ремап еще в ядре. Хорошо, что за меня это уже осуществил gpio драйвер и необходимые значения можно взять из /proc/iomem.

Добавляем наш интерфейс в сборку openocd

Не забываем добавить --enable-bcm63xxgpio к CONFIGURE_ARGS в feeds/packages/utils/openocd/Makefile файле.

Пересобираем, устанавливаем и запускаем на роутере:

root@OpenWrt:~# openocd -f interface/bcm63xx-swd.cfg -f target/stm32f1x.cfg
Open On-Chip Debugger 0.10.0+dev-00085-gfced6ac6-dirty (2017-03-xx-21:49)
Licensed under GNU GPL v2
For bug reports, read
    http://openocd.org/doc/doxygen/bugs.html
bcm63xx_GPIO num: swclk = 2
bcm63xx_GPIO num: swdio = 11
bcm63xx_GPIO jtag_delay:= 10 tics
bcm63xx_GPIO address: direction = 10000084, value = 1000008c
adapter speed: 1000 kHz
adapter_nsrst_delay: 100
none separate
cortex_m reset_config sysresetreq
Info : bcm63xx_gpio JTAG/SWD bitbang driver
Info : SWD only mode enabled (specify tck, tms, tdi and tdo gpios to add JTAG mode)
Info : This adapter doesn't support configurable speed
Info : SWD DPIDR 0x1ba01477
Info : stm32f1x.cpu: hardware has 6 breakpoints, 4 watchpoints

Проверяем скорость дампа.

> dump_image dump.bin 0x08000000 0x1ffff
dumped 131071 bytes in 4.729815s (27.062 KiB/s)

Очень даже хорошо, проигрываем в два раза где-то st-link и малинке, но на глаз разница не заметна. Фризов при отладке нет, ну и подождать лишние пару секунд при прошивке — «понты».

Все тесты проводились на микроконтроллере STM32F103C8T6 и только на интерфейсе SWD, на отлаживаемой плате, к сожалению, отсутствовал jtag. Соответственно поэтому гарантировать полноценную работу на jtag не могу. К тому же надо не забывать про согласование уровней сигналов (в частности для MK AVR).

Сам роутер был взят из кучки хлама, среди которого было полно Sagem F@st 2704V2 и V7. К сожалению все устройства были в неисправном состоянии. Но удалось без проблем восстановить плату (см [2]).

Если кто-то готов сделать из этого конструктора отладчик/программатор, то готов на безвозмездной основе поделиться своими запасами с общественностью со снятием с себя всей ответственности и средств на пересылку (из default-city). Для запроса использовать следующий код:

char * my_mail = { 0x20, 0x73, 0x65, 0x72, 0x2d, 0x6d, 0x6b, 0x40, 0x79, 0x61, 0x2e, 0x72, 0x75, 0x0a };

Обновленная прошивка здесь.
Предупреждаю, что изменены стандартные настройки сети и firewall.

На этом всё, удачной отладки!

Список полезных ресурсов


  1. wiki.openwrt.org/toh/sagem/fast2704
  2. radiohlam.ru/forum/viewtopic.php?f=54&t=3749
  3. openocd.org
  4. developer.mbed.org/handbook/CMSIS-DAP
  5. github.com/rogerclarkmelbourne/Arduino_STM32/wiki/Programming-an-STM32F103XXX-with-a-generic-ST-Link-V2-programmer-from-Linux
Поделиться с друзьями
-->

Комментарии (22)


  1. brainsucker
    13.03.2017 23:36
    +3

    Вау. Вряд ли будет нужда (стлинка или перешитых стмок выше крыши, хотя изоляция, хм) и дойдут руки повторить (тем более под руками все на квалкомах, т.е. портить), но респект за саму идею :)

    Впрочем одни пикчи (я роутер а не...) и текст заслуживают + :)
    Спасибо.


    1. ser-mk
      13.03.2017 23:59
      +1

      Мир же не только на arm контроллерах держится, есть другие архитектуры, есть ПЛИС, есть микросхемы памяти. Все это надо программировать и отлаживать, а с помощью связки openocd + linux можно изготовить универсальный программатор\отладчик и бонусом заиметь еще пару плюшек)


  1. Alex_ME
    14.03.2017 01:45
    +2

    А какая у вас IDE на скрине?


    1. EXL
      14.03.2017 09:13
      +1

      Это Qt Creator с тёмной темой.


  1. Punk_Joker
    14.03.2017 02:42

    хорошая идея, у меня как раз валяется роутер подходящий для openwrt, надо будет попробовать.


  1. Rast1234
    14.03.2017 04:16
    +2

    новая жизнь старых медленных (802.11b/g) роутеров %)


  1. mwaso
    14.03.2017 05:33
    +4

    Такое надо на Хабр в песочницу постить…
    Было бы интересно почитать о настройке IDE и прочих программных средств для удаленной отладки по JTAG.


    1. ser-mk
      14.03.2017 12:13

      вотхорошее руководство . Единственно там, на мой взгляд, используется непривычная система сборки qbs.
      К сожалению для qmake не смог найти примера, а самомоу составлять не было мотивации.


      1. mwaso
        14.03.2017 19:01

        Вы неправильно меня поняли, или глаза меня подводят, но я в статье по ссылке не увидел ничего про удаленную отладку. Интересна именно ситуация, описанная вами в первом предложении статьи. Подробная история о том, как кто-то заботливо прицепил к целевому контроллеру недорогую железку, позволяющую пробросить настоящий JTAG или SWD через интернет и дебажить контроллер из другой точки планеты.


        1. ser-mk
          14.03.2017 23:45
          +1

          Сейчас через vpn зашел на устройство, зупустил openocd и подцепился gdb клиентом. Вся удаленная отладка сводится к поднятию VPN или GRE тунеля.


  1. REPISOT
    14.03.2017 09:44

    Только это SWD а не JTAG.


    1. ser-mk
      14.03.2017 12:18

      поддержка JTAG тоже есть, в коде она реализована. Не было подопытного, что бы продемонстрировать работу.


      1. REPISOT
        15.03.2017 06:34
        +1

        может, и есть. Только после одного упоминания во вступлении больше нигде о нем ничего нет.


  1. sirocco
    14.03.2017 10:42
    +1

    Есть ещё virtualhere, http://www.virtualhere.com/, пробрасывает USB. На TP-Link MR3020 работает просто замечательно. Есть под очень многие платформы, в том числе и под openWRT. Удобный клиент. Один минус — денег стоит. Вот бы что-то подобное, но бесплатно.


  1. geekmetwice
    16.03.2017 13:22
    -5

    Сейчас для топикстартера и остальных гордых собой гениев переведу, как видят эту статью люди со стороны или начинающие:
    «Есть такая штуковина АБВ, у неё тут ГДЕЖ и можно запросто использовать ИКЛМ! Сейчас я туда засуну ЙЦУК и получится ЁПРСТ!». И ты сидишь такой, думаешь: ведь почти все слова знакомые, а смысла — ноль!
    И наоборот: сидит специалист, смотрит на текст и хмыкает: «ичо? подумаешь, поковырял левым мизинцем в правой ноздре!».

    Господа, выражать своё тщеславие на публичных серверах — полезный грех, но делать это надо ПОНЯТНЫМ ЯЗЫКОМ. И в каждом слове постоянно думать о ЦА! (вот сам лезь и переводи, что это означает!)
    Для профи ваша статья — пшик, они это делают уже лет 10. Начинающий не может понять, чего вы добились (и добились ли вообще — может тупо прочли инструкцию к рутеру). Вы как-то плавно объясняйте, ЛУЧШЕ СО СХЕМАМИ, что вы достигли и какая от этого польза читающим.
    Я сейчас немного практикую программировать вот эти все малины-ардуины, но в упор не понимаю, как рутер может быть «JTAG». Он может быть ADSL, Wi-Fi. А «пробросить USB через сеть» — это как и зачем? Что, можно флэшку воткнуть в сетевой разъём? Или наоборот — сетевой кабель воткнуть в USB? А что делать тем, у кого нет Sagem? Всё, волшебство не работает? Или работает, но вы забыли сказать главное? Вот-вот, бросил статью в вечность, а вы там разбирайтесь!
    Статья откровенно технарская в самом отрицательном смысле — узкоспециализированная свалка терминов для одной задачи, да ещё на каком-то странноватом оборудовании. Я бы на вашем месте переписал её для людей — чувствую, что там есть полезное, но где оно — я не вижу.


    1. Punk_Joker
      16.03.2017 13:38
      +2

      На Geektimes полно статей, которые не поймет основная масса или группа идей.Если вы этого не понимаете, то возможно оно вам и не надо? В данной статье все вполне нормально описано.
      >немного практикую программировать вот эти все малины-ардуины
      Возможно поэтому вы и не понимаете
      Я не могу назвать себе опытным программистом, и подобное я бы не сделал, точнее даже не догадался бы сделать. Но тем не менее, статья лично для меня понятна.


      1. geekmetwice
        16.03.2017 14:18
        -6

        Весь прикол в том и состоит, что в статье не описано НИЧЕГО сверхъестественного. Для профи. Спрошу ещё раз, раз не понимаете смысла моего коммента: ДЛЯ КОГО эта статья? Полуначинающих? Недопрофессионалов? Домашних Кулибиных-самоучек? Абзацы, где идёт самая суть, описаны на птичьем языке. «Я взял АБВ и залил в ГДЕ». Люди может и знают, что такое АБВ, но не могут понять его связи с остальным алфавитом. Вот я и попросил писать статью чуть более развёрнуто — гиктаймс — не помойка для тщеславных подростковых испражнений, это технический ОБУЧАЮЩИЙ ресурс — люди находят здесь интересные материалы и пытаются поднять свой уровень. Из данной статьи почти ничего непонятно — для чего тогда её писали?


    1. ser-mk
      16.03.2017 13:43
      +3

      Привет, увы у меня для вас плохая новость
      Если вам не знакомы с аббревиатурами SWD и JTAG, то как не расписывай статью то полезнее она не станет
      Какие схемы вам нужны? как подключить 4 провода? Если вы были бы уже знакомы с вышеупомянутыми аббревиатурами, то вопросов не возникало.
      нет Sagem — выдам… (вы точно до конца дочитали?)
      Ну и борцам за «рутер», хотелось бы видеть официальный документ, в котором подтверждалось правописание слова. Это скорее всего должен быть словарь, официально одобренный гос структурой. Без этого будет просто пустой холивар.


      1. geekmetwice
        16.03.2017 14:36
        -8

        Привет, увы у меня для вас плохая новость


        Извините, вы бы как-то научились разговаривать на правильном языке? Либо «Здравствуйте» и далее обращение на «вы», либо дворовое «привет» и на «ты». Хотите — перейдём на «ты», т.к. видно, что культурой тут не обременены.

        Если вам не знакомы с аббревиатурами SWD и JTAG, то как не расписывай статью то полезнее она не станет


        Да-да. Сказал человек, который в статье вообще JTAG не использовал! К чему эти понты, милейший? Вы серьёзно думаете, что вас на всю Россию десять человек, знающих JTAG? :)) Да вообще не в нём суть, если так подумать — весь смысл в словах «удалённая отладка», но который так и остался где-то в дебрях вашего сознания — не умеете вы доносить смысл, только жонглирование терминами.

        Какие схемы вам нужны? как подключить 4 провода?


        Вот видите, к чему приводит узкое мировоззрение — для вас «схема» — это провода. А есть схема метро, представляете? :) А есть схема, где можно было бы понятно обрисовать — где у вас рутер, где микросхема и почему всё это называется «удалённым», да ещё с бросанием USB.

        Про рутер не надо умничать, молодой человек! Вы сами знаете, что обделались по полной — не бывает JTAG рутеров. JTAG — тестовый интерфейс, никакого отношения к маршрутизации пакетов он отношения не имеет. Поэтому в заголовке слову JTAG вообще не место, если уж быть точным.

        Это грустно… очень грустно, что вместо принятия критики вы огрызаетесь, будто действительно написали недооценённый шедевр! Всё уже сделано до вас, понимаете? Нет только грамотных статей, где одни НОРМАЛЬНЫЕ люди объясняли другим НОРМАЛЬНЫМ как достичь чего-то. Наверное ещё лет 10 и вы поймёте, почему грубость ничего не решает — на общем ресурсе надо созидать, а не понтоваться.


        1. Lordi
          17.03.2017 12:14
          +1

          Уважаемый, смиритесь, что кому-то дано, а кому-то не дано и проходите мимо. Авторы пишут всегда на своем уровне, если хотят написать что-то для совсем начинающих, то делают отметку «туториал». Возможно вам стоит посмотреть именно на такие статьи?
          Иначе если следовать вашей логике, то на том же хабре — все самовлюбленные скоты и надо его закрыть. Там ведь уровень статей и их изложение на порядок выше, чем на гиктаймсе. И все это не из желания «понтоваться», просто достаточно вспомнить историю — хабр для профессиональных разработчиков, гик часть их оттянул на себя, отсюда закономерность — самые интересные статьи пишут как раз не любители ардуины, а вполне себе инженеры чтобы поделиться с такими же инженерами.


    1. mark_ablov
      17.03.2017 13:27
      +3

      Автор написал практически готовый howto, зачем разжевывать каждый термин? Для этого есть другие статьи.


  1. poglazoff
    17.03.2017 09:53

    Статья классная, пишите ещё!
    Если вы так отлаживаете, то вам и про программирование МК наверняка есть что рассказать :-)