f10d12073668aaa18af54f3ff274a838785e5db8
[openocd.git] / src / jtag / drivers / ti_icdi_usb.c
1 /***************************************************************************
2 * *
3 * Copyright (C) 2012 by Spencer Oliver *
4 * spen@spen-soft.co.uk *
5 * *
6 * This program is free software; you can redistribute it and/or modify *
7 * it under the terms of the GNU General Public License as published by *
8 * the Free Software Foundation; either version 2 of the License, or *
9 * (at your option) any later version. *
10 * *
11 * This program 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 *
14 * GNU General Public License for more details. *
15 * *
16 * You should have received a copy of the GNU General Public License *
17 * along with this program; if not, write to the *
18 * Free Software Foundation, Inc., *
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
20 ***************************************************************************/
21
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25
26 /* project specific includes */
27 #include <helper/binarybuffer.h>
28 #include <jtag/interface.h>
29 #include <jtag/hla/hla_layout.h>
30 #include <jtag/hla/hla_transport.h>
31 #include <jtag/hla/hla_interface.h>
32 #include <target/target.h>
33
34 #include <target/cortex_m.h>
35
36 #include <libusb.h>
37
38 #define ICDI_WRITE_ENDPOINT 0x02
39 #define ICDI_READ_ENDPOINT 0x83
40
41 #define ICDI_WRITE_TIMEOUT 1000
42 #define ICDI_READ_TIMEOUT 1000
43 #define ICDI_PACKET_SIZE 2048
44
45 #define PACKET_START "$"
46 #define PACKET_END "#"
47
48 struct icdi_usb_handle_s {
49 libusb_context *usb_ctx;
50 libusb_device_handle *usb_dev;
51
52 char *read_buffer;
53 char *write_buffer;
54 int max_packet;
55 int read_count;
56 };
57
58 static int icdi_usb_read_mem32(void *handle, uint32_t addr, uint16_t len, uint8_t *buffer);
59 static int icdi_usb_write_mem32(void *handle, uint32_t addr, uint16_t len, const uint8_t *buffer);
60
61 static int remote_escape_output(const char *buffer, int len, char *out_buf, int *out_len, int out_maxlen)
62 {
63 int input_index, output_index;
64
65 output_index = 0;
66
67 for (input_index = 0; input_index < len; input_index++) {
68
69 char b = buffer[input_index];
70
71 if (b == '$' || b == '#' || b == '}' || b == '*') {
72 /* These must be escaped. */
73 if (output_index + 2 > out_maxlen)
74 break;
75 out_buf[output_index++] = '}';
76 out_buf[output_index++] = b ^ 0x20;
77 } else {
78 if (output_index + 1 > out_maxlen)
79 break;
80 out_buf[output_index++] = b;
81 }
82 }
83
84 *out_len = input_index;
85 return output_index;
86 }
87
88 static int remote_unescape_input(const char *buffer, int len, char *out_buf, int out_maxlen)
89 {
90 int input_index, output_index;
91 int escaped;
92
93 output_index = 0;
94 escaped = 0;
95
96 for (input_index = 0; input_index < len; input_index++) {
97
98 char b = buffer[input_index];
99
100 if (output_index + 1 > out_maxlen)
101 LOG_ERROR("Received too much data from the target.");
102
103 if (escaped) {
104 out_buf[output_index++] = b ^ 0x20;
105 escaped = 0;
106 } else if (b == '}')
107 escaped = 1;
108 else
109 out_buf[output_index++] = b;
110 }
111
112 if (escaped)
113 LOG_ERROR("Unmatched escape character in target response.");
114
115 return output_index;
116 }
117
118 static int icdi_send_packet(void *handle, int len)
119 {
120 unsigned char cksum = 0;
121 struct icdi_usb_handle_s *h;
122 int result, retry = 0;
123 int transferred = 0;
124
125 assert(handle != NULL);
126 h = (struct icdi_usb_handle_s *)handle;
127
128 /* check we have a large enough buffer for checksum "#00" */
129 if (len + 3 > h->max_packet) {
130 LOG_ERROR("packet buffer too small");
131 return ERROR_FAIL;
132 }
133
134 /* calculate checksum - offset start of packet */
135 for (int i = 1; i < len; i++)
136 cksum += h->write_buffer[i];
137
138 len += sprintf(&h->write_buffer[len], PACKET_END "%02x", cksum);
139
140 #ifdef _DEBUG_USB_COMMS_
141 char buffer[50];
142 char ch = h->write_buffer[1];
143 if (ch == 'x' || ch == 'X')
144 LOG_DEBUG("writing packet: <binary>");
145 else {
146 memcpy(buffer, h->write_buffer, len >= 50 ? 50-1 : len);
147 buffer[len] = 0;
148 LOG_DEBUG("writing packet: %s", buffer);
149 }
150 #endif
151
152 while (1) {
153
154 result = libusb_bulk_transfer(h->usb_dev, ICDI_WRITE_ENDPOINT, (unsigned char *)h->write_buffer, len,
155 &transferred, ICDI_WRITE_TIMEOUT);
156 if (result != 0 || transferred != len) {
157 LOG_DEBUG("Error TX Data %d", result);
158 return ERROR_FAIL;
159 }
160
161 /* check that the client got the message ok, or shall we resend */
162 result = libusb_bulk_transfer(h->usb_dev, ICDI_READ_ENDPOINT, (unsigned char *)h->read_buffer, h->max_packet,
163 &transferred, ICDI_READ_TIMEOUT);
164 if (result != 0 || transferred < 1) {
165 LOG_DEBUG("Error RX Data %d", result);
166 return ERROR_FAIL;
167 }
168
169 #ifdef _DEBUG_USB_COMMS_
170 LOG_DEBUG("received reply: '%c' : count %d", h->read_buffer[0], transferred);
171 #endif
172
173 if (h->read_buffer[0] == '-') {
174 LOG_DEBUG("Resending packet %d", ++retry);
175 } else {
176 if (h->read_buffer[0] != '+')
177 LOG_DEBUG("Unexpected Reply from ICDI: %c", h->read_buffer[0]);
178 break;
179 }
180
181 if (retry == 3) {
182 LOG_DEBUG("maximum nack retries attempted");
183 return ERROR_FAIL;
184 }
185 }
186
187 retry = 0;
188 h->read_count = transferred;
189
190 while (1) {
191
192 /* read reply from icdi */
193 result = libusb_bulk_transfer(h->usb_dev, ICDI_READ_ENDPOINT, (unsigned char *)h->read_buffer + h->read_count,
194 h->max_packet - h->read_count, &transferred, ICDI_READ_TIMEOUT);
195
196 #ifdef _DEBUG_USB_COMMS_
197 LOG_DEBUG("received data: count %d", transferred);
198 #endif
199
200 /* check for errors but retry for timeout */
201 if (result != 0) {
202
203 if (result == LIBUSB_ERROR_TIMEOUT) {
204 LOG_DEBUG("Error RX timeout %d", result);
205 } else {
206 LOG_DEBUG("Error RX Data %d", result);
207 return ERROR_FAIL;
208 }
209 }
210
211 h->read_count += transferred;
212
213 /* we need to make sure we have a full packet, including checksum */
214 if (h->read_count > 5) {
215
216 /* check that we have received an packet delimiter
217 * we do not validate the checksum
218 * reply should contain $...#AA - so we check for # */
219 if (h->read_buffer[h->read_count - 3] == '#')
220 return ERROR_OK;
221 }
222
223 if (retry++ == 3) {
224 LOG_DEBUG("maximum data retries attempted");
225 break;
226 }
227 }
228
229 return ERROR_FAIL;
230 }
231
232 static int icdi_send_cmd(void *handle, const char *cmd)
233 {
234 struct icdi_usb_handle_s *h;
235 h = (struct icdi_usb_handle_s *)handle;
236
237 int cmd_len = snprintf(h->write_buffer, h->max_packet, PACKET_START "%s", cmd);
238 return icdi_send_packet(handle, cmd_len);
239 }
240
241 static int icdi_send_remote_cmd(void *handle, const char *data)
242 {
243 struct icdi_usb_handle_s *h;
244 h = (struct icdi_usb_handle_s *)handle;
245
246 size_t cmd_len = sprintf(h->write_buffer, PACKET_START "qRcmd,");
247 cmd_len += hexify(h->write_buffer + cmd_len, data, 0, h->max_packet - cmd_len);
248
249 return icdi_send_packet(handle, cmd_len);
250 }
251
252 static int icdi_get_cmd_result(void *handle)
253 {
254 struct icdi_usb_handle_s *h;
255 int offset = 0;
256 char ch;
257
258 assert(handle != NULL);
259 h = (struct icdi_usb_handle_s *)handle;
260
261 do {
262 ch = h->read_buffer[offset++];
263 if (offset > h->read_count)
264 return ERROR_FAIL;
265 } while (ch != '$');
266
267 if (memcmp("OK", h->read_buffer + offset, 2) == 0)
268 return ERROR_OK;
269
270 if (h->read_buffer[offset] == 'E') {
271 /* get error code */
272 char result;
273 if (unhexify(&result, h->read_buffer + offset + 1, 1) != 1)
274 return ERROR_FAIL;
275 return result;
276 }
277
278 /* for now we assume everything else is ok */
279 return ERROR_OK;
280 }
281
282 static int icdi_usb_idcode(void *handle, uint32_t *idcode)
283 {
284 return ERROR_OK;
285 }
286
287 static int icdi_usb_write_debug_reg(void *handle, uint32_t addr, uint32_t val)
288 {
289 return icdi_usb_write_mem32(handle, addr, 1, (uint8_t *)&val);
290 }
291
292 static enum target_state icdi_usb_state(void *handle)
293 {
294 int result;
295 struct icdi_usb_handle_s *h;
296 uint32_t dhcsr;
297
298 h = (struct icdi_usb_handle_s *)handle;
299
300 result = icdi_usb_read_mem32(h, DCB_DHCSR, 1, (uint8_t *)&dhcsr);
301 if (result != ERROR_OK)
302 return TARGET_UNKNOWN;
303
304 if (dhcsr & S_HALT)
305 return TARGET_HALTED;
306
307 return TARGET_RUNNING;
308 }
309
310 static int icdi_usb_version(void *handle)
311 {
312 struct icdi_usb_handle_s *h;
313 h = (struct icdi_usb_handle_s *)handle;
314
315 char version[20];
316
317 /* get info about icdi */
318 int result = icdi_send_remote_cmd(handle, "version");
319 if (result != ERROR_OK)
320 return result;
321
322 if (h->read_count < 8) {
323 LOG_ERROR("Invalid Reply Received");
324 return ERROR_FAIL;
325 }
326
327 /* convert reply */
328 if (unhexify(version, h->read_buffer + 2, 4) != 4) {
329 LOG_WARNING("unable to get ICDI version");
330 return ERROR_OK;
331 }
332
333 /* null terminate and print info */
334 version[4] = 0;
335
336 LOG_INFO("ICDI Firmware version: %s", version);
337
338 return ERROR_OK;
339 }
340
341 static int icdi_usb_query(void *handle)
342 {
343 int result;
344
345 struct icdi_usb_handle_s *h;
346 h = (struct icdi_usb_handle_s *)handle;
347
348 result = icdi_send_cmd(handle, "qSupported");
349 if (result != ERROR_OK)
350 return result;
351
352 /* check result */
353 result = icdi_get_cmd_result(handle);
354 if (result != ERROR_OK) {
355 LOG_ERROR("query supported failed: 0x%x", result);
356 return ERROR_FAIL;
357 }
358
359 /* from this we can get the max packet supported */
360
361 /* query packet buffer size */
362 char *offset = strstr(h->read_buffer, "PacketSize");
363 if (offset) {
364 char *separator;
365 int max_packet;
366
367 max_packet = strtoul(offset + 11, &separator, 16);
368 if (!max_packet)
369 LOG_ERROR("invalid max packet, using defaults");
370 else
371 h->max_packet = max_packet;
372 LOG_DEBUG("max packet supported : %" PRIu32 " bytes", h->max_packet);
373 }
374
375
376 /* if required re allocate packet buffer */
377 if (h->max_packet != ICDI_PACKET_SIZE) {
378 h->read_buffer = realloc(h->read_buffer, h->max_packet);
379 h->write_buffer = realloc(h->write_buffer, h->max_packet);
380 if (h->read_buffer == 0 || h->write_buffer == 0) {
381 LOG_ERROR("unable to reallocate memory");
382 return ERROR_FAIL;
383 }
384 }
385
386 /* set extended mode */
387 result = icdi_send_cmd(handle, "!");
388 if (result != ERROR_OK)
389 return result;
390
391 /* check result */
392 result = icdi_get_cmd_result(handle);
393 if (result != ERROR_OK) {
394 LOG_ERROR("unable to enable extended mode: 0x%x", result);
395 return ERROR_FAIL;
396 }
397
398 return ERROR_OK;
399 }
400
401 static int icdi_usb_reset(void *handle)
402 {
403 /* we do this in hla_target.c */
404 return ERROR_OK;
405 }
406
407 static int icdi_usb_assert_srst(void *handle, int srst)
408 {
409 /* TODO not supported yet */
410 return ERROR_COMMAND_NOTFOUND;
411 }
412
413 static int icdi_usb_run(void *handle)
414 {
415 int result;
416
417 /* resume target at current address */
418 result = icdi_send_cmd(handle, "c");
419 if (result != ERROR_OK)
420 return result;
421
422 /* check result */
423 result = icdi_get_cmd_result(handle);
424 if (result != ERROR_OK) {
425 LOG_ERROR("continue failed: 0x%x", result);
426 return ERROR_FAIL;
427 }
428
429 return result;
430 }
431
432 static int icdi_usb_halt(void *handle)
433 {
434 int result;
435
436 /* this query halts the target ?? */
437 result = icdi_send_cmd(handle, "?");
438 if (result != ERROR_OK)
439 return result;
440
441 /* check result */
442 result = icdi_get_cmd_result(handle);
443 if (result != ERROR_OK) {
444 LOG_ERROR("halt failed: 0x%x", result);
445 return ERROR_FAIL;
446 }
447
448 return result;
449 }
450
451 static int icdi_usb_step(void *handle)
452 {
453 int result;
454
455 /* step target at current address */
456 result = icdi_send_cmd(handle, "s");
457 if (result != ERROR_OK)
458 return result;
459
460 /* check result */
461 result = icdi_get_cmd_result(handle);
462 if (result != ERROR_OK) {
463 LOG_ERROR("step failed: 0x%x", result);
464 return ERROR_FAIL;
465 }
466
467 return result;
468 }
469
470 static int icdi_usb_read_regs(void *handle)
471 {
472 /* currently unsupported */
473 return ERROR_OK;
474 }
475
476 static int icdi_usb_read_reg(void *handle, int num, uint32_t *val)
477 {
478 int result;
479 struct icdi_usb_handle_s *h;
480 char cmd[10];
481
482 h = (struct icdi_usb_handle_s *)handle;
483
484 snprintf(cmd, sizeof(cmd), "p%x", num);
485 result = icdi_send_cmd(handle, cmd);
486 if (result != ERROR_OK)
487 return result;
488
489 /* check result */
490 result = icdi_get_cmd_result(handle);
491 if (result != ERROR_OK) {
492 LOG_ERROR("register read failed: 0x%x", result);
493 return ERROR_FAIL;
494 }
495
496 /* convert result */
497 if (unhexify((char *)val, h->read_buffer + 2, 4) != 4) {
498 LOG_ERROR("failed to convert result");
499 return ERROR_FAIL;
500 }
501
502 return result;
503 }
504
505 static int icdi_usb_write_reg(void *handle, int num, uint32_t val)
506 {
507 int result;
508 char cmd[20];
509
510 int cmd_len = snprintf(cmd, sizeof(cmd), "P%x=", num);
511 hexify(cmd + cmd_len, (char *)&val, 4, sizeof(cmd));
512
513 result = icdi_send_cmd(handle, cmd);
514 if (result != ERROR_OK)
515 return result;
516
517 /* check result */
518 result = icdi_get_cmd_result(handle);
519 if (result != ERROR_OK) {
520 LOG_ERROR("register write failed: 0x%x", result);
521 return ERROR_FAIL;
522 }
523
524 return result;
525 }
526
527 static int icdi_usb_read_mem(void *handle, uint32_t addr, uint32_t len, uint8_t *buffer)
528 {
529 int result;
530 struct icdi_usb_handle_s *h;
531 char cmd[20];
532
533 h = (struct icdi_usb_handle_s *)handle;
534
535 snprintf(cmd, sizeof(cmd), "x%x,%x", addr, len);
536 result = icdi_send_cmd(handle, cmd);
537 if (result != ERROR_OK)
538 return result;
539
540 /* check result */
541 result = icdi_get_cmd_result(handle);
542 if (result != ERROR_OK) {
543 LOG_ERROR("memory read failed: 0x%x", result);
544 return ERROR_FAIL;
545 }
546
547 /* unescape input */
548 int read_len = remote_unescape_input(h->read_buffer + 5, h->read_count - 8, (char *)buffer, len);
549 if (read_len != (int)len) {
550 LOG_ERROR("read more bytes than expected: actual 0x%" PRIx32 " expected 0x%" PRIx32, read_len, len);
551 return ERROR_FAIL;
552 }
553
554 return ERROR_OK;
555 }
556
557 static int icdi_usb_write_mem(void *handle, uint32_t addr, uint32_t len, const uint8_t *buffer)
558 {
559 int result;
560 struct icdi_usb_handle_s *h;
561
562 h = (struct icdi_usb_handle_s *)handle;
563
564 size_t cmd_len = snprintf(h->write_buffer, h->max_packet, PACKET_START "X%x,%x:", addr, len);
565
566 int out_len;
567 cmd_len += remote_escape_output((char *)buffer, len, h->write_buffer + cmd_len,
568 &out_len, h->max_packet - cmd_len);
569
570 if (out_len < (int)len) {
571 /* for now issue a error as we have no way of allocating a larger buffer */
572 LOG_ERROR("memory buffer too small: requires 0x%" PRIx32 " actual 0x%" PRIx32, out_len, len);
573 return ERROR_FAIL;
574 }
575
576 result = icdi_send_packet(handle, cmd_len);
577 if (result != ERROR_OK)
578 return result;
579
580 /* check result */
581 result = icdi_get_cmd_result(handle);
582 if (result != ERROR_OK) {
583 LOG_ERROR("memory write failed: 0x%x", result);
584 return ERROR_FAIL;
585 }
586
587 return ERROR_OK;
588 }
589
590 static int icdi_usb_read_mem8(void *handle, uint32_t addr, uint16_t len, uint8_t *buffer)
591 {
592 return icdi_usb_read_mem(handle, addr, len, buffer);
593 }
594
595 static int icdi_usb_write_mem8(void *handle, uint32_t addr, uint16_t len, const uint8_t *buffer)
596 {
597 return icdi_usb_write_mem(handle, addr, len, buffer);
598 }
599
600 static int icdi_usb_read_mem32(void *handle, uint32_t addr, uint16_t len, uint8_t *buffer)
601 {
602 return icdi_usb_read_mem(handle, addr, len * 4, buffer);
603 }
604
605 static int icdi_usb_write_mem32(void *handle, uint32_t addr, uint16_t len, const uint8_t *buffer)
606 {
607 return icdi_usb_write_mem(handle, addr, len * 4, buffer);
608 }
609
610 static int icdi_usb_close(void *handle)
611 {
612 struct icdi_usb_handle_s *h;
613
614 h = (struct icdi_usb_handle_s *)handle;
615
616 if (h->usb_dev)
617 libusb_close(h->usb_dev);
618
619 if (h->usb_ctx)
620 libusb_exit(h->usb_ctx);
621
622 if (h->read_buffer)
623 free(h->read_buffer);
624
625 if (h->write_buffer)
626 free(h->write_buffer);
627
628 free(handle);
629
630 return ERROR_OK;
631 }
632
633 static int icdi_usb_open(struct hl_interface_param_s *param, void **fd)
634 {
635 int retval;
636 struct icdi_usb_handle_s *h;
637
638 LOG_DEBUG("icdi_usb_open");
639
640 h = calloc(1, sizeof(struct icdi_usb_handle_s));
641
642 if (h == 0) {
643 LOG_ERROR("unable to allocate memory");
644 return ERROR_FAIL;
645 }
646
647 LOG_DEBUG("transport: %d vid: 0x%04x pid: 0x%04x", param->transport,
648 param->vid, param->pid);
649
650 if (libusb_init(&h->usb_ctx) != 0) {
651 LOG_ERROR("libusb init failed");
652 goto error_open;
653 }
654
655 h->usb_dev = libusb_open_device_with_vid_pid(h->usb_ctx, param->vid, param->pid);
656 if (!h->usb_dev) {
657 LOG_ERROR("open failed");
658 goto error_open;
659 }
660
661 if (libusb_claim_interface(h->usb_dev, 2)) {
662 LOG_DEBUG("claim interface failed");
663 goto error_open;
664 }
665
666 /* check if mode is supported */
667 retval = ERROR_OK;
668
669 switch (param->transport) {
670 #if 0
671 /* TODO place holder as swd is not currently supported */
672 case HL_TRANSPORT_SWD:
673 #endif
674 case HL_TRANSPORT_JTAG:
675 break;
676 default:
677 retval = ERROR_FAIL;
678 break;
679 }
680
681 if (retval != ERROR_OK) {
682 LOG_ERROR("mode (transport) not supported by device");
683 goto error_open;
684 }
685
686 /* allocate buffer */
687 h->read_buffer = malloc(ICDI_PACKET_SIZE);
688 h->write_buffer = malloc(ICDI_PACKET_SIZE);
689 h->max_packet = ICDI_PACKET_SIZE;
690
691 if (h->read_buffer == 0 || h->write_buffer == 0) {
692 LOG_DEBUG("malloc failed");
693 goto error_open;
694 }
695
696 /* query icdi version etc */
697 retval = icdi_usb_version(h);
698 if (retval != ERROR_OK)
699 goto error_open;
700
701 /* query icdi support */
702 retval = icdi_usb_query(h);
703 if (retval != ERROR_OK)
704 goto error_open;
705
706 *fd = h;
707
708 /* set the max target read/write buffer in bytes
709 * as we are using gdb binary packets to transfer memory we have to
710 * reserve half the buffer for any possible escape chars plus
711 * at least 64 bytes for the gdb packet header */
712 param->max_buffer = (((h->max_packet - 64) / 4) * 4) / 2;
713
714 return ERROR_OK;
715
716 error_open:
717 icdi_usb_close(h);
718
719 return ERROR_FAIL;
720 }
721
722 struct hl_layout_api_s icdi_usb_layout_api = {
723 .open = icdi_usb_open,
724 .close = icdi_usb_close,
725 .idcode = icdi_usb_idcode,
726 .state = icdi_usb_state,
727 .reset = icdi_usb_reset,
728 .assert_srst = icdi_usb_assert_srst,
729 .run = icdi_usb_run,
730 .halt = icdi_usb_halt,
731 .step = icdi_usb_step,
732 .read_regs = icdi_usb_read_regs,
733 .read_reg = icdi_usb_read_reg,
734 .write_reg = icdi_usb_write_reg,
735 .read_mem8 = icdi_usb_read_mem8,
736 .write_mem8 = icdi_usb_write_mem8,
737 .read_mem32 = icdi_usb_read_mem32,
738 .write_mem32 = icdi_usb_write_mem32,
739 .write_debug_reg = icdi_usb_write_debug_reg
740 };

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)