bitbang: split jtag and swd operations
[openocd.git] / src / jtag / drivers / sysfsgpio.c
1 /***************************************************************************
2 * Copyright (C) 2012 by Creative Product Design, marc @ cpdesign.com.au *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program. If not, see <http://www.gnu.org/licenses/>. *
16 ***************************************************************************/
17
18 /* 2014-12: Addition of the SWD protocol support is based on the initial work
19 * on bcm2835gpio.c by Paul Fertser and modifications by Jean-Christian de Rivaz. */
20
21 /**
22 * @file
23 * This driver implements a bitbang jtag interface using gpio lines via
24 * sysfs.
25 * The aim of this driver implementation is use system GPIOs but avoid the
26 * need for a additional kernel driver.
27 * (Note memory mapped IO is another option, however it doesn't mix well with
28 * the kernel gpiolib driver - which makes sense I guess.)
29 *
30 * A gpio is required for tck, tms, tdi and tdo. One or both of srst and trst
31 * must be also be specified. The required jtag gpios are specified via the
32 * sysfsgpio_jtag_nums command or the relevant sysfsgpio_XXX_num commang.
33 * The srst and trst gpios are set via the sysfsgpio_srst_num and
34 * sysfsgpio_trst_num respectively. GPIO numbering follows the kernel
35 * convention of starting from 0.
36 *
37 * The gpios should not be in use by another entity, and must not be requested
38 * by a kernel driver without also being exported by it (otherwise they can't
39 * be exported by sysfs).
40 *
41 * The sysfs gpio interface can only manipulate one gpio at a time, so the
42 * bitbang write handler remembers the last state for tck, tms, tdi to avoid
43 * superfluous writes.
44 * For speed the sysfs "value" entry is opened at init and held open.
45 * This results in considerable gains over open-write-close (45s vs 900s)
46 *
47 * Further work could address:
48 * -srst and trst open drain/ push pull
49 * -configurable active high/low for srst & trst
50 */
51 #ifdef HAVE_CONFIG_H
52 #include "config.h"
53 #endif
54
55 #include <helper/time_support.h>
56 #include <jtag/interface.h>
57 #include "bitbang.h"
58
59 /*
60 * Helper func to determine if gpio number valid
61 *
62 * Assume here that there will be less than 10000 gpios on a system
63 */
64 static bool is_gpio_valid(int gpio)
65 {
66 return gpio >= 0 && gpio < 10000;
67 }
68
69 /*
70 * Helper func to open, write to and close a file
71 * name and valstr must be null terminated.
72 *
73 * Returns negative on failure.
74 */
75 static int open_write_close(const char *name, const char *valstr)
76 {
77 int ret;
78 int fd = open(name, O_WRONLY);
79 if (fd < 0)
80 return fd;
81
82 ret = write(fd, valstr, strlen(valstr));
83 close(fd);
84
85 return ret;
86 }
87
88 /*
89 * Helper func to unexport gpio from sysfs
90 */
91 static void unexport_sysfs_gpio(int gpio)
92 {
93 char gpiostr[5];
94
95 if (!is_gpio_valid(gpio))
96 return;
97
98 snprintf(gpiostr, sizeof(gpiostr), "%d", gpio);
99 if (open_write_close("/sys/class/gpio/unexport", gpiostr) < 0)
100 LOG_ERROR("Couldn't unexport gpio %d", gpio);
101 }
102
103 /*
104 * Exports and sets up direction for gpio.
105 * If the gpio is an output, it is initialized according to init_high,
106 * otherwise it is ignored.
107 *
108 * If the gpio is already exported we just show a warning and continue; if
109 * openocd happened to crash (or was killed by user) then the gpios will not
110 * have been cleaned up.
111 */
112 static int setup_sysfs_gpio(int gpio, int is_output, int init_high)
113 {
114 struct timeval timeout, now;
115 char buf[40];
116 char gpiostr[5];
117 int ret;
118
119 if (!is_gpio_valid(gpio))
120 return ERROR_OK;
121
122 snprintf(gpiostr, sizeof(gpiostr), "%d", gpio);
123 ret = open_write_close("/sys/class/gpio/export", gpiostr);
124 if (ret < 0) {
125 if (errno == EBUSY) {
126 LOG_WARNING("gpio %d is already exported", gpio);
127 } else {
128 LOG_ERROR("Couldn't export gpio %d", gpio);
129 perror("sysfsgpio: ");
130 return ERROR_FAIL;
131 }
132 }
133
134 gettimeofday(&timeout, NULL);
135 timeval_add_time(&timeout, 0, 500000);
136
137 snprintf(buf, sizeof(buf), "/sys/class/gpio/gpio%d/direction", gpio);
138 for (;;) {
139 ret = open_write_close(buf, is_output ? (init_high ? "high" : "low") : "in");
140 if (ret >= 0 || errno != EACCES)
141 break;
142 gettimeofday(&now, NULL);
143 if (timeval_compare(&now, &timeout) >= 0)
144 break;
145 jtag_sleep(10000);
146 }
147 if (ret < 0) {
148 LOG_ERROR("Couldn't set direction for gpio %d", gpio);
149 perror("sysfsgpio: ");
150 unexport_sysfs_gpio(gpio);
151 return ERROR_FAIL;
152 }
153
154 snprintf(buf, sizeof(buf), "/sys/class/gpio/gpio%d/value", gpio);
155 for (;;) {
156 ret = open(buf, O_RDWR | O_NONBLOCK | O_SYNC);
157 if (ret >= 0 || errno != EACCES)
158 break;
159 gettimeofday(&now, NULL);
160 if (timeval_compare(&now, &timeout) >= 0)
161 break;
162 jtag_sleep(10000);
163 }
164 if (ret < 0) {
165 LOG_ERROR("Couldn't open value for gpio %d", gpio);
166 perror("sysfsgpio: ");
167 unexport_sysfs_gpio(gpio);
168 }
169
170 return ret;
171 }
172
173 /* gpio numbers for each gpio. Negative values are invalid */
174 static int tck_gpio = -1;
175 static int tms_gpio = -1;
176 static int tdi_gpio = -1;
177 static int tdo_gpio = -1;
178 static int trst_gpio = -1;
179 static int srst_gpio = -1;
180 static int swclk_gpio = -1;
181 static int swdio_gpio = -1;
182
183 /*
184 * file descriptors for /sys/class/gpio/gpioXX/value
185 * Set up during init.
186 */
187 static int tck_fd = -1;
188 static int tms_fd = -1;
189 static int tdi_fd = -1;
190 static int tdo_fd = -1;
191 static int trst_fd = -1;
192 static int srst_fd = -1;
193 static int swclk_fd = -1;
194 static int swdio_fd = -1;
195
196 static int last_swclk;
197 static int last_swdio;
198 static bool last_stored;
199 static bool swdio_input;
200
201 static void sysfsgpio_swdio_drive(bool is_output)
202 {
203 char buf[40];
204 int ret;
205
206 snprintf(buf, sizeof(buf), "/sys/class/gpio/gpio%d/direction", swdio_gpio);
207 ret = open_write_close(buf, is_output ? "high" : "in");
208 if (ret < 0) {
209 LOG_ERROR("Couldn't set direction for gpio %d", swdio_gpio);
210 perror("sysfsgpio: ");
211 }
212
213 last_stored = false;
214 swdio_input = !is_output;
215 }
216
217 static int sysfsgpio_swdio_read(void)
218 {
219 char buf[1];
220
221 /* important to seek to signal sysfs of new read */
222 lseek(swdio_fd, 0, SEEK_SET);
223 int ret = read(swdio_fd, &buf, sizeof(buf));
224
225 if (ret < 0) {
226 LOG_WARNING("reading swdio failed");
227 return 0;
228 }
229
230 return buf[0] != '0';
231 }
232
233 static int sysfsgpio_swd_write(int swclk, int swdio)
234 {
235 const char one[] = "1";
236 const char zero[] = "0";
237
238 size_t bytes_written;
239
240 if (!swdio_input) {
241 if (!last_stored || (swdio != last_swdio)) {
242 bytes_written = write(swdio_fd, swdio ? &one : &zero, 1);
243 if (bytes_written != 1)
244 LOG_WARNING("writing swdio failed");
245 }
246 }
247
248 /* write swclk last */
249 if (!last_stored || (swclk != last_swclk)) {
250 bytes_written = write(swclk_fd, swclk ? &one : &zero, 1);
251 if (bytes_written != 1)
252 LOG_WARNING("writing swclk failed");
253 }
254
255 last_swdio = swdio;
256 last_swclk = swclk;
257 last_stored = true;
258
259 return ERROR_OK;
260 }
261
262 /*
263 * Bitbang interface read of TDO
264 *
265 * The sysfs value will read back either '0' or '1'. The trick here is to call
266 * lseek to bypass buffering in the sysfs kernel driver.
267 */
268 static bb_value_t sysfsgpio_read(void)
269 {
270 char buf[1];
271
272 /* important to seek to signal sysfs of new read */
273 lseek(tdo_fd, 0, SEEK_SET);
274 int ret = read(tdo_fd, &buf, sizeof(buf));
275
276 if (ret < 0) {
277 LOG_WARNING("reading tdo failed");
278 return 0;
279 }
280
281 return buf[0] == '0' ? BB_LOW : BB_HIGH;
282 }
283
284 /*
285 * Bitbang interface write of TCK, TMS, TDI
286 *
287 * Seeing as this is the only function where the outputs are changed,
288 * we can cache the old value to avoid needlessly writing it.
289 */
290 static int sysfsgpio_write(int tck, int tms, int tdi)
291 {
292 const char one[] = "1";
293 const char zero[] = "0";
294
295 static int last_tck;
296 static int last_tms;
297 static int last_tdi;
298
299 static int first_time;
300 size_t bytes_written;
301
302 if (!first_time) {
303 last_tck = !tck;
304 last_tms = !tms;
305 last_tdi = !tdi;
306 first_time = 1;
307 }
308
309 if (tdi != last_tdi) {
310 bytes_written = write(tdi_fd, tdi ? &one : &zero, 1);
311 if (bytes_written != 1)
312 LOG_WARNING("writing tdi failed");
313 }
314
315 if (tms != last_tms) {
316 bytes_written = write(tms_fd, tms ? &one : &zero, 1);
317 if (bytes_written != 1)
318 LOG_WARNING("writing tms failed");
319 }
320
321 /* write clk last */
322 if (tck != last_tck) {
323 bytes_written = write(tck_fd, tck ? &one : &zero, 1);
324 if (bytes_written != 1)
325 LOG_WARNING("writing tck failed");
326 }
327
328 last_tdi = tdi;
329 last_tms = tms;
330 last_tck = tck;
331
332 return ERROR_OK;
333 }
334
335 /*
336 * Bitbang interface to manipulate reset lines SRST and TRST
337 *
338 * (1) assert or (0) deassert reset lines
339 */
340 static int sysfsgpio_reset(int trst, int srst)
341 {
342 LOG_DEBUG("sysfsgpio_reset");
343 const char one[] = "1";
344 const char zero[] = "0";
345 size_t bytes_written;
346
347 /* assume active low */
348 if (srst_fd >= 0) {
349 bytes_written = write(srst_fd, srst ? &zero : &one, 1);
350 if (bytes_written != 1)
351 LOG_WARNING("writing srst failed");
352 }
353
354 /* assume active low */
355 if (trst_fd >= 0) {
356 bytes_written = write(trst_fd, trst ? &zero : &one, 1);
357 if (bytes_written != 1)
358 LOG_WARNING("writing trst failed");
359 }
360
361 return ERROR_OK;
362 }
363
364 COMMAND_HANDLER(sysfsgpio_handle_jtag_gpionums)
365 {
366 if (CMD_ARGC == 4) {
367 COMMAND_PARSE_NUMBER(int, CMD_ARGV[0], tck_gpio);
368 COMMAND_PARSE_NUMBER(int, CMD_ARGV[1], tms_gpio);
369 COMMAND_PARSE_NUMBER(int, CMD_ARGV[2], tdi_gpio);
370 COMMAND_PARSE_NUMBER(int, CMD_ARGV[3], tdo_gpio);
371 } else if (CMD_ARGC != 0) {
372 return ERROR_COMMAND_SYNTAX_ERROR;
373 }
374
375 command_print(CMD,
376 "SysfsGPIO nums: tck = %d, tms = %d, tdi = %d, tdo = %d",
377 tck_gpio, tms_gpio, tdi_gpio, tdo_gpio);
378
379 return ERROR_OK;
380 }
381
382 COMMAND_HANDLER(sysfsgpio_handle_jtag_gpionum_tck)
383 {
384 if (CMD_ARGC == 1)
385 COMMAND_PARSE_NUMBER(int, CMD_ARGV[0], tck_gpio);
386
387 command_print(CMD, "SysfsGPIO num: tck = %d", tck_gpio);
388 return ERROR_OK;
389 }
390
391 COMMAND_HANDLER(sysfsgpio_handle_jtag_gpionum_tms)
392 {
393 if (CMD_ARGC == 1)
394 COMMAND_PARSE_NUMBER(int, CMD_ARGV[0], tms_gpio);
395
396 command_print(CMD, "SysfsGPIO num: tms = %d", tms_gpio);
397 return ERROR_OK;
398 }
399
400 COMMAND_HANDLER(sysfsgpio_handle_jtag_gpionum_tdo)
401 {
402 if (CMD_ARGC == 1)
403 COMMAND_PARSE_NUMBER(int, CMD_ARGV[0], tdo_gpio);
404
405 command_print(CMD, "SysfsGPIO num: tdo = %d", tdo_gpio);
406 return ERROR_OK;
407 }
408
409 COMMAND_HANDLER(sysfsgpio_handle_jtag_gpionum_tdi)
410 {
411 if (CMD_ARGC == 1)
412 COMMAND_PARSE_NUMBER(int, CMD_ARGV[0], tdi_gpio);
413
414 command_print(CMD, "SysfsGPIO num: tdi = %d", tdi_gpio);
415 return ERROR_OK;
416 }
417
418 COMMAND_HANDLER(sysfsgpio_handle_jtag_gpionum_srst)
419 {
420 if (CMD_ARGC == 1)
421 COMMAND_PARSE_NUMBER(int, CMD_ARGV[0], srst_gpio);
422
423 command_print(CMD, "SysfsGPIO num: srst = %d", srst_gpio);
424 return ERROR_OK;
425 }
426
427 COMMAND_HANDLER(sysfsgpio_handle_jtag_gpionum_trst)
428 {
429 if (CMD_ARGC == 1)
430 COMMAND_PARSE_NUMBER(int, CMD_ARGV[0], trst_gpio);
431
432 command_print(CMD, "SysfsGPIO num: trst = %d", trst_gpio);
433 return ERROR_OK;
434 }
435
436 COMMAND_HANDLER(sysfsgpio_handle_swd_gpionums)
437 {
438 if (CMD_ARGC == 2) {
439 COMMAND_PARSE_NUMBER(int, CMD_ARGV[0], swclk_gpio);
440 COMMAND_PARSE_NUMBER(int, CMD_ARGV[1], swdio_gpio);
441 } else if (CMD_ARGC != 0) {
442 return ERROR_COMMAND_SYNTAX_ERROR;
443 }
444
445 command_print(CMD,
446 "SysfsGPIO nums: swclk = %d, swdio = %d",
447 swclk_gpio, swdio_gpio);
448
449 return ERROR_OK;
450 }
451
452 COMMAND_HANDLER(sysfsgpio_handle_swd_gpionum_swclk)
453 {
454 if (CMD_ARGC == 1)
455 COMMAND_PARSE_NUMBER(int, CMD_ARGV[0], swclk_gpio);
456
457 command_print(CMD, "SysfsGPIO num: swclk = %d", swclk_gpio);
458 return ERROR_OK;
459 }
460
461 COMMAND_HANDLER(sysfsgpio_handle_swd_gpionum_swdio)
462 {
463 if (CMD_ARGC == 1)
464 COMMAND_PARSE_NUMBER(int, CMD_ARGV[0], swdio_gpio);
465
466 command_print(CMD, "SysfsGPIO num: swdio = %d", swdio_gpio);
467 return ERROR_OK;
468 }
469
470 static const struct command_registration sysfsgpio_command_handlers[] = {
471 {
472 .name = "sysfsgpio_jtag_nums",
473 .handler = &sysfsgpio_handle_jtag_gpionums,
474 .mode = COMMAND_CONFIG,
475 .help = "gpio numbers for tck, tms, tdi, tdo. (in that order)",
476 .usage = "[tck tms tdi tdo]",
477 },
478 {
479 .name = "sysfsgpio_tck_num",
480 .handler = &sysfsgpio_handle_jtag_gpionum_tck,
481 .mode = COMMAND_CONFIG,
482 .help = "gpio number for tck.",
483 .usage = "[tck]",
484 },
485 {
486 .name = "sysfsgpio_tms_num",
487 .handler = &sysfsgpio_handle_jtag_gpionum_tms,
488 .mode = COMMAND_CONFIG,
489 .help = "gpio number for tms.",
490 .usage = "[tms]",
491 },
492 {
493 .name = "sysfsgpio_tdo_num",
494 .handler = &sysfsgpio_handle_jtag_gpionum_tdo,
495 .mode = COMMAND_CONFIG,
496 .help = "gpio number for tdo.",
497 .usage = "[tdo]",
498 },
499 {
500 .name = "sysfsgpio_tdi_num",
501 .handler = &sysfsgpio_handle_jtag_gpionum_tdi,
502 .mode = COMMAND_CONFIG,
503 .help = "gpio number for tdi.",
504 .usage = "[tdi]",
505 },
506 {
507 .name = "sysfsgpio_srst_num",
508 .handler = &sysfsgpio_handle_jtag_gpionum_srst,
509 .mode = COMMAND_CONFIG,
510 .help = "gpio number for srst.",
511 .usage = "[srst]",
512 },
513 {
514 .name = "sysfsgpio_trst_num",
515 .handler = &sysfsgpio_handle_jtag_gpionum_trst,
516 .mode = COMMAND_CONFIG,
517 .help = "gpio number for trst.",
518 .usage = "[trst]",
519 },
520 {
521 .name = "sysfsgpio_swd_nums",
522 .handler = &sysfsgpio_handle_swd_gpionums,
523 .mode = COMMAND_CONFIG,
524 .help = "gpio numbers for swclk, swdio. (in that order)",
525 .usage = "[swclk swdio]",
526 },
527 {
528 .name = "sysfsgpio_swclk_num",
529 .handler = &sysfsgpio_handle_swd_gpionum_swclk,
530 .mode = COMMAND_CONFIG,
531 .help = "gpio number for swclk.",
532 .usage = "[swclk]",
533 },
534 {
535 .name = "sysfsgpio_swdio_num",
536 .handler = &sysfsgpio_handle_swd_gpionum_swdio,
537 .mode = COMMAND_CONFIG,
538 .help = "gpio number for swdio.",
539 .usage = "[swdio]",
540 },
541 COMMAND_REGISTRATION_DONE
542 };
543
544 static int sysfsgpio_init(void);
545 static int sysfsgpio_quit(void);
546
547 static const char * const sysfsgpio_transports[] = { "jtag", "swd", NULL };
548
549 static struct jtag_interface sysfsgpio_interface = {
550 .supported = DEBUG_CAP_TMS_SEQ,
551 .execute_queue = bitbang_execute_queue,
552 };
553
554 struct adapter_driver sysfsgpio_adapter_driver = {
555 .name = "sysfsgpio",
556 .transports = sysfsgpio_transports,
557 .commands = sysfsgpio_command_handlers,
558
559 .init = sysfsgpio_init,
560 .quit = sysfsgpio_quit,
561 .reset = sysfsgpio_reset,
562
563 .jtag_ops = &sysfsgpio_interface,
564 .swd_ops = &bitbang_swd,
565 };
566
567 static struct bitbang_interface sysfsgpio_bitbang = {
568 .read = sysfsgpio_read,
569 .write = sysfsgpio_write,
570 .swdio_read = sysfsgpio_swdio_read,
571 .swdio_drive = sysfsgpio_swdio_drive,
572 .swd_write = sysfsgpio_swd_write,
573 .blink = 0
574 };
575
576 /* helper func to close and cleanup files only if they were valid/ used */
577 static void cleanup_fd(int fd, int gpio)
578 {
579 if (gpio >= 0) {
580 if (fd >= 0)
581 close(fd);
582
583 unexport_sysfs_gpio(gpio);
584 }
585 }
586
587 static void cleanup_all_fds(void)
588 {
589 cleanup_fd(tck_fd, tck_gpio);
590 cleanup_fd(tms_fd, tms_gpio);
591 cleanup_fd(tdi_fd, tdi_gpio);
592 cleanup_fd(tdo_fd, tdo_gpio);
593 cleanup_fd(trst_fd, trst_gpio);
594 cleanup_fd(srst_fd, srst_gpio);
595 cleanup_fd(swclk_fd, swclk_gpio);
596 cleanup_fd(swdio_fd, swdio_gpio);
597 }
598
599 static bool sysfsgpio_jtag_mode_possible(void)
600 {
601 if (!is_gpio_valid(tck_gpio))
602 return false;
603 if (!is_gpio_valid(tms_gpio))
604 return false;
605 if (!is_gpio_valid(tdi_gpio))
606 return false;
607 if (!is_gpio_valid(tdo_gpio))
608 return false;
609 return true;
610 }
611
612 static bool sysfsgpio_swd_mode_possible(void)
613 {
614 if (!is_gpio_valid(swclk_gpio))
615 return false;
616 if (!is_gpio_valid(swdio_gpio))
617 return false;
618 return true;
619 }
620
621 static int sysfsgpio_init(void)
622 {
623 bitbang_interface = &sysfsgpio_bitbang;
624
625 LOG_INFO("SysfsGPIO JTAG/SWD bitbang driver");
626
627 if (sysfsgpio_jtag_mode_possible()) {
628 if (sysfsgpio_swd_mode_possible())
629 LOG_INFO("JTAG and SWD modes enabled");
630 else
631 LOG_INFO("JTAG only mode enabled (specify swclk and swdio gpio to add SWD mode)");
632 } else if (sysfsgpio_swd_mode_possible()) {
633 LOG_INFO("SWD only mode enabled (specify tck, tms, tdi and tdo gpios to add JTAG mode)");
634 } else {
635 LOG_ERROR("Require tck, tms, tdi and tdo gpios for JTAG mode and/or swclk and swdio gpio for SWD mode");
636 return ERROR_JTAG_INIT_FAILED;
637 }
638
639
640 /*
641 * Configure TDO as an input, and TDI, TCK, TMS, TRST, SRST
642 * as outputs. Drive TDI and TCK low, and TMS/TRST/SRST high.
643 * For SWD, SWCLK and SWDIO are configures as output high.
644 */
645 if (tck_gpio >= 0) {
646 tck_fd = setup_sysfs_gpio(tck_gpio, 1, 0);
647 if (tck_fd < 0)
648 goto out_error;
649 }
650
651 if (tms_gpio >= 0) {
652 tms_fd = setup_sysfs_gpio(tms_gpio, 1, 1);
653 if (tms_fd < 0)
654 goto out_error;
655 }
656
657 if (tdi_gpio >= 0) {
658 tdi_fd = setup_sysfs_gpio(tdi_gpio, 1, 0);
659 if (tdi_fd < 0)
660 goto out_error;
661 }
662
663 if (tdo_gpio >= 0) {
664 tdo_fd = setup_sysfs_gpio(tdo_gpio, 0, 0);
665 if (tdo_fd < 0)
666 goto out_error;
667 }
668
669 /* assume active low*/
670 if (trst_gpio >= 0) {
671 trst_fd = setup_sysfs_gpio(trst_gpio, 1, 1);
672 if (trst_fd < 0)
673 goto out_error;
674 }
675
676 /* assume active low*/
677 if (srst_gpio >= 0) {
678 srst_fd = setup_sysfs_gpio(srst_gpio, 1, 1);
679 if (srst_fd < 0)
680 goto out_error;
681 }
682
683 if (swclk_gpio >= 0) {
684 swclk_fd = setup_sysfs_gpio(swclk_gpio, 1, 0);
685 if (swclk_fd < 0)
686 goto out_error;
687 }
688
689 if (swdio_gpio >= 0) {
690 swdio_fd = setup_sysfs_gpio(swdio_gpio, 1, 0);
691 if (swdio_fd < 0)
692 goto out_error;
693 }
694
695 return ERROR_OK;
696
697 out_error:
698 cleanup_all_fds();
699 return ERROR_JTAG_INIT_FAILED;
700 }
701
702 static int sysfsgpio_quit(void)
703 {
704 cleanup_all_fds();
705 return ERROR_OK;
706 }

Linking to existing account procedure

If you already have an account and want to add another login method you MUST first sign in with your existing account and then change URL to read https://review.openocd.org/login/?link to get to this page again but this time it'll work for linking. Thank you.

SSH host keys fingerprints

1024 SHA256:YKx8b7u5ZWdcbp7/4AeXNaqElP49m6QrwfXaqQGJAOk gerrit-code-review@openocd.zylin.com (DSA)
384 SHA256:jHIbSQa4REvwCFG4cq5LBlBLxmxSqelQPem/EXIrxjk gerrit-code-review@openocd.org (ECDSA)
521 SHA256:UAOPYkU9Fjtcao0Ul/Rrlnj/OsQvt+pgdYSZ4jOYdgs gerrit-code-review@openocd.org (ECDSA)
256 SHA256:A13M5QlnozFOvTllybRZH6vm7iSt0XLxbA48yfc2yfY gerrit-code-review@openocd.org (ECDSA)
256 SHA256:spYMBqEYoAOtK7yZBrcwE8ZpYt6b68Cfh9yEVetvbXg gerrit-code-review@openocd.org (ED25519)
+--[ED25519 256]--+
|=..              |
|+o..   .         |
|*.o   . .        |
|+B . . .         |
|Bo. = o S        |
|Oo.+ + =         |
|oB=.* = . o      |
| =+=.+   + E     |
|. .=o   . o      |
+----[SHA256]-----+
2048 SHA256:0Onrb7/PHjpo6iVZ7xQX2riKN83FJ3KGU0TvI0TaFG4 gerrit-code-review@openocd.zylin.com (RSA)