diff options
author | Marc Reilly <marc@cpdesign.com.au> | 2012-08-20 09:46:07 +1000 |
---|---|---|
committer | Freddie Chopin <freddie.chopin@gmail.com> | 2012-09-26 16:16:17 +0000 |
commit | fe52282c37cc0c4ab9863e95ef6970e6fad540b4 (patch) | |
tree | 62fe800ed0fac86ad3d286774b61be2b48cca8fa /src/jtag | |
parent | a4830e7a6ad0c6bf954184894a0dbec74075306b (diff) | |
download | riscv-openocd-fe52282c37cc0c4ab9863e95ef6970e6fad540b4.zip riscv-openocd-fe52282c37cc0c4ab9863e95ef6970e6fad540b4.tar.gz riscv-openocd-fe52282c37cc0c4ab9863e95ef6970e6fad540b4.tar.bz2 |
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 <marc@cpdesign.com.au>
Reviewed-on: http://openocd.zylin.com/762
Reviewed-by: Spencer Oliver <spen@spen-soft.co.uk>
Tested-by: jenkins
Reviewed-by: Freddie Chopin <freddie.chopin@gmail.com>
Diffstat (limited to 'src/jtag')
-rw-r--r-- | src/jtag/drivers/Makefile.am | 3 | ||||
-rw-r--r-- | src/jtag/drivers/sysfsgpio.c | 505 | ||||
-rw-r--r-- | src/jtag/interfaces.c | 6 |
3 files changed, 514 insertions, 0 deletions
diff --git a/src/jtag/drivers/Makefile.am b/src/jtag/drivers/Makefile.am index b02c353..d137713 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 0000000..05d9a9d --- /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 <jtag/interface.h> +#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 4b054be..19d2fcc 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, }; |