8ca1183b0234e8c1595cf3b7eaef7fc7079d25d6
[openocd.git] / src / rtos / rtos.c
1 /***************************************************************************
2 * Copyright (C) 2011 by Broadcom Corporation *
3 * Evan Hunter - ehunter@broadcom.com *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program. If not, see <http://www.gnu.org/licenses/>. *
17 ***************************************************************************/
18
19 #ifdef HAVE_CONFIG_H
20 #include "config.h"
21 #endif
22
23 #include "rtos.h"
24 #include "target/target.h"
25 #include "helper/log.h"
26 #include "helper/binarybuffer.h"
27 #include "server/gdb_server.h"
28
29 /* RTOSs */
30 extern struct rtos_type FreeRTOS_rtos;
31 extern struct rtos_type ThreadX_rtos;
32 extern struct rtos_type eCos_rtos;
33 extern struct rtos_type Linux_os;
34 extern struct rtos_type ChibiOS_rtos;
35 extern struct rtos_type embKernel_rtos;
36 extern struct rtos_type mqx_rtos;
37 extern struct rtos_type uCOS_III_rtos;
38 extern struct rtos_type nuttx_rtos;
39
40 static struct rtos_type *rtos_types[] = {
41 &ThreadX_rtos,
42 &FreeRTOS_rtos,
43 &eCos_rtos,
44 &Linux_os,
45 &ChibiOS_rtos,
46 &embKernel_rtos,
47 &mqx_rtos,
48 &uCOS_III_rtos,
49 &nuttx_rtos,
50 NULL
51 };
52
53 int rtos_thread_packet(struct connection *connection, const char *packet, int packet_size);
54
55 int rtos_smp_init(struct target *target)
56 {
57 if (target->rtos->type->smp_init)
58 return target->rtos->type->smp_init(target);
59 return ERROR_TARGET_INIT_FAILED;
60 }
61
62 static int rtos_target_for_threadid(struct connection *connection, int64_t threadid, struct target **t)
63 {
64 struct target *curr = get_target_from_connection(connection);
65 if (t)
66 *t = curr;
67
68 return ERROR_OK;
69 }
70
71 static int os_alloc(struct target *target, struct rtos_type *ostype)
72 {
73 struct rtos *os = target->rtos = calloc(1, sizeof(struct rtos));
74
75 if (!os)
76 return JIM_ERR;
77
78 os->type = ostype;
79 os->current_threadid = -1;
80 os->current_thread = 0;
81 os->symbols = NULL;
82 os->target = target;
83
84 /* RTOS drivers can override the packet handler in _create(). */
85 os->gdb_thread_packet = rtos_thread_packet;
86 os->gdb_target_for_threadid = rtos_target_for_threadid;
87
88 return JIM_OK;
89 }
90
91 static void os_free(struct target *target)
92 {
93 if (!target->rtos)
94 return;
95
96 if (target->rtos->symbols)
97 free(target->rtos->symbols);
98
99 free(target->rtos);
100 target->rtos = NULL;
101 }
102
103 static int os_alloc_create(struct target *target, struct rtos_type *ostype)
104 {
105 int ret = os_alloc(target, ostype);
106
107 if (JIM_OK == ret) {
108 ret = target->rtos->type->create(target);
109 if (ret != JIM_OK)
110 os_free(target);
111 }
112
113 return ret;
114 }
115
116 int rtos_create(Jim_GetOptInfo *goi, struct target *target)
117 {
118 int x;
119 const char *cp;
120 struct Jim_Obj *res;
121 int e;
122
123 if (!goi->isconfigure && goi->argc != 0) {
124 Jim_WrongNumArgs(goi->interp, goi->argc, goi->argv, "NO PARAMS");
125 return JIM_ERR;
126 }
127
128 os_free(target);
129
130 e = Jim_GetOpt_String(goi, &cp, NULL);
131 if (e != JIM_OK)
132 return e;
133
134 if (0 == strcmp(cp, "auto")) {
135 /* Auto detect tries to look up all symbols for each RTOS,
136 * and runs the RTOS driver's _detect() function when GDB
137 * finds all symbols for any RTOS. See rtos_qsymbol(). */
138 target->rtos_auto_detect = true;
139
140 /* rtos_qsymbol() will iterate over all RTOSes. Allocate
141 * target->rtos here, and set it to the first RTOS type. */
142 return os_alloc(target, rtos_types[0]);
143 }
144
145 for (x = 0; rtos_types[x]; x++)
146 if (0 == strcmp(cp, rtos_types[x]->name))
147 return os_alloc_create(target, rtos_types[x]);
148
149 Jim_SetResultFormatted(goi->interp, "Unknown RTOS type %s, try one of: ", cp);
150 res = Jim_GetResult(goi->interp);
151 for (x = 0; rtos_types[x]; x++)
152 Jim_AppendStrings(goi->interp, res, rtos_types[x]->name, ", ", NULL);
153 Jim_AppendStrings(goi->interp, res, " or auto", NULL);
154
155 return JIM_ERR;
156 }
157
158 int gdb_thread_packet(struct connection *connection, char const *packet, int packet_size)
159 {
160 struct target *target = get_target_from_connection(connection);
161 if (target->rtos == NULL)
162 return rtos_thread_packet(connection, packet, packet_size); /* thread not
163 *found*/
164 return target->rtos->gdb_thread_packet(connection, packet, packet_size);
165 }
166
167 static symbol_table_elem_t *next_symbol(struct rtos *os, char *cur_symbol, uint64_t cur_addr)
168 {
169 symbol_table_elem_t *s;
170
171 if (!os->symbols)
172 os->type->get_symbol_list_to_lookup(&os->symbols);
173
174 if (!cur_symbol[0])
175 return &os->symbols[0];
176
177 for (s = os->symbols; s->symbol_name; s++)
178 if (!strcmp(s->symbol_name, cur_symbol)) {
179 s->address = cur_addr;
180 s++;
181 return s;
182 }
183
184 return NULL;
185 }
186
187 /* searches for 'symbol' in the lookup table for 'os' and returns TRUE,
188 * if 'symbol' is not declared optional */
189 static bool is_symbol_mandatory(const struct rtos *os, const char *symbol)
190 {
191 for (symbol_table_elem_t *s = os->symbols; s->symbol_name; ++s) {
192 if (!strcmp(s->symbol_name, symbol))
193 return !s->optional;
194 }
195 return false;
196 }
197
198 /* rtos_qsymbol() processes and replies to all qSymbol packets from GDB.
199 *
200 * GDB sends a qSymbol:: packet (empty address, empty name) to notify
201 * that it can now answer qSymbol::hexcodedname queries, to look up symbols.
202 *
203 * If the qSymbol packet has no address that means GDB did not find the
204 * symbol, in which case auto-detect will move on to try the next RTOS.
205 *
206 * rtos_qsymbol() then calls the next_symbol() helper function, which
207 * iterates over symbol names for the current RTOS until it finds the
208 * symbol in the received GDB packet, and then returns the next entry
209 * in the list of symbols.
210 *
211 * If GDB replied about the last symbol for the RTOS and the RTOS was
212 * specified explicitly, then no further symbol lookup is done. When
213 * auto-detecting, the RTOS driver _detect() function must return success.
214 *
215 * rtos_qsymbol() returns 1 if an RTOS has been detected, or 0 otherwise.
216 */
217 int rtos_qsymbol(struct connection *connection, char const *packet, int packet_size)
218 {
219 int rtos_detected = 0;
220 uint64_t addr = 0;
221 size_t reply_len;
222 char reply[GDB_BUFFER_SIZE], cur_sym[GDB_BUFFER_SIZE / 2] = "";
223 symbol_table_elem_t *next_sym = NULL;
224 struct target *target = get_target_from_connection(connection);
225 struct rtos *os = target->rtos;
226
227 reply_len = sprintf(reply, "OK");
228
229 if (!os)
230 goto done;
231
232 /* Decode any symbol name in the packet*/
233 size_t len = unhexify((uint8_t *)cur_sym, strchr(packet + 8, ':') + 1, strlen(strchr(packet + 8, ':') + 1));
234 cur_sym[len] = 0;
235
236 if ((strcmp(packet, "qSymbol::") != 0) && /* GDB is not offering symbol lookup for the first time */
237 (!sscanf(packet, "qSymbol:%" SCNx64 ":", &addr)) && /* GDB did not find an address for a symbol */
238 is_symbol_mandatory(os, cur_sym)) { /* the symbol is mandatory for this RTOS */
239
240 /* GDB could not find an address for the previous symbol */
241 if (!target->rtos_auto_detect) {
242 LOG_WARNING("RTOS %s not detected. (GDB could not find symbol \'%s\')", os->type->name, cur_sym);
243 goto done;
244 } else {
245 /* Autodetecting RTOS - try next RTOS */
246 if (!rtos_try_next(target)) {
247 LOG_WARNING("No RTOS could be auto-detected!");
248 goto done;
249 }
250
251 /* Next RTOS selected - invalidate current symbol */
252 cur_sym[0] = '\x00';
253 }
254 }
255 next_sym = next_symbol(os, cur_sym, addr);
256
257 if (!next_sym->symbol_name) {
258 /* No more symbols need looking up */
259
260 if (!target->rtos_auto_detect) {
261 rtos_detected = 1;
262 goto done;
263 }
264
265 if (os->type->detect_rtos(target)) {
266 LOG_INFO("Auto-detected RTOS: %s", os->type->name);
267 rtos_detected = 1;
268 goto done;
269 } else {
270 LOG_WARNING("No RTOS could be auto-detected!");
271 goto done;
272 }
273 }
274
275 if (8 + (strlen(next_sym->symbol_name) * 2) + 1 > sizeof(reply)) {
276 LOG_ERROR("ERROR: RTOS symbol '%s' name is too long for GDB!", next_sym->symbol_name);
277 goto done;
278 }
279
280 reply_len = snprintf(reply, sizeof(reply), "qSymbol:");
281 reply_len += hexify(reply + reply_len,
282 (const uint8_t *)next_sym->symbol_name, strlen(next_sym->symbol_name),
283 sizeof(reply) - reply_len);
284
285 done:
286 gdb_put_packet(connection, reply, reply_len);
287 return rtos_detected;
288 }
289
290 int rtos_thread_packet(struct connection *connection, char const *packet, int packet_size)
291 {
292 struct target *target = get_target_from_connection(connection);
293
294 if (strncmp(packet, "qThreadExtraInfo,", 17) == 0) {
295 if ((target->rtos != NULL) && (target->rtos->thread_details != NULL) &&
296 (target->rtos->thread_count != 0)) {
297 threadid_t threadid = 0;
298 int found = -1;
299 sscanf(packet, "qThreadExtraInfo,%" SCNx64, &threadid);
300
301 if ((target->rtos != NULL) && (target->rtos->thread_details != NULL)) {
302 int thread_num;
303 for (thread_num = 0; thread_num < target->rtos->thread_count; thread_num++) {
304 if (target->rtos->thread_details[thread_num].threadid == threadid) {
305 if (target->rtos->thread_details[thread_num].exists)
306 found = thread_num;
307 }
308 }
309 }
310 if (found == -1) {
311 gdb_put_packet(connection, "E01", 3); /* thread not found */
312 return ERROR_OK;
313 }
314
315 struct thread_detail *detail = &target->rtos->thread_details[found];
316
317 int str_size = 0;
318 if (detail->thread_name_str != NULL)
319 str_size += strlen(detail->thread_name_str);
320 if (detail->extra_info_str != NULL)
321 str_size += strlen(detail->extra_info_str);
322
323 char *tmp_str = calloc(str_size + 9, sizeof(char));
324 char *tmp_str_ptr = tmp_str;
325
326 if (detail->thread_name_str != NULL)
327 tmp_str_ptr += sprintf(tmp_str_ptr, "Name: %s", detail->thread_name_str);
328 if (detail->extra_info_str != NULL) {
329 if (tmp_str_ptr != tmp_str)
330 tmp_str_ptr += sprintf(tmp_str_ptr, ", ");
331 tmp_str_ptr += sprintf(tmp_str_ptr, "%s", detail->extra_info_str);
332 }
333
334 assert(strlen(tmp_str) ==
335 (size_t) (tmp_str_ptr - tmp_str));
336
337 char *hex_str = malloc(strlen(tmp_str) * 2 + 1);
338 size_t pkt_len = hexify(hex_str, (const uint8_t *)tmp_str,
339 strlen(tmp_str), strlen(tmp_str) * 2 + 1);
340
341 gdb_put_packet(connection, hex_str, pkt_len);
342 free(hex_str);
343 free(tmp_str);
344 return ERROR_OK;
345
346 }
347 gdb_put_packet(connection, "", 0);
348 return ERROR_OK;
349 } else if (strncmp(packet, "qSymbol", 7) == 0) {
350 if (rtos_qsymbol(connection, packet, packet_size) == 1) {
351 if (target->rtos_auto_detect == true) {
352 target->rtos_auto_detect = false;
353 target->rtos->type->create(target);
354 }
355 target->rtos->type->update_threads(target->rtos);
356 }
357 return ERROR_OK;
358 } else if (strncmp(packet, "qfThreadInfo", 12) == 0) {
359 int i;
360 if (target->rtos != NULL) {
361 if (target->rtos->thread_count == 0) {
362 gdb_put_packet(connection, "l", 1);
363 } else {
364 /*thread id are 16 char +1 for ',' */
365 char *out_str = malloc(17 * target->rtos->thread_count + 1);
366 char *tmp_str = out_str;
367 for (i = 0; i < target->rtos->thread_count; i++) {
368 tmp_str += sprintf(tmp_str, "%c%016" PRIx64, i == 0 ? 'm' : ',',
369 target->rtos->thread_details[i].threadid);
370 }
371 gdb_put_packet(connection, out_str, strlen(out_str));
372 free(out_str);
373 }
374 } else
375 gdb_put_packet(connection, "l", 1);
376
377 return ERROR_OK;
378 } else if (strncmp(packet, "qsThreadInfo", 12) == 0) {
379 gdb_put_packet(connection, "l", 1);
380 return ERROR_OK;
381 } else if (strncmp(packet, "qAttached", 9) == 0) {
382 gdb_put_packet(connection, "1", 1);
383 return ERROR_OK;
384 } else if (strncmp(packet, "qOffsets", 8) == 0) {
385 char offsets[] = "Text=0;Data=0;Bss=0";
386 gdb_put_packet(connection, offsets, sizeof(offsets)-1);
387 return ERROR_OK;
388 } else if (strncmp(packet, "qCRC:", 5) == 0) {
389 /* make sure we check this before "qC" packet below
390 * otherwise it gets incorrectly handled */
391 return GDB_THREAD_PACKET_NOT_CONSUMED;
392 } else if (strncmp(packet, "qC", 2) == 0) {
393 if (target->rtos != NULL) {
394 char buffer[19];
395 int size;
396 size = snprintf(buffer, 19, "QC%016" PRIx64, target->rtos->current_thread);
397 gdb_put_packet(connection, buffer, size);
398 } else
399 gdb_put_packet(connection, "QC0", 3);
400 return ERROR_OK;
401 } else if (packet[0] == 'T') { /* Is thread alive? */
402 threadid_t threadid;
403 int found = -1;
404 sscanf(packet, "T%" SCNx64, &threadid);
405 if ((target->rtos != NULL) && (target->rtos->thread_details != NULL)) {
406 int thread_num;
407 for (thread_num = 0; thread_num < target->rtos->thread_count; thread_num++) {
408 if (target->rtos->thread_details[thread_num].threadid == threadid) {
409 if (target->rtos->thread_details[thread_num].exists)
410 found = thread_num;
411 }
412 }
413 }
414 if (found != -1)
415 gdb_put_packet(connection, "OK", 2); /* thread alive */
416 else
417 gdb_put_packet(connection, "E01", 3); /* thread not found */
418 return ERROR_OK;
419 } else if (packet[0] == 'H') { /* Set current thread ( 'c' for step and continue, 'g' for
420 * all other operations ) */
421 if ((packet[1] == 'g') && (target->rtos != NULL)) {
422 threadid_t threadid;
423 sscanf(packet, "Hg%16" SCNx64, &threadid);
424 LOG_DEBUG("RTOS: GDB requested to set current thread to 0x%" PRIx64, threadid);
425 /* threadid of 0 indicates target should choose */
426 if (threadid == 0)
427 target->rtos->current_threadid = target->rtos->current_thread;
428 else
429 target->rtos->current_threadid = threadid;
430 }
431 gdb_put_packet(connection, "OK", 2);
432 return ERROR_OK;
433 }
434
435 return GDB_THREAD_PACKET_NOT_CONSUMED;
436 }
437
438 static int rtos_put_gdb_reg_list(struct connection *connection,
439 struct rtos_reg *reg_list, int num_regs)
440 {
441 size_t num_bytes = 1; /* NUL */
442 for (int i = 0; i < num_regs; ++i)
443 num_bytes += DIV_ROUND_UP(reg_list[i].size, 8) * 2;
444
445 char *hex = malloc(num_bytes);
446 char *hex_p = hex;
447
448 for (int i = 0; i < num_regs; ++i) {
449 size_t count = DIV_ROUND_UP(reg_list[i].size, 8);
450 size_t n = hexify(hex_p, reg_list[i].value, count, num_bytes);
451 hex_p += n;
452 num_bytes -= n;
453 }
454
455 gdb_put_packet(connection, hex, strlen(hex));
456 free(hex);
457
458 return ERROR_OK;
459 }
460
461 int rtos_get_gdb_reg(struct connection *connection, int reg_num)
462 {
463 struct target *target = get_target_from_connection(connection);
464 int64_t current_threadid = target->rtos->current_threadid;
465 if ((target->rtos != NULL) && (current_threadid != -1) &&
466 (current_threadid != 0) &&
467 ((current_threadid != target->rtos->current_thread) ||
468 (target->smp))) { /* in smp several current thread are possible */
469 struct rtos_reg *reg_list;
470 int num_regs;
471
472 LOG_DEBUG("RTOS: getting register %d for thread 0x%" PRIx64
473 ", target->rtos->current_thread=0x%" PRIx64 "\r\n",
474 reg_num,
475 current_threadid,
476 target->rtos->current_thread);
477
478 int retval = target->rtos->type->get_thread_reg_list(target->rtos,
479 current_threadid,
480 &reg_list,
481 &num_regs);
482 if (retval != ERROR_OK) {
483 LOG_ERROR("RTOS: failed to get register list");
484 return retval;
485 }
486
487 for (int i = 0; i < num_regs; ++i) {
488 if (reg_list[i].number == (uint32_t)reg_num) {
489 rtos_put_gdb_reg_list(connection, reg_list + i, 1);
490 free(reg_list);
491 return ERROR_OK;
492 }
493 }
494
495 free(reg_list);
496 }
497 return ERROR_FAIL;
498 }
499
500 int rtos_get_gdb_reg_list(struct connection *connection)
501 {
502 struct target *target = get_target_from_connection(connection);
503 int64_t current_threadid = target->rtos->current_threadid;
504 if ((target->rtos != NULL) && (current_threadid != -1) &&
505 (current_threadid != 0) &&
506 ((current_threadid != target->rtos->current_thread) ||
507 (target->smp))) { /* in smp several current thread are possible */
508 struct rtos_reg *reg_list;
509 int num_regs;
510
511 LOG_DEBUG("RTOS: getting register list for thread 0x%" PRIx64
512 ", target->rtos->current_thread=0x%" PRIx64 "\r\n",
513 current_threadid,
514 target->rtos->current_thread);
515
516 int retval = target->rtos->type->get_thread_reg_list(target->rtos,
517 current_threadid,
518 &reg_list,
519 &num_regs);
520 if (retval != ERROR_OK) {
521 LOG_ERROR("RTOS: failed to get register list");
522 return retval;
523 }
524
525 rtos_put_gdb_reg_list(connection, reg_list, num_regs);
526 free(reg_list);
527
528 return ERROR_OK;
529 }
530 return ERROR_FAIL;
531 }
532
533 int rtos_generic_stack_read(struct target *target,
534 const struct rtos_register_stacking *stacking,
535 int64_t stack_ptr,
536 struct rtos_reg **reg_list,
537 int *num_regs)
538 {
539 int retval;
540
541 if (stack_ptr == 0) {
542 LOG_ERROR("Error: null stack pointer in thread");
543 return -5;
544 }
545 /* Read the stack */
546 uint8_t *stack_data = malloc(stacking->stack_registers_size);
547 uint32_t address = stack_ptr;
548
549 if (stacking->stack_growth_direction == 1)
550 address -= stacking->stack_registers_size;
551 retval = target_read_buffer(target, address, stacking->stack_registers_size, stack_data);
552 if (retval != ERROR_OK) {
553 free(stack_data);
554 LOG_ERROR("Error reading stack frame from thread");
555 return retval;
556 }
557 LOG_DEBUG("RTOS: Read stack frame at 0x%" PRIx32, address);
558
559 #if 0
560 LOG_OUTPUT("Stack Data :");
561 for (i = 0; i < stacking->stack_registers_size; i++)
562 LOG_OUTPUT("%02X", stack_data[i]);
563 LOG_OUTPUT("\r\n");
564 #endif
565
566 int64_t new_stack_ptr;
567 if (stacking->calculate_process_stack != NULL) {
568 new_stack_ptr = stacking->calculate_process_stack(target,
569 stack_data, stacking, stack_ptr);
570 } else {
571 new_stack_ptr = stack_ptr - stacking->stack_growth_direction *
572 stacking->stack_registers_size;
573 }
574
575 *reg_list = calloc(stacking->num_output_registers, sizeof(struct rtos_reg));
576 *num_regs = stacking->num_output_registers;
577
578 for (int i = 0; i < stacking->num_output_registers; ++i) {
579 (*reg_list)[i].number = stacking->register_offsets[i].number;
580 (*reg_list)[i].size = stacking->register_offsets[i].width_bits;
581
582 int offset = stacking->register_offsets[i].offset;
583 if (offset == -2)
584 buf_cpy(&new_stack_ptr, (*reg_list)[i].value, (*reg_list)[i].size);
585 else if (offset != -1)
586 buf_cpy(stack_data + offset, (*reg_list)[i].value, (*reg_list)[i].size);
587 }
588
589 free(stack_data);
590 /* LOG_OUTPUT("Output register string: %s\r\n", *hex_reg_list); */
591 return ERROR_OK;
592 }
593
594 int rtos_try_next(struct target *target)
595 {
596 struct rtos *os = target->rtos;
597 struct rtos_type **type = rtos_types;
598
599 if (!os)
600 return 0;
601
602 while (*type && os->type != *type)
603 type++;
604
605 if (!*type || !*(++type))
606 return 0;
607
608 os->type = *type;
609 if (os->symbols) {
610 free(os->symbols);
611 os->symbols = NULL;
612 }
613
614 return 1;
615 }
616
617 int rtos_update_threads(struct target *target)
618 {
619 if ((target->rtos != NULL) && (target->rtos->type != NULL))
620 target->rtos->type->update_threads(target->rtos);
621 return ERROR_OK;
622 }
623
624 void rtos_free_threadlist(struct rtos *rtos)
625 {
626 if (rtos->thread_details) {
627 int j;
628
629 for (j = 0; j < rtos->thread_count; j++) {
630 struct thread_detail *current_thread = &rtos->thread_details[j];
631 free(current_thread->thread_name_str);
632 free(current_thread->extra_info_str);
633 }
634 free(rtos->thread_details);
635 rtos->thread_details = NULL;
636 rtos->thread_count = 0;
637 rtos->current_threadid = -1;
638 rtos->current_thread = 0;
639 }
640 }

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)