From: Marc Reilly Date: Sun, 19 Aug 2012 23:46:07 +0000 (+1000) Subject: drivers: new jtag bitbang driver using sysfs gpio X-Git-Tag: v0.7.0-rc1~239 X-Git-Url: https://review.openocd.org/gitweb?p=openocd.git;a=commitdiff_plain;h=fe52282c37cc0c4ab9863e95ef6970e6fad540b4;hp=a4830e7a6ad0c6bf954184894a0dbec74075306b drivers: new jtag bitbang driver using sysfs gpio This driver implements a bitbang jtag interface using gpio lines exported via sysfs. The aim of this driver implementation is to use system GPIOs but to avoid the need for an additional kernel driver. A config suitable for RaspberryPi is included. Change-Id: Ib2acf720247a219768d1cbfeebd88057ed2d7b8b Signed-off-by: Marc Reilly Reviewed-on: http://openocd.zylin.com/762 Reviewed-by: Spencer Oliver Tested-by: jenkins Reviewed-by: Freddie Chopin --- diff --git a/configure.ac b/configure.ac index 30da60e077..3665f03f30 100644 --- a/configure.ac +++ b/configure.ac @@ -487,6 +487,10 @@ AC_ARG_ENABLE([opendous], AS_HELP_STRING([--enable-opendous], [Enable building support for the estick/opendous JTAG Programmer]), [build_opendous=$enableval], [build_opendous=no]) +AC_ARG_ENABLE([sysfsgpio], + AS_HELP_STRING([--enable-sysfsgpio], [Enable building support for programming driven via sysfs gpios.]), + [build_sysfsgpio=$enableval], [build_sysfsgpio=no]) + AC_ARG_ENABLE([minidriver_dummy], AS_HELP_STRING([--enable-minidriver-dummy], [Enable the dummy minidriver.]), [build_minidriver_dummy=$enableval], [build_minidriver_dummy=no]) @@ -819,6 +823,12 @@ else AC_DEFINE([BUILD_REMOTE_BITBANG], [0], [0 if you don't want the Remote Bitbang JTAG driver.]) fi +if test $build_sysfsgpio = yes; then + build_bitbang=yes + AC_DEFINE([BUILD_SYSFSGPIO], [1], [1 if you want the SysfsGPIO driver.]) +else + AC_DEFINE([BUILD_SYSFSGPIO], [0], [0 if you don't want SysfsGPIO driver.]) +fi #-- Deal with MingW/Cygwin FTD2XX issues if test $is_win32 = yes; then @@ -1185,6 +1195,7 @@ AM_CONDITIONAL([BUSPIRATE], [test $build_buspirate = yes]) AM_CONDITIONAL([STLINK], [test $build_stlink = yes]) AM_CONDITIONAL([OSBDM], [test $build_osbdm = yes]) AM_CONDITIONAL([OPENDOUS], [test $build_opendous = yes]) +AM_CONDITIONAL([SYSFSGPIO], [test $build_sysfsgpio = yes]) AM_CONDITIONAL([USB], [test $build_usb = yes]) AM_CONDITIONAL([USB_NG], [test $build_usb_ng = yes]) AM_CONDITIONAL([USE_LIBUSB0], [test $use_libusb0 = yes]) diff --git a/src/jtag/drivers/Makefile.am b/src/jtag/drivers/Makefile.am index b02c353407..d137713145 100644 --- a/src/jtag/drivers/Makefile.am +++ b/src/jtag/drivers/Makefile.am @@ -107,6 +107,9 @@ endif if OPENDOUS DRIVERFILES += opendous.c endif +if SYSFSGPIO +DRIVERFILES += sysfsgpio.c +endif noinst_HEADERS = \ bitbang.h \ diff --git a/src/jtag/drivers/sysfsgpio.c b/src/jtag/drivers/sysfsgpio.c new file mode 100644 index 0000000000..05d9a9dc0a --- /dev/null +++ b/src/jtag/drivers/sysfsgpio.c @@ -0,0 +1,505 @@ +/*************************************************************************** + * Copyright (C) 2012 by Creative Product Design, marc @ cpdesign.com.au * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +/** + * @file + * This driver implements a bitbang jtag interface using gpio lines via + * sysfs. + * 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 + * sysfsgpio_jtag_nums command or the relevant sysfsgpio_XXX_num commang. + * The srst and trst gpios are set via the sysfsgpio_srst_num and + * sysfsgpio_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 sysfs). + * + * The sysfs 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 sysfs "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 +#include "bitbang.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 < 1000; +} + +/* + * Helper func to open, write to and close a file + * name and valstr must be null terminated. + * + * Returns negative on failure. + */ +static int open_write_close(const char *name, const char *valstr) +{ + int ret; + int fd = open(name, O_WRONLY); + if (fd < 0) + return fd; + + ret = write(fd, valstr, strlen(valstr)); + close(fd); + + return ret; +} + +/* + * Helper func to unexport gpio from sysfs + */ +static void unexport_sysfs_gpio(int gpio) +{ + char gpiostr[4]; + + if (!is_gpio_valid(gpio)) + return; + + snprintf(gpiostr, sizeof(gpiostr), "%d", gpio); + if (open_write_close("/sys/class/gpio/unexport", gpiostr) < 0) + LOG_ERROR("Couldn't unexport gpio %d", gpio); + + return; +} + +/* + * Exports and sets up direction for gpio. + * If the gpio is an output, it is initialized according to init_high, + * otherwise it is ignored. + * + * If the gpio is already exported we just show a warning and continue; if + * openocd happened to crash (or was killed by user) then the gpios will not + * have been cleaned up. + */ +static int setup_sysfs_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; + + snprintf(gpiostr, sizeof(gpiostr), "%d", gpio); + ret = open_write_close("/sys/class/gpio/export", gpiostr); + if (ret < 0) { + if (errno == EBUSY) { + LOG_WARNING("gpio %d is already exported", gpio); + } else { + LOG_ERROR("Couldn't export gpio %d", gpio); + perror("sysfsgpio: "); + return ERROR_FAIL; + } + } + + snprintf(buf, sizeof(buf), "/sys/class/gpio/gpio%d/direction", gpio); + ret = open_write_close(buf, is_output ? (init_high ? "high" : "low") : "in"); + if (ret < 0) { + LOG_ERROR("Couldn't set direction for gpio %d", gpio); + perror("sysfsgpio: "); + unexport_sysfs_gpio(gpio); + return ERROR_FAIL; + } + + snprintf(buf, sizeof(buf), "/sys/class/gpio/gpio%d/value", gpio); + if (is_output) + ret = open(buf, O_WRONLY | O_NONBLOCK | O_SYNC); + else + ret = open(buf, O_RDONLY | O_NONBLOCK | O_SYNC); + + if (ret < 0) + unexport_sysfs_gpio(gpio); + + return ret; +} + +/* + * 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; + +/* + * Bitbang interface read of TDO + * + * The sysfs value will read back either '0' or '1'. The trick here is to call + * lseek to bypass buffering in the sysfs kernel driver. + */ +static int sysfsgpio_read(void) +{ + char buf[1]; + + /* important to seek to signal sysfs of new read */ + lseek(tdo_fd, 0, SEEK_SET); + int ret = read(tdo_fd, &buf, sizeof(buf)); + + if (ret < 0) { + LOG_WARNING("reading tdo failed"); + return 0; + } + + return buf[0] == '1'; +} + +/* + * 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 sysfsgpio_write(int tck, int tms, int tdi) +{ + const char one[] = "1"; + const char zero[] = "0"; + + static int last_tck; + static int last_tms; + static int last_tdi; + + static int first_time; + + if (!first_time) { + last_tck = !tck; + last_tms = !tms; + last_tdi = !tdi; + first_time = 1; + } + + if (tdi != last_tdi) + write(tdi_fd, tdi ? &one : &zero, 1); + if (tms != last_tms) + write(tms_fd, tms ? &one : &zero, 1); + /* write clk last */ + if (tck != last_tck) + write(tck_fd, tck ? &one : &zero, 1); + + 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 sysfsgpio_reset(int trst, int srst) +{ + const char one[] = "1"; + const char zero[] = "0"; + + /* assume active low */ + if (srst_fd >= 0) + write(srst_fd, srst ? &zero : &one, 1); + + /* assume active low */ + if (trst_fd >= 0) + write(trst_fd, trst ? &zero : &one, 1); +} + +/* No speed control is implemented yet */ +static int sysfsgpio_speed(int speed) +{ + return ERROR_OK; +} + +static int sysfsgpio_khz(int khz, int *jtag_speed) +{ + /* no adaptive clocking */ + if (khz == 0) + return ERROR_FAIL; + + *jtag_speed = 0; + return ERROR_OK; +} + +static int sysfsgpio_speed_div(int speed, int *khz) +{ + *khz = 1; + return ERROR_OK; +} + +/* 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; + +COMMAND_HANDLER(sysfsgpio_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, + "SysfsGPIO nums: tck = %d, tms = %d, tdi = %d, tdi = %d", + tck_gpio, tms_gpio, tdi_gpio, tdo_gpio); + + return ERROR_OK; +} + +COMMAND_HANDLER(sysfsgpio_handle_jtag_gpionum_tck) +{ + if (CMD_ARGC == 1) + COMMAND_PARSE_NUMBER(int, CMD_ARGV[0], tck_gpio); + + command_print(CMD_CTX, "SysfsGPIO num: tck = %d", tck_gpio); + return ERROR_OK; +} + +COMMAND_HANDLER(sysfsgpio_handle_jtag_gpionum_tms) +{ + if (CMD_ARGC == 1) + COMMAND_PARSE_NUMBER(int, CMD_ARGV[0], tms_gpio); + + command_print(CMD_CTX, "SysfsGPIO num: tms = %d", tms_gpio); + return ERROR_OK; +} + +COMMAND_HANDLER(sysfsgpio_handle_jtag_gpionum_tdo) +{ + if (CMD_ARGC == 1) + COMMAND_PARSE_NUMBER(int, CMD_ARGV[0], tdo_gpio); + + command_print(CMD_CTX, "SysfsGPIO num: tdo = %d", tdo_gpio); + return ERROR_OK; +} + +COMMAND_HANDLER(sysfsgpio_handle_jtag_gpionum_tdi) +{ + if (CMD_ARGC == 1) + COMMAND_PARSE_NUMBER(int, CMD_ARGV[0], tdi_gpio); + + command_print(CMD_CTX, "SysfsGPIO num: tdi = %d", tdi_gpio); + return ERROR_OK; +} + +COMMAND_HANDLER(sysfsgpio_handle_jtag_gpionum_srst) +{ + if (CMD_ARGC == 1) + COMMAND_PARSE_NUMBER(int, CMD_ARGV[0], srst_gpio); + + command_print(CMD_CTX, "SysfsGPIO num: srst = %d", srst_gpio); + return ERROR_OK; +} + +COMMAND_HANDLER(sysfsgpio_handle_jtag_gpionum_trst) +{ + if (CMD_ARGC == 1) + COMMAND_PARSE_NUMBER(int, CMD_ARGV[0], trst_gpio); + + command_print(CMD_CTX, "SysfsGPIO num: trst = %d", trst_gpio); + return ERROR_OK; +} + +static const struct command_registration sysfsgpio_command_handlers[] = { + { + .name = "sysfsgpio_jtag_nums", + .handler = &sysfsgpio_handle_jtag_gpionums, + .mode = COMMAND_CONFIG, + .help = "gpio numbers for tck, tms, tdi, tdo. (in that order)", + .usage = "(tck tms tdi tdo)* ", + }, + { + .name = "sysfsgpio_tck_num", + .handler = &sysfsgpio_handle_jtag_gpionum_tck, + .mode = COMMAND_CONFIG, + .help = "gpio number for tck.", + }, + { + .name = "sysfsgpio_tms_num", + .handler = &sysfsgpio_handle_jtag_gpionum_tms, + .mode = COMMAND_CONFIG, + .help = "gpio number for tms.", + }, + { + .name = "sysfsgpio_tdo_num", + .handler = &sysfsgpio_handle_jtag_gpionum_tdo, + .mode = COMMAND_CONFIG, + .help = "gpio number for tdo.", + }, + { + .name = "sysfsgpio_tdi_num", + .handler = &sysfsgpio_handle_jtag_gpionum_tdi, + .mode = COMMAND_CONFIG, + .help = "gpio number for tdi.", + }, + { + .name = "sysfsgpio_srst_num", + .handler = &sysfsgpio_handle_jtag_gpionum_srst, + .mode = COMMAND_CONFIG, + .help = "gpio number for srst.", + }, + { + .name = "sysfsgpio_trst_num", + .handler = &sysfsgpio_handle_jtag_gpionum_trst, + .mode = COMMAND_CONFIG, + .help = "gpio number for trst.", + }, + COMMAND_REGISTRATION_DONE +}; + +static int sysfsgpio_init(void); +static int sysfsgpio_quit(void); + +struct jtag_interface sysfsgpio_interface = { + .name = "sysfsgpio", + .supported = DEBUG_CAP_TMS_SEQ, + .execute_queue = bitbang_execute_queue, + .transports = jtag_only, + .speed = sysfsgpio_speed, + .khz = sysfsgpio_khz, + .speed_div = sysfsgpio_speed_div, + .commands = sysfsgpio_command_handlers, + .init = sysfsgpio_init, + .quit = sysfsgpio_quit, +}; + +static struct bitbang_interface sysfsgpio_bitbang = { + .read = sysfsgpio_read, + .write = sysfsgpio_write, + .reset = sysfsgpio_reset, + .blink = 0 +}; + +/* helper func to close and cleanup files only if they were valid/ used */ +static void cleanup_fd(int fd, int gpio) +{ + if (gpio >= 0) { + if (fd >= 0) + close(fd); + + unexport_sysfs_gpio(gpio); + } +} + +static void cleanup_all_fds(void) +{ + cleanup_fd(tck_fd, tck_gpio); + cleanup_fd(tms_fd, tms_gpio); + cleanup_fd(tdi_fd, tdi_gpio); + cleanup_fd(tdo_fd, tdo_gpio); + cleanup_fd(trst_fd, trst_gpio); + cleanup_fd(srst_fd, srst_gpio); +} + +static int sysfsgpio_init(void) +{ + bitbang_interface = &sysfsgpio_bitbang; + + LOG_INFO("SysfsGPIO JTAG bitbang driver"); + + if (!(is_gpio_valid(tck_gpio) + && is_gpio_valid(tms_gpio) + && is_gpio_valid(tdi_gpio) + && is_gpio_valid(tdo_gpio))) { + if (!is_gpio_valid(tck_gpio)) + LOG_ERROR("gpio num for tck is invalid"); + if (!is_gpio_valid(tms_gpio)) + LOG_ERROR("gpio num for tms is invalid"); + if (!is_gpio_valid(tdo_gpio)) + LOG_ERROR("gpio num for tdo is invalid"); + if (!is_gpio_valid(tdi_gpio)) + LOG_ERROR("gpio num for tdi is invalid"); + + LOG_ERROR("Require tck, tms, tdi and tdo gpios to all be specified"); + return ERROR_JTAG_INIT_FAILED; + } + + 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; + } + + /* + * Configure TDO as an input, and TDI, TCK, TMS, TRST, SRST + * as outputs. Drive TDI and TCK low, and TMS/TRST/SRST high. + */ + tck_fd = setup_sysfs_gpio(tck_gpio, 1, 0); + if (tck_fd < 0) + goto out_error; + + tms_fd = setup_sysfs_gpio(tms_gpio, 1, 1); + if (tms_fd < 0) + goto out_error; + + tdi_fd = setup_sysfs_gpio(tdi_gpio, 1, 0); + if (tdi_fd < 0) + goto out_error; + + tdo_fd = setup_sysfs_gpio(tdo_gpio, 0, 0); + if (tdo_fd < 0) + goto out_error; + + /* assume active low*/ + trst_fd = setup_sysfs_gpio(trst_gpio, 1, 1); + if (trst_gpio > 0 && trst_fd < 0) + goto out_error; + + /* assume active low*/ + srst_fd = setup_sysfs_gpio(srst_gpio, 1, 1); + if (srst_gpio > 0 && srst_fd < 0) + goto out_error; + + return ERROR_OK; + +out_error: + cleanup_all_fds(); + return ERROR_JTAG_INIT_FAILED; +} + +static int sysfsgpio_quit(void) +{ + cleanup_all_fds(); + return ERROR_OK; +} + diff --git a/src/jtag/interfaces.c b/src/jtag/interfaces.c index 4b054bef4e..19d2fccca6 100644 --- a/src/jtag/interfaces.c +++ b/src/jtag/interfaces.c @@ -113,6 +113,9 @@ extern struct jtag_interface osbdm_interface; #if BUILD_OPENDOUS == 1 extern struct jtag_interface opendous_interface; #endif +#if BUILD_SYSFSGPIO == 1 +extern struct jtag_interface sysfsgpio_interface; +#endif #endif /* standard drivers */ /** @@ -194,6 +197,9 @@ struct jtag_interface *jtag_interfaces[] = { #if BUILD_OPENDOUS == 1 &opendous_interface, #endif +#if BUILD_SYSFSGPIO == 1 + &sysfsgpio_interface, +#endif #endif /* standard drivers */ NULL, }; diff --git a/tcl/interface/sysfsgpio-raspberrypi.cfg b/tcl/interface/sysfsgpio-raspberrypi.cfg new file mode 100644 index 0000000000..105173c94a --- /dev/null +++ b/tcl/interface/sysfsgpio-raspberrypi.cfg @@ -0,0 +1,24 @@ +# +# Config for using RaspberryPi's expansion header +# +# This is best used with a fast enough buffer but also +# is suitable for direct connection if the target voltage +# matches RPi's 3.3V +# +# Do not forget the GND connection, pin 6 of the expansion header. +# + +interface sysfsgpio + +# This has no effect on the driver, but is required +adapter_khz 100 + +# Each of the JTAG lines need a gpio number set: tck tms tdi tdo +# Header pin numbers: 23 22 19 21 +sysfsgpio_jtag_nums 11 25 10 9 + +# At least one of srst or trst needs to be specified +# Header pin numbers: TRST - 26, SRST - 18 +sysfsgpio_trst_num 7 +# sysfsgpio_srst_num 24 +