remote_bitbang: Add SWD support
[openocd.git] / contrib / remote_bitbang / remote_bitbang_sysfsgpio.c
1 // SPDX-License-Identifier: GPL-2.0-or-later
2
3 /***************************************************************************
4 * Copyright (C) 2021 by Manuel Wick <manuel@matronix.de> *
5 * Copyright (C) 2013 Paul Fertser <fercerpav@gmail.com> *
6 * Copyright (C) 2012 by Creative Product Design, marc @ cpdesign.com.au *
7 ***************************************************************************/
8
9 /*
10 * This is a test application to be used as a remote bitbang server for
11 * the OpenOCD remote_bitbang interface driver.
12 *
13 * To compile run:
14 * gcc -Wall -ansi -pedantic -std=c99 -o remote_bitbang_sysfsgpio remote_bitbang_sysfsgpio.c
15 *
16 *
17 * Usage example:
18 *
19 * On Raspberry Pi run:
20 * socat TCP6-LISTEN:7777,fork EXEC:"sudo ./remote_bitbang_sysfsgpio tck 11 tms 25 tdo 9 tdi 10"
21 *
22 * On host run:
23 * openocd -c "adapter driver remote_bitbang; remote_bitbang host raspberrypi; remote_bitbang port 7777" \
24 * -f target/stm32f1x.cfg
25 *
26 * Or if you want to test UNIX sockets, run both on Raspberry Pi:
27 * socat UNIX-LISTEN:/tmp/remotebitbang-socket,fork EXEC:"sudo ./remote_bitbang_sysfsgpio tck 11 tms 25 tdo 9 tdi 10"
28 * openocd -c "adapter driver remote_bitbang; remote_bitbang host /tmp/remotebitbang-socket" -f target/stm32f1x.cfg
29 */
30
31 #include <sys/types.h>
32 #include <sys/stat.h>
33 #include <fcntl.h>
34 #include <unistd.h>
35 #include <stdlib.h>
36 #include <stdio.h>
37 #include <string.h>
38 #include <errno.h>
39
40 #define LOG_ERROR(...) do { \
41 fprintf(stderr, __VA_ARGS__); \
42 fputc('\n', stderr); \
43 } while (0)
44 #define LOG_WARNING(...) LOG_ERROR(__VA_ARGS__)
45
46 #define ERROR_OK (-1)
47 #define ERROR_FAIL (-2)
48 #define ERROR_JTAG_INIT_FAILED ERROR_FAIL
49
50 /*
51 * Helper func to determine if gpio number valid
52 *
53 * Assume here that there will be less than 1000 gpios on a system
54 */
55 static int is_gpio_valid(int gpio)
56 {
57 return gpio >= 0 && gpio < 1000;
58 }
59
60 /*
61 * Helper func to open, write to and close a file
62 * name and valstr must be null terminated.
63 *
64 * Returns negative on failure.
65 */
66 static int open_write_close(const char *name, const char *valstr)
67 {
68 int ret;
69 int fd = open(name, O_WRONLY);
70 if (fd < 0)
71 return fd;
72
73 ret = write(fd, valstr, strlen(valstr));
74 close(fd);
75
76 return ret;
77 }
78
79 /*
80 * Helper func to unexport gpio from sysfs
81 */
82 static void unexport_sysfs_gpio(int gpio)
83 {
84 char gpiostr[4];
85
86 if (!is_gpio_valid(gpio))
87 return;
88
89 snprintf(gpiostr, sizeof(gpiostr), "%d", gpio);
90 if (open_write_close("/sys/class/gpio/unexport", gpiostr) < 0)
91 LOG_ERROR("Couldn't unexport gpio %d", gpio);
92
93 return;
94 }
95
96 /*
97 * Exports and sets up direction for gpio.
98 * If the gpio is an output, it is initialized according to init_high,
99 * otherwise it is ignored.
100 *
101 * When open_rw is set, the file descriptor will be open as read and write,
102 * e.g. for SWDIO (TMS) that is used as input and output.
103 *
104 * If the gpio is already exported we just show a warning and continue; if
105 * openocd happened to crash (or was killed by user) then the gpios will not
106 * have been cleaned up.
107 */
108 static int setup_sysfs_gpio(int gpio, int is_output, int init_high, int open_rw)
109 {
110 char buf[40];
111 char gpiostr[4];
112 int ret;
113
114 if (!is_gpio_valid(gpio))
115 return ERROR_OK;
116
117 snprintf(gpiostr, sizeof(gpiostr), "%d", gpio);
118 ret = open_write_close("/sys/class/gpio/export", gpiostr);
119 if (ret < 0) {
120 if (errno == EBUSY) {
121 LOG_WARNING("gpio %d is already exported", gpio);
122 } else {
123 LOG_ERROR("Couldn't export gpio %d", gpio);
124 perror("sysfsgpio: ");
125 return ERROR_FAIL;
126 }
127 }
128
129 snprintf(buf, sizeof(buf), "/sys/class/gpio/gpio%d/direction", gpio);
130 ret = open_write_close(buf, is_output ? (init_high ? "high" : "low") : "in");
131 if (ret < 0) {
132 LOG_ERROR("Couldn't set direction for gpio %d", gpio);
133 perror("sysfsgpio: ");
134 unexport_sysfs_gpio(gpio);
135 return ERROR_FAIL;
136 }
137
138 snprintf(buf, sizeof(buf), "/sys/class/gpio/gpio%d/value", gpio);
139 if (open_rw)
140 ret = open(buf, O_RDWR | O_NONBLOCK | O_SYNC);
141 else if (is_output)
142 ret = open(buf, O_WRONLY | O_NONBLOCK | O_SYNC);
143 else
144 ret = open(buf, O_RDONLY | O_NONBLOCK | O_SYNC);
145
146 if (ret < 0)
147 unexport_sysfs_gpio(gpio);
148
149 return ret;
150 }
151
152 /*
153 * Change direction for gpio.
154 */
155 static int change_dir_sysfs_gpio(int gpio, int is_output, int init_high)
156 {
157 char buf[40];
158 int ret;
159
160 if (!is_gpio_valid(gpio))
161 return ERROR_OK;
162
163 snprintf(buf, sizeof(buf), "/sys/class/gpio/gpio%d/direction", gpio);
164 ret = open_write_close(buf, is_output ? (init_high ? "high" : "low") : "in");
165 if (ret < 0) {
166 LOG_ERROR("Couldn't set direction for gpio %d", gpio);
167 perror("sysfsgpio: ");
168 unexport_sysfs_gpio(gpio);
169 return ERROR_FAIL;
170 }
171
172 return ERROR_OK;
173 }
174
175 /* gpio numbers for each gpio. Negative values are invalid */
176 static int tck_gpio = -1;
177 static int tms_gpio = -1;
178 static int tdi_gpio = -1;
179 static int tdo_gpio = -1;
180 static int trst_gpio = -1;
181 static int srst_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
194 /*
195 * GPIO state of /sys/class/gpio/gpioXX/value
196 */
197 static int last_tck = -1;
198 static int last_tms = -1;
199 static int last_tms_drive = -1;
200 static int last_tdi = -1;
201 static int last_initialized = -1;
202
203 /*
204 * Bitbang interface read of TDO
205 *
206 * The sysfs value will read back either '0' or '1'. The trick here is to call
207 * lseek to bypass buffering in the sysfs kernel driver.
208 */
209 static int sysfsgpio_read(void)
210 {
211 char buf[1];
212
213 /* important to seek to signal sysfs of new read */
214 lseek(tdo_fd, 0, SEEK_SET);
215 int ret = read(tdo_fd, &buf, sizeof(buf));
216
217 if (ret < 0) {
218 LOG_WARNING("reading tdo failed");
219 return 0;
220 }
221
222 return buf[0];
223 }
224
225 /*
226 * Bitbang interface write of TCK, TMS, TDI
227 *
228 * Output states are changed here and in sysfsgpio_write_swd,
229 * which are not used simultaneously, so we can cache the old
230 * value to avoid needlessly writing it.
231 */
232 static void sysfsgpio_write(int tck, int tms, int tdi)
233 {
234 const char one[] = "1";
235 const char zero[] = "0";
236
237 size_t bytes_written;
238
239 if (!last_initialized) {
240 last_tck = !tck;
241 last_tms = !tms;
242 last_tdi = !tdi;
243 last_initialized = 1;
244 }
245
246 if (tdi != last_tdi) {
247 bytes_written = write(tdi_fd, tdi ? &one : &zero, 1);
248 if (bytes_written != 1)
249 LOG_WARNING("writing tdi failed");
250 }
251
252 if (tms != last_tms) {
253 bytes_written = write(tms_fd, tms ? &one : &zero, 1);
254 if (bytes_written != 1)
255 LOG_WARNING("writing tms failed");
256 }
257
258 /* write clk last */
259 if (tck != last_tck) {
260 bytes_written = write(tck_fd, tck ? &one : &zero, 1);
261 if (bytes_written != 1)
262 LOG_WARNING("writing tck failed");
263 }
264
265 last_tdi = tdi;
266 last_tms = tms;
267 last_tck = tck;
268 }
269
270 /*
271 * Bitbang interface to manipulate reset lines SRST and TRST
272 *
273 * (1) assert or (0) deassert reset lines
274 */
275 static void sysfsgpio_reset(int trst, int srst)
276 {
277 const char one[] = "1";
278 const char zero[] = "0";
279 size_t bytes_written;
280
281 /* assume active low */
282 if (srst_fd >= 0) {
283 bytes_written = write(srst_fd, srst ? &zero : &one, 1);
284 if (bytes_written != 1)
285 LOG_WARNING("writing srst failed");
286 }
287
288 /* assume active low */
289 if (trst_fd >= 0) {
290 bytes_written = write(trst_fd, trst ? &zero : &one, 1);
291 if (bytes_written != 1)
292 LOG_WARNING("writing trst failed");
293 }
294 }
295
296 /*
297 * Bitbang interface set direction of SWDIO (TMS)
298 */
299 static void sysfsgpio_swdio_drive(int is_output)
300 {
301 int ret;
302
303 if (is_output != 0 && last_tms == -1)
304 last_tms = 0;
305
306 ret = change_dir_sysfs_gpio(tms_gpio, (is_output != 0) ? 1 : 0, last_tms);
307 if (ret != ERROR_OK)
308 LOG_WARNING("Failed to change SWDIO (TMS) direction to output");
309 else
310 last_tms_drive = (is_output != 0) ? 1 : 0;
311 }
312
313 /*
314 * Bitbang interface read of SWDIO (TMS)
315 *
316 * The sysfs value will read back either '0' or '1'. The trick here is to call
317 * lseek to bypass buffering in the sysfs kernel driver.
318 */
319 static int sysfsgpio_swdio_read(void)
320 {
321 char buf[1];
322
323 /* important to seek to signal sysfs of new read */
324 lseek(tms_fd, 0, SEEK_SET);
325 int ret = read(tms_fd, &buf, sizeof(buf));
326
327 if (ret < 0) {
328 LOG_WARNING("reading swdio (tms) failed");
329 return 0;
330 }
331
332 return buf[0];
333 }
334
335 /*
336 * Bitbang interface write of SWCLK (TCK) and SWDIO (TMS)
337 *
338 * Output states are changed here and in sysfsgpio_write, which
339 * are not used simultaneously, so we can cache the old value
340 * to avoid needlessly writing it.
341 */
342 static void sysfsgpio_swd_write(int swclk, int swdio)
343 {
344 static const char one[] = "1";
345 static const char zero[] = "0";
346
347 size_t bytes_written;
348
349 if (!last_initialized) {
350 last_tck = !swclk;
351 last_tms = !swdio;
352 last_initialized = 1;
353 }
354
355 if (last_tms_drive == 1 && swdio != last_tms) {
356 bytes_written = write(tms_fd, swdio ? &one : &zero, 1);
357 if (bytes_written != 1)
358 LOG_WARNING("writing swdio (tms) failed");
359 }
360
361 /* write clk last */
362 if (swclk != last_tck) {
363 bytes_written = write(tck_fd, swclk ? &one : &zero, 1);
364 if (bytes_written != 1)
365 LOG_WARNING("writing swclk (tck) failed");
366 }
367
368 last_tms = swdio;
369 last_tck = swclk;
370 }
371
372 /* helper func to close and cleanup files only if they were valid/ used */
373 static void cleanup_fd(int fd, int gpio)
374 {
375 if (gpio >= 0) {
376 if (fd >= 0)
377 close(fd);
378
379 unexport_sysfs_gpio(gpio);
380 }
381 }
382
383 static void cleanup_all_fds(void)
384 {
385 cleanup_fd(tck_fd, tck_gpio);
386 cleanup_fd(tms_fd, tms_gpio);
387 cleanup_fd(tdi_fd, tdi_gpio);
388 cleanup_fd(tdo_fd, tdo_gpio);
389 cleanup_fd(trst_fd, trst_gpio);
390 cleanup_fd(srst_fd, srst_gpio);
391 }
392
393 static void process_remote_protocol(void)
394 {
395 int c;
396 while (1) {
397 c = getchar();
398 if (c == EOF || c == 'Q') /* Quit */
399 break;
400 else if (c == 'b' || c == 'B') /* Blink */
401 continue;
402 else if (c >= 'r' && c <= 'r' + 3) { /* Reset */
403 char d = c - 'r';
404 sysfsgpio_reset(!!(d & 2),
405 (d & 1));
406 } else if (c >= '0' && c <= '0' + 7) { /* Write */
407 char d = c - '0';
408 sysfsgpio_write(!!(d & 4),
409 !!(d & 2),
410 (d & 1));
411 } else if (c == 'R')
412 putchar(sysfsgpio_read());
413 else if (c == 'c') /* SWDIO read */
414 putchar(sysfsgpio_swdio_read());
415 else if (c == 'o' || c == 'O') /* SWDIO drive */
416 sysfsgpio_swdio_drive(c == 'o' ? 0 : 1);
417 else if (c >= 'd' && c <= 'g') { /* SWD write */
418 char d = c - 'd';
419 sysfsgpio_swd_write((d & 2), (d & 1));
420 }
421 else
422 LOG_ERROR("Unknown command '%c' received", c);
423 }
424 }
425
426 int main(int argc, char *argv[])
427 {
428 LOG_WARNING("SysfsGPIO remote_bitbang JTAG+SWD driver\n");
429
430 for (int i = 1; i < argc; i++) {
431 if (!strcmp(argv[i], "tck"))
432 tck_gpio = atoi(argv[++i]);
433 else if (!strcmp(argv[i], "tms"))
434 tms_gpio = atoi(argv[++i]);
435 else if (!strcmp(argv[i], "tdo"))
436 tdo_gpio = atoi(argv[++i]);
437 else if (!strcmp(argv[i], "tdi"))
438 tdi_gpio = atoi(argv[++i]);
439 else if (!strcmp(argv[i], "trst"))
440 trst_gpio = atoi(argv[++i]);
441 else if (!strcmp(argv[i], "srst"))
442 srst_gpio = atoi(argv[++i]);
443 else {
444 LOG_ERROR("Usage:\n%s ((tck|tms|tdo|tdi|trst|srst) num)*", argv[0]);
445 return -1;
446 }
447 }
448
449 if (!(is_gpio_valid(tck_gpio)
450 && is_gpio_valid(tms_gpio)
451 && is_gpio_valid(tdi_gpio)
452 && is_gpio_valid(tdo_gpio))) {
453 if (!is_gpio_valid(tck_gpio))
454 LOG_ERROR("gpio num for tck is invalid");
455 if (!is_gpio_valid(tms_gpio))
456 LOG_ERROR("gpio num for tms is invalid");
457 if (!is_gpio_valid(tdo_gpio))
458 LOG_ERROR("gpio num for tdo is invalid");
459 if (!is_gpio_valid(tdi_gpio))
460 LOG_ERROR("gpio num for tdi is invalid");
461
462 LOG_ERROR("Require tck, tms, tdi and tdo gpios to all be specified");
463 return ERROR_JTAG_INIT_FAILED;
464 }
465
466 /*
467 * Configure TDO as an input, and TDI, TCK, TMS, TRST, SRST
468 * as outputs. Drive TDI and TCK low, and TMS/TRST/SRST high.
469 */
470 tck_fd = setup_sysfs_gpio(tck_gpio, 1, 0, 0);
471 if (tck_fd < 0)
472 goto out_error;
473
474 tms_fd = setup_sysfs_gpio(tms_gpio, 1, 1, 1);
475 if (tms_fd < 0)
476 goto out_error;
477 last_tms_drive = 0;
478
479 tdi_fd = setup_sysfs_gpio(tdi_gpio, 1, 0, 0);
480 if (tdi_fd < 0)
481 goto out_error;
482
483 tdo_fd = setup_sysfs_gpio(tdo_gpio, 0, 0, 0);
484 if (tdo_fd < 0)
485 goto out_error;
486
487 /* assume active low */
488 if (trst_gpio > 0) {
489 trst_fd = setup_sysfs_gpio(trst_gpio, 1, 1, 0);
490 if (trst_fd < 0)
491 goto out_error;
492 }
493
494 /* assume active low */
495 if (srst_gpio > 0) {
496 srst_fd = setup_sysfs_gpio(srst_gpio, 1, 1, 0);
497 if (srst_fd < 0)
498 goto out_error;
499 }
500
501 last_initialized = 0;
502
503 LOG_WARNING("SysfsGPIO nums: tck = %d, tms = %d, tdi = %d, tdo = %d",
504 tck_gpio, tms_gpio, tdi_gpio, tdo_gpio);
505 LOG_WARNING("SysfsGPIO num: srst = %d", srst_gpio);
506 LOG_WARNING("SysfsGPIO num: trst = %d", trst_gpio);
507
508 setvbuf(stdout, NULL, _IONBF, 0);
509 process_remote_protocol();
510
511 cleanup_all_fds();
512 return 0;
513 out_error:
514 cleanup_all_fds();
515 return ERROR_JTAG_INIT_FAILED;
516 }

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)