├── .gitignore ├── Makefile ├── README.md └── main.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | rtt2pty 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all 2 | all: rtt2pty 3 | 4 | rtt2pty: main.o 5 | $(CC) $? -ldl -o $@ 6 | 7 | clean: 8 | rm -f rtt2pty main.o 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RTT to PTY bridge 2 | 3 | This utility allows you to form a bridge between Segger's 4 | [Real Time Transfer (RTT)](https://www.segger.com/products/debug-probes/j-link/technology/about-real-time-transfer/) 5 | and a pseudo-terminal interface (PTY). 6 | 7 | It behaves similarly to common USB to serial adapters, instantiating a PTY 8 | device while the utility is running, allowing you to send and receive data 9 | through the device using any number of common utilities (`minicom`, etc.). 10 | 11 | ## Requirements 12 | 13 | Since RTT is a proprietary standard, it will only work with a Segger J-Link or 14 | compatible device, and you will need to download the appropriate jlink arm 15 | library (`libjlinkarm.so` on Linux, `libjlinkarm.dylib` on OS X). 16 | 17 | The application will search for `libjlinkarm.so` in common paths by default, 18 | but you can specify the file location using the `-j ` option. 19 | 20 | ## Key Parameters 21 | 22 | The following command-line parameters are available (`rtt2pty --help`): 23 | 24 | ``` 25 | rtt2pty: Segger RTT to PTY bridge 26 | rtt2pty [-opt ] 27 | 28 | Options: 29 | -d Segger device name 30 | -s J-Link serial number 31 | -S SWD/JTAG speed 32 | -b Buffer name 33 | -2 Enable bi-directional comms 34 | -j libjlinkarm.so/dylib location 35 | -p Print list of available buffers 36 | ``` 37 | 38 | ### Device Name (`-d `) 39 | 40 | The Segger target device name string. The default value is `nrf52`. 41 | 42 | **NOTE:** You can get a full list of device names by opening `JLinkExe` and 43 | entering the following command: `expdevlist `. 44 | 45 | ### Serial Number (`-s `) 46 | 47 | If you have more than one J-Link connected to your computer, the first device 48 | found will be selected by default. You can indicate the device you intend to 49 | connect to by supplying the J-Link's serial number, which is visible any time 50 | you run `JLinkExe`. 51 | 52 | ### Bi-directional Communication (`-2`) 53 | 54 | By default, the tool only allows communication in one direction (target to PC). 55 | Setting the `-2` flag allows communication in both directions. 56 | 57 | ### Buffer name (`-b`) 58 | 59 | Print available buffers with `-p`: 60 | 61 | ``` 62 | $ ./rtt2pty -p 63 | Connected to: 64 | ################# 65 | S/N: ############ 66 | Searching for RTT control block... 67 | Up-buffers: 68 | 0 Terminal (size=4096) 69 | 1 Logger (size=1024) 70 | 2 btmonitor (size=1024) 71 | Down-buffers: 72 | 0 Terminal (size=16) 73 | 1 Terminal (size=0) 74 | 2 Terminal (size=0) 75 | ``` 76 | 77 | Then open PTY to the selected buffer: 78 | 79 | ``` 80 | $ ./rtt2pty -b btmonitor 81 | Connected to: 82 | ################# 83 | S/N: ############ 84 | Searching for RTT control block... 85 | Using up-buffer #2 (size=1024) 86 | PTY name is /dev/pts/6 87 | ``` 88 | 89 | ## Example 90 | 91 | On OS X, you can run the following command to open a PTY device with 92 | bi-directional communication over RTT: 93 | 94 | ``` 95 | $ ./rtt2pty -j libjlinkarm.dylib -2 1 96 | Connected to: 97 | ################# 98 | S/N: ############ 99 | Searching for RTT control block... 100 | Using up-buffer #0 (size=1024) 101 | Using down-buffer #0 (size=16) 102 | PTY name is /dev/ttys003 103 | ``` 104 | 105 | You can then connect to the PTY using a tool like `minicom` as follows: 106 | 107 | ``` 108 | $ minicom -D /dev/ttys003 109 | ``` 110 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * rtt2pty - RTT-PTY bridge 3 | * 4 | * Copyright (C) 2017 Codecoup 5 | * 6 | * This library is free software; you can redistribute it and/or 7 | * modify it under the terms of the GNU Lesser General Public 8 | * License as published by the Free Software Foundation; either 9 | * version 2.1 of the License, or (at your option) any later version. 10 | * 11 | * This library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | * Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public 17 | * License along with this library; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | */ 20 | 21 | #define _XOPEN_SOURCE 600 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #define RTT_CONTROL_START 0 36 | #define RTT_CONTROL_STOP 1 37 | #define RTT_CONTROL_GET_DESC 2 38 | #define RTT_CONTROL_GET_NUM_BUF 3 39 | #define RTT_CONTROL_GET_STAT 4 40 | 41 | #define RTT_DIRECTION_UP 0 42 | #define RTT_DIRECTION_DOWN 1 43 | 44 | struct rtt_desc { 45 | uint32_t index; 46 | uint32_t direction; 47 | char name[32]; 48 | uint32_t size; 49 | uint32_t flags; 50 | }; 51 | 52 | /* libjlinkarm.so exports */ 53 | static int (* jlink_emu_selectbyusbsn) (unsigned sn); 54 | static int (* jlink_open) (void); 55 | static int (* jlink_execcommand) (char *in, char *out, int size); 56 | static int (* jlink_tif_select) (int); 57 | static void (* jlink_setspeed) (long int speed); 58 | static int (* jlink_connect) (void); 59 | static int (* jlink_close) (void); 60 | static unsigned (* jlink_getsn) (void); 61 | static void (* jlink_emu_getproductname) (char *out, int size); 62 | static int (*jlink_rtterminal_control) (int cmd, void *data); 63 | static int (*jlink_rtterminal_read) (int cmd, char *buf, int size); 64 | static int (*jlink_rtterminal_write) (int cmd, char *buf, int size); 65 | 66 | static const struct option options[] = { 67 | { "device", required_argument, NULL, 'd' }, 68 | { "if", required_argument, NULL, 'i' }, 69 | { "sn", required_argument, NULL, 's' }, 70 | { "speed", required_argument, NULL, 'S' }, 71 | { "buffer", required_argument, NULL, 'b' }, 72 | { "bidir", no_argument, NULL, '2' }, 73 | { "jlinkarm", required_argument, NULL, 'j' }, 74 | { "help", no_argument, NULL, 'h' }, 75 | { "link", required_argument, NULL, 'l' }, 76 | { } 77 | }; 78 | 79 | static unsigned opt_sn = 0; 80 | static const char *opt_address = NULL; 81 | static const char *opt_device = "nrf52"; 82 | static int opt_if = 1; // SWD 83 | static unsigned opt_speed = 4000; 84 | static const char *opt_buffer = "Terminal"; 85 | static const char *opt_link = NULL; 86 | static int opt_bidir = 0; 87 | static const char *opt_jlinkarm = NULL; 88 | static int opt_help = 0; 89 | static int opt_printbufs = 0; 90 | 91 | static bool jlink_opened = false; 92 | 93 | static bool do_exit = false; 94 | 95 | static void *try_dlopen_jlinkarm(void) 96 | { 97 | static const char *dir[] = { 98 | "/usr/bin", 99 | "/opt/SEGGER/JLink", 100 | }; 101 | static const char *file[] = { 102 | "libjlinkarm.so", 103 | "libjlinkarm.so.6", 104 | }; 105 | char fname[100]; 106 | void *so = NULL; 107 | int i, j; 108 | 109 | for (i = 0; so == NULL && i < sizeof(dir) / sizeof(dir[0]); i++) { 110 | for (j = 0; so == NULL && j < sizeof(file) / sizeof(file[0]); j++) { 111 | snprintf(fname, sizeof(fname), "%s/%s", dir[i], file[j]); 112 | fname[sizeof(fname) - 1] = '\0'; 113 | 114 | so = dlopen(fname, RTLD_LAZY); 115 | if (so) { 116 | printf("Using jlinkarm found at %s\n", fname); 117 | } 118 | } 119 | } 120 | 121 | return so; 122 | } 123 | 124 | static int load_jlinkarm(void) 125 | { 126 | void *so; 127 | 128 | if (opt_jlinkarm) { 129 | so = dlopen(opt_jlinkarm, RTLD_LAZY); 130 | } else { 131 | so = try_dlopen_jlinkarm(); 132 | } 133 | 134 | if (!so) { 135 | fprintf(stderr, "Failed to open jlinkarm (%s)\n", dlerror()); 136 | return -1; 137 | } 138 | 139 | jlink_emu_selectbyusbsn = dlsym(so, "JLINK_EMU_SelectByUSBSN"); 140 | jlink_open = dlsym(so, "JLINK_Open"); 141 | jlink_execcommand = dlsym(so, "JLINK_ExecCommand"); 142 | jlink_tif_select = dlsym(so, "JLINK_TIF_Select"); 143 | jlink_setspeed = dlsym(so, "JLINK_SetSpeed"); 144 | jlink_connect = dlsym(so, "JLINK_Connect"); 145 | jlink_close = dlsym(so, "JLINK_Close"); 146 | jlink_getsn = dlsym(so, "JLINK_GetSN"); 147 | jlink_emu_getproductname = dlsym(so, "JLINK_EMU_GetProductName"); 148 | jlink_rtterminal_control = dlsym(so, "JLINK_RTTERMINAL_Control"); 149 | jlink_rtterminal_read = dlsym(so, "JLINK_RTTERMINAL_Read"); 150 | jlink_rtterminal_write = dlsym(so, "JLINK_RTTERMINAL_Write"); 151 | 152 | if (!jlink_emu_selectbyusbsn || !jlink_open || !jlink_execcommand || 153 | !jlink_tif_select || !jlink_setspeed || !jlink_connect || 154 | !jlink_getsn || !jlink_emu_getproductname || 155 | !jlink_rtterminal_control || !jlink_rtterminal_read || 156 | !jlink_rtterminal_write) { 157 | fprintf(stderr, "Failed to initialize jlinkarm\n"); 158 | return -1; 159 | } 160 | 161 | return 0; 162 | } 163 | 164 | static int connect_jlink(void) 165 | { 166 | unsigned sn; 167 | char buf[1024]; 168 | 169 | if (opt_sn) { 170 | if (jlink_emu_selectbyusbsn(opt_sn) < 0) { 171 | fprintf(stderr, "Failed to select emu\n"); 172 | return -1; 173 | } 174 | } 175 | 176 | if (jlink_open()) { 177 | fprintf(stderr, "Failed to open J-Link\n"); 178 | return -1; 179 | } 180 | 181 | jlink_opened = true; 182 | 183 | snprintf(buf, sizeof(buf), "device=%s", opt_device); 184 | if (jlink_execcommand(buf, NULL, 0)) { 185 | fprintf(stderr, "Failed to setup J-Link\n"); 186 | return -1; 187 | } 188 | 189 | if (jlink_tif_select(opt_if)) { 190 | fprintf(stderr, "Failed to setup J-Link\n"); 191 | return -1; 192 | } 193 | 194 | jlink_setspeed(opt_speed); 195 | 196 | if (jlink_connect()) { 197 | fprintf(stderr, "Failed to connect J-Link\n"); 198 | return -1; 199 | } 200 | 201 | sn = jlink_getsn(); 202 | jlink_emu_getproductname(buf, sizeof(buf)); 203 | 204 | printf("Connected to:\n"); 205 | printf(" %s\n", buf); 206 | printf(" S/N: %u\n", sn); 207 | 208 | return 0; 209 | } 210 | 211 | static void print_buffers(int direction) 212 | { 213 | struct rtt_desc desc; 214 | int count; 215 | int index; 216 | 217 | do { 218 | usleep(100); 219 | count = jlink_rtterminal_control(RTT_CONTROL_GET_NUM_BUF, &direction); 220 | } while (!do_exit && (count < 0)); 221 | 222 | for (index = 0; index < count; index++) { 223 | int rc; 224 | 225 | desc.index = index; 226 | desc.direction = direction; 227 | 228 | rc = jlink_rtterminal_control(RTT_CONTROL_GET_DESC, &desc); 229 | if (rc) { 230 | continue; 231 | } 232 | printf(" #%d %s (size=%d)\n", desc.index, desc.name, desc.size); 233 | } 234 | } 235 | 236 | static int find_buffer(const char *name, int direction, struct rtt_desc *desc) 237 | { 238 | int count; 239 | int index; 240 | 241 | do { 242 | usleep(100); 243 | count = jlink_rtterminal_control(RTT_CONTROL_GET_NUM_BUF, &direction); 244 | } while (!do_exit && (count < 0)); 245 | 246 | for (index = 0; index < count; index++) { 247 | int rc; 248 | 249 | memset(desc, 0, sizeof(*desc)); 250 | desc->index = index; 251 | desc->direction = direction; 252 | 253 | rc = jlink_rtterminal_control(RTT_CONTROL_GET_DESC, desc); 254 | if (rc) { 255 | continue; 256 | } 257 | 258 | if (desc->size > 0 && !strcmp(name, desc->name)) { 259 | break; 260 | } 261 | } 262 | 263 | if (index == count) { 264 | return -1; 265 | } 266 | 267 | return index; 268 | } 269 | 270 | static int configure_rtt(void) 271 | { 272 | char *range_size; 273 | char cmd[128]; 274 | 275 | if (opt_address && strlen(opt_address)) { 276 | range_size = strchr(opt_address, ','); 277 | if (range_size) { 278 | *range_size++ = '\0'; 279 | if (strlen(range_size) == 0) { 280 | range_size = "0x1000"; 281 | } 282 | snprintf(cmd, sizeof(cmd), "SetRTTSearchRanges %s %s", opt_address, range_size); 283 | } else { 284 | snprintf(cmd, sizeof(cmd), "SetRTTAddr %s", opt_address); 285 | } 286 | 287 | cmd[sizeof(cmd) - 1] = '\0'; 288 | 289 | if (jlink_execcommand(cmd, NULL, 0)) { 290 | fprintf(stderr, "Warning: failed to set RTT control block search range\n"); 291 | return -1; 292 | } 293 | } 294 | 295 | if (jlink_rtterminal_control(RTT_CONTROL_START, NULL)) { 296 | fprintf(stderr, "Failed to initialize RTT\n"); 297 | return -1; 298 | } 299 | 300 | return 0; 301 | } 302 | 303 | static void cleanup_jlink(void) 304 | { 305 | if (jlink_opened) { 306 | if (jlink_close()) { 307 | fprintf(stderr, "Failed to close J-Link\n"); 308 | } 309 | } 310 | } 311 | 312 | static int find_buffers(int *index_up, int *index_down) 313 | { 314 | struct rtt_desc desc; 315 | int index; 316 | 317 | index = find_buffer(opt_buffer, RTT_DIRECTION_UP, &desc); 318 | if (index < 0) { 319 | fprintf(stderr, "Failed to find matching up-buffer\n"); 320 | return -1; 321 | } else { 322 | printf("Using up-buffer #%d (size=%d)\n", index, desc.size); 323 | *index_up = index; 324 | } 325 | 326 | if (!opt_bidir) { 327 | return 0; 328 | } 329 | 330 | index = find_buffer(opt_buffer, RTT_DIRECTION_DOWN, &desc); 331 | if (index < 0) { 332 | fprintf(stderr, "Failed to find matching down-buffer\n"); 333 | return -1; 334 | } else { 335 | printf("Using down-buffer #%d (size=%d)\n", index, desc.size); 336 | *index_down = index; 337 | } 338 | 339 | return 0; 340 | } 341 | 342 | static int open_pty(void) 343 | { 344 | struct termios tio; 345 | int fdm; 346 | 347 | if (opt_bidir) { 348 | fdm = posix_openpt(O_RDWR); 349 | } else { 350 | fdm = posix_openpt(O_WRONLY); 351 | } 352 | if (fdm < 0) { 353 | perror("Failed to open pty"); 354 | return -1; 355 | } 356 | 357 | if (tcgetattr(fdm, &tio) == 0) { 358 | tio.c_lflag &= ~ECHO; 359 | tcsetattr(fdm, TCSAFLUSH, &tio); 360 | } 361 | 362 | if (grantpt(fdm) < 0) { 363 | close(fdm); 364 | perror("Failed to configure pty"); 365 | return -1; 366 | } 367 | 368 | if (unlockpt(fdm) < 0) { 369 | close(fdm); 370 | perror("Failed to unlock pty"); 371 | return -1; 372 | } 373 | 374 | printf("PTY name is %s\n", ptsname(fdm)); 375 | fflush(stdout); 376 | 377 | if (opt_link != NULL) { 378 | if (symlink(ptsname(fdm), opt_link) < 0) { 379 | close(fdm); 380 | perror("Failed to create symlink"); 381 | return -1; 382 | } 383 | 384 | printf("Created a symlink %s -> %s\n", opt_link, ptsname(fdm)); 385 | } 386 | 387 | return fdm; 388 | } 389 | 390 | static void sig_handler(int signum) 391 | { 392 | do_exit = true; 393 | } 394 | 395 | int main(int argc, char **argv) 396 | { 397 | int pfd; 398 | int index_up; 399 | int index_down; 400 | int ret; 401 | 402 | for (;;) { 403 | int opt; 404 | 405 | opt = getopt_long(argc, argv, "a:d:i:l:ps:S:b:2j:h:", options, NULL); 406 | if (opt < 0) 407 | break; 408 | 409 | switch (opt) { 410 | case 'a': 411 | opt_address = optarg; 412 | break; 413 | case 'd': 414 | opt_device = optarg; 415 | break; 416 | case 'i': 417 | // TODO: not yet supported... 418 | break; 419 | case 'l': 420 | opt_link = optarg; 421 | break; 422 | case 'p': 423 | opt_printbufs = 1; 424 | break; 425 | case 's': 426 | opt_sn = atoi(optarg); 427 | break; 428 | case 'S': 429 | opt_speed = atoi(optarg); 430 | break; 431 | case 'b': 432 | opt_buffer = optarg; 433 | break; 434 | case '2': 435 | opt_bidir = 1; 436 | break; 437 | case 'j': 438 | opt_jlinkarm = optarg; 439 | break; 440 | case 'h': 441 | opt_help = 1; 442 | break; 443 | default: 444 | return EXIT_FAILURE; 445 | } 446 | } 447 | 448 | if (opt_help) { 449 | printf("rtt2pty: Segger RTT to PTY bridge\n"); 450 | printf("rtt2pty [-opt ]\n"); 451 | printf("\nOptions:\n"); 452 | printf("\t-d Segger device name\n"); 453 | printf("\t-s J-Link serial number\n"); 454 | printf("\t-S SWD/JTAG speed\n"); 455 | printf("\t-b Buffer name\n"); 456 | printf("\t-2 Enable bi-directional comms\n"); 457 | printf("\t-j libjlinkarm.so/dylib location\n"); 458 | printf("\t-p Print list of available buffers\n"); 459 | return EXIT_SUCCESS; 460 | } 461 | 462 | if (load_jlinkarm() < 0) { 463 | return EXIT_FAILURE; 464 | } 465 | 466 | if (connect_jlink() < 0) { 467 | cleanup_jlink(); 468 | return EXIT_FAILURE; 469 | } 470 | 471 | signal(SIGINT, sig_handler); 472 | signal(SIGTERM, sig_handler); 473 | signal(SIGQUIT, sig_handler); 474 | 475 | ret = configure_rtt(); 476 | if (ret < 0) { 477 | cleanup_jlink(); 478 | return EXIT_FAILURE; 479 | } 480 | 481 | printf("Searching for RTT control block...\n"); 482 | 483 | if (opt_printbufs) { 484 | printf("Up-buffers:\n"); 485 | print_buffers(RTT_DIRECTION_UP); 486 | printf("Down-buffers:\n"); 487 | print_buffers(RTT_DIRECTION_DOWN); 488 | cleanup_jlink(); 489 | return EXIT_SUCCESS; 490 | } 491 | 492 | ret = find_buffers(&index_up, &index_down); 493 | if (ret < 0) { 494 | cleanup_jlink(); 495 | return EXIT_FAILURE; 496 | } 497 | 498 | pfd = open_pty(); 499 | if (pfd < 0) { 500 | cleanup_jlink(); 501 | return EXIT_FAILURE; 502 | } 503 | 504 | while (!do_exit) { 505 | char buf[4096]; 506 | 507 | ret = jlink_rtterminal_read(index_up, buf, sizeof(buf)); 508 | if (ret > 0) { 509 | write(pfd, buf, ret); 510 | } else if (opt_bidir) { 511 | struct timeval tmo; 512 | fd_set rfds; 513 | 514 | tmo.tv_sec = 0; 515 | tmo.tv_usec = 100; 516 | 517 | FD_ZERO(&rfds); 518 | FD_SET(pfd, &rfds); 519 | 520 | ret = select(pfd + 1, &rfds, NULL, NULL, &tmo); 521 | 522 | if (ret > 0 && FD_ISSET(pfd, &rfds)) { 523 | ret = read(pfd, buf, sizeof(buf)); 524 | if (ret > 0) { 525 | jlink_rtterminal_write(index_down, buf, ret); 526 | } 527 | } 528 | } else { 529 | usleep(100); 530 | } 531 | } 532 | 533 | cleanup_jlink(); 534 | 535 | if (opt_link != NULL && remove(opt_link) < 0) { 536 | perror("Failed to remove symlink"); 537 | } 538 | 539 | close(pfd); 540 | 541 | return EXIT_SUCCESS; 542 | } 543 | --------------------------------------------------------------------------------