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

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)