cortex_m: read and display core security state
[openocd.git] / src / rtos / FreeRTOS.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 <helper/time_support.h>
24 #include <jtag/jtag.h>
25 #include "target/target.h"
26 #include "target/target_type.h"
27 #include "rtos.h"
28 #include "helper/log.h"
29 #include "helper/types.h"
30 #include "rtos_standard_stackings.h"
31 #include "target/armv7m.h"
32 #include "target/cortex_m.h"
33
34
35
36 #define FREERTOS_MAX_PRIORITIES 63
37
38 #define FreeRTOS_STRUCT(int_type, ptr_type, list_prev_offset)
39
40 struct FreeRTOS_params {
41 const char *target_name;
42 const unsigned char thread_count_width;
43 const unsigned char pointer_width;
44 const unsigned char list_next_offset;
45 const unsigned char list_width;
46 const unsigned char list_elem_next_offset;
47 const unsigned char list_elem_content_offset;
48 const unsigned char thread_stack_offset;
49 const unsigned char thread_name_offset;
50 const struct rtos_register_stacking *stacking_info_cm3;
51 const struct rtos_register_stacking *stacking_info_cm4f;
52 const struct rtos_register_stacking *stacking_info_cm4f_fpu;
53 };
54
55 static const struct FreeRTOS_params FreeRTOS_params_list[] = {
56 {
57 "cortex_m", /* target_name */
58 4, /* thread_count_width; */
59 4, /* pointer_width; */
60 16, /* list_next_offset; */
61 20, /* list_width; */
62 8, /* list_elem_next_offset; */
63 12, /* list_elem_content_offset */
64 0, /* thread_stack_offset; */
65 52, /* thread_name_offset; */
66 &rtos_standard_Cortex_M3_stacking, /* stacking_info */
67 &rtos_standard_Cortex_M4F_stacking,
68 &rtos_standard_Cortex_M4F_FPU_stacking,
69 },
70 {
71 "hla_target", /* target_name */
72 4, /* thread_count_width; */
73 4, /* pointer_width; */
74 16, /* list_next_offset; */
75 20, /* list_width; */
76 8, /* list_elem_next_offset; */
77 12, /* list_elem_content_offset */
78 0, /* thread_stack_offset; */
79 52, /* thread_name_offset; */
80 &rtos_standard_Cortex_M3_stacking, /* stacking_info */
81 &rtos_standard_Cortex_M4F_stacking,
82 &rtos_standard_Cortex_M4F_FPU_stacking,
83 },
84 {
85 "nds32_v3", /* target_name */
86 4, /* thread_count_width; */
87 4, /* pointer_width; */
88 16, /* list_next_offset; */
89 20, /* list_width; */
90 8, /* list_elem_next_offset; */
91 12, /* list_elem_content_offset */
92 0, /* thread_stack_offset; */
93 52, /* thread_name_offset; */
94 &rtos_standard_NDS32_N1068_stacking, /* stacking_info */
95 &rtos_standard_Cortex_M4F_stacking,
96 &rtos_standard_Cortex_M4F_FPU_stacking,
97 },
98 };
99
100 #define FREERTOS_NUM_PARAMS ((int)(sizeof(FreeRTOS_params_list)/sizeof(struct FreeRTOS_params)))
101
102 static bool FreeRTOS_detect_rtos(struct target *target);
103 static int FreeRTOS_create(struct target *target);
104 static int FreeRTOS_update_threads(struct rtos *rtos);
105 static int FreeRTOS_get_thread_reg_list(struct rtos *rtos, int64_t thread_id,
106 struct rtos_reg **reg_list, int *num_regs);
107 static int FreeRTOS_get_symbol_list_to_lookup(symbol_table_elem_t *symbol_list[]);
108
109 struct rtos_type FreeRTOS_rtos = {
110 .name = "FreeRTOS",
111
112 .detect_rtos = FreeRTOS_detect_rtos,
113 .create = FreeRTOS_create,
114 .update_threads = FreeRTOS_update_threads,
115 .get_thread_reg_list = FreeRTOS_get_thread_reg_list,
116 .get_symbol_list_to_lookup = FreeRTOS_get_symbol_list_to_lookup,
117 };
118
119 enum FreeRTOS_symbol_values {
120 FreeRTOS_VAL_pxCurrentTCB = 0,
121 FreeRTOS_VAL_pxReadyTasksLists = 1,
122 FreeRTOS_VAL_xDelayedTaskList1 = 2,
123 FreeRTOS_VAL_xDelayedTaskList2 = 3,
124 FreeRTOS_VAL_pxDelayedTaskList = 4,
125 FreeRTOS_VAL_pxOverflowDelayedTaskList = 5,
126 FreeRTOS_VAL_xPendingReadyList = 6,
127 FreeRTOS_VAL_xTasksWaitingTermination = 7,
128 FreeRTOS_VAL_xSuspendedTaskList = 8,
129 FreeRTOS_VAL_uxCurrentNumberOfTasks = 9,
130 FreeRTOS_VAL_uxTopUsedPriority = 10,
131 };
132
133 struct symbols {
134 const char *name;
135 bool optional;
136 };
137
138 static const struct symbols FreeRTOS_symbol_list[] = {
139 { "pxCurrentTCB", false },
140 { "pxReadyTasksLists", false },
141 { "xDelayedTaskList1", false },
142 { "xDelayedTaskList2", false },
143 { "pxDelayedTaskList", false },
144 { "pxOverflowDelayedTaskList", false },
145 { "xPendingReadyList", false },
146 { "xTasksWaitingTermination", true }, /* Only if INCLUDE_vTaskDelete */
147 { "xSuspendedTaskList", true }, /* Only if INCLUDE_vTaskSuspend */
148 { "uxCurrentNumberOfTasks", false },
149 { "uxTopUsedPriority", true }, /* Unavailable since v7.5.3 */
150 { NULL, false }
151 };
152
153 /* TODO: */
154 /* this is not safe for little endian yet */
155 /* may be problems reading if sizes are not 32 bit long integers. */
156 /* test mallocs for failure */
157
158 static int FreeRTOS_update_threads(struct rtos *rtos)
159 {
160 int retval;
161 int tasks_found = 0;
162 const struct FreeRTOS_params *param;
163
164 if (rtos->rtos_specific_params == NULL)
165 return -1;
166
167 param = (const struct FreeRTOS_params *) rtos->rtos_specific_params;
168
169 if (rtos->symbols == NULL) {
170 LOG_ERROR("No symbols for FreeRTOS");
171 return -3;
172 }
173
174 if (rtos->symbols[FreeRTOS_VAL_uxCurrentNumberOfTasks].address == 0) {
175 LOG_ERROR("Don't have the number of threads in FreeRTOS");
176 return -2;
177 }
178
179 int thread_list_size = 0;
180 retval = target_read_buffer(rtos->target,
181 rtos->symbols[FreeRTOS_VAL_uxCurrentNumberOfTasks].address,
182 param->thread_count_width,
183 (uint8_t *)&thread_list_size);
184 LOG_DEBUG("FreeRTOS: Read uxCurrentNumberOfTasks at 0x%" PRIx64 ", value %d\r\n",
185 rtos->symbols[FreeRTOS_VAL_uxCurrentNumberOfTasks].address,
186 thread_list_size);
187
188 if (retval != ERROR_OK) {
189 LOG_ERROR("Could not read FreeRTOS thread count from target");
190 return retval;
191 }
192
193 /* wipe out previous thread details if any */
194 rtos_free_threadlist(rtos);
195
196 /* read the current thread */
197 retval = target_read_buffer(rtos->target,
198 rtos->symbols[FreeRTOS_VAL_pxCurrentTCB].address,
199 param->pointer_width,
200 (uint8_t *)&rtos->current_thread);
201 if (retval != ERROR_OK) {
202 LOG_ERROR("Error reading current thread in FreeRTOS thread list");
203 return retval;
204 }
205 LOG_DEBUG("FreeRTOS: Read pxCurrentTCB at 0x%" PRIx64 ", value 0x%" PRIx64 "\r\n",
206 rtos->symbols[FreeRTOS_VAL_pxCurrentTCB].address,
207 rtos->current_thread);
208
209 if ((thread_list_size == 0) || (rtos->current_thread == 0)) {
210 /* Either : No RTOS threads - there is always at least the current execution though */
211 /* OR : No current thread - all threads suspended - show the current execution
212 * of idling */
213 char tmp_str[] = "Current Execution";
214 thread_list_size++;
215 tasks_found++;
216 rtos->thread_details = malloc(
217 sizeof(struct thread_detail) * thread_list_size);
218 if (!rtos->thread_details) {
219 LOG_ERROR("Error allocating memory for %d threads", thread_list_size);
220 return ERROR_FAIL;
221 }
222 rtos->thread_details->threadid = 1;
223 rtos->thread_details->exists = true;
224 rtos->thread_details->extra_info_str = NULL;
225 rtos->thread_details->thread_name_str = malloc(sizeof(tmp_str));
226 strcpy(rtos->thread_details->thread_name_str, tmp_str);
227
228 if (thread_list_size == 1) {
229 rtos->thread_count = 1;
230 return ERROR_OK;
231 }
232 } else {
233 /* create space for new thread details */
234 rtos->thread_details = malloc(
235 sizeof(struct thread_detail) * thread_list_size);
236 if (!rtos->thread_details) {
237 LOG_ERROR("Error allocating memory for %d threads", thread_list_size);
238 return ERROR_FAIL;
239 }
240 }
241
242 /* Find out how many lists are needed to be read from pxReadyTasksLists, */
243 if (rtos->symbols[FreeRTOS_VAL_uxTopUsedPriority].address == 0) {
244 LOG_ERROR("FreeRTOS: uxTopUsedPriority is not defined, consult the OpenOCD manual for a work-around");
245 return ERROR_FAIL;
246 }
247 uint64_t top_used_priority = 0;
248 /* FIXME: endianness error on almost all target_read_buffer(), see also
249 * other rtoses */
250 retval = target_read_buffer(rtos->target,
251 rtos->symbols[FreeRTOS_VAL_uxTopUsedPriority].address,
252 param->pointer_width,
253 (uint8_t *)&top_used_priority);
254 if (retval != ERROR_OK)
255 return retval;
256 LOG_DEBUG("FreeRTOS: Read uxTopUsedPriority at 0x%" PRIx64 ", value %" PRIu64 "\r\n",
257 rtos->symbols[FreeRTOS_VAL_uxTopUsedPriority].address,
258 top_used_priority);
259 if (top_used_priority > FREERTOS_MAX_PRIORITIES) {
260 LOG_ERROR("FreeRTOS top used priority is unreasonably big, not proceeding: %" PRIu64,
261 top_used_priority);
262 return ERROR_FAIL;
263 }
264
265 /* uxTopUsedPriority was defined as configMAX_PRIORITIES - 1
266 * in old FreeRTOS versions (before V7.5.3)
267 * Use contrib/rtos-helpers/FreeRTOS-openocd.c to get compatible symbol
268 * in newer FreeRTOS versions.
269 * Here we restore the original configMAX_PRIORITIES value */
270 unsigned int config_max_priorities = top_used_priority + 1;
271
272 symbol_address_t *list_of_lists =
273 malloc(sizeof(symbol_address_t) * (config_max_priorities + 5));
274 if (!list_of_lists) {
275 LOG_ERROR("Error allocating memory for %u priorities", config_max_priorities);
276 return ERROR_FAIL;
277 }
278
279 unsigned int num_lists;
280 for (num_lists = 0; num_lists < config_max_priorities; num_lists++)
281 list_of_lists[num_lists] = rtos->symbols[FreeRTOS_VAL_pxReadyTasksLists].address +
282 num_lists * param->list_width;
283
284 list_of_lists[num_lists++] = rtos->symbols[FreeRTOS_VAL_xDelayedTaskList1].address;
285 list_of_lists[num_lists++] = rtos->symbols[FreeRTOS_VAL_xDelayedTaskList2].address;
286 list_of_lists[num_lists++] = rtos->symbols[FreeRTOS_VAL_xPendingReadyList].address;
287 list_of_lists[num_lists++] = rtos->symbols[FreeRTOS_VAL_xSuspendedTaskList].address;
288 list_of_lists[num_lists++] = rtos->symbols[FreeRTOS_VAL_xTasksWaitingTermination].address;
289
290 for (unsigned int i = 0; i < num_lists; i++) {
291 if (list_of_lists[i] == 0)
292 continue;
293
294 /* Read the number of threads in this list */
295 int64_t list_thread_count = 0;
296 retval = target_read_buffer(rtos->target,
297 list_of_lists[i],
298 param->thread_count_width,
299 (uint8_t *)&list_thread_count);
300 if (retval != ERROR_OK) {
301 LOG_ERROR("Error reading number of threads in FreeRTOS thread list");
302 free(list_of_lists);
303 return retval;
304 }
305 LOG_DEBUG("FreeRTOS: Read thread count for list %u at 0x%" PRIx64 ", value %" PRId64 "\r\n",
306 i, list_of_lists[i], list_thread_count);
307
308 if (list_thread_count == 0)
309 continue;
310
311 /* Read the location of first list item */
312 uint64_t prev_list_elem_ptr = -1;
313 uint64_t list_elem_ptr = 0;
314 retval = target_read_buffer(rtos->target,
315 list_of_lists[i] + param->list_next_offset,
316 param->pointer_width,
317 (uint8_t *)&list_elem_ptr);
318 if (retval != ERROR_OK) {
319 LOG_ERROR("Error reading first thread item location in FreeRTOS thread list");
320 free(list_of_lists);
321 return retval;
322 }
323 LOG_DEBUG("FreeRTOS: Read first item for list %u at 0x%" PRIx64 ", value 0x%" PRIx64 "\r\n",
324 i, list_of_lists[i] + param->list_next_offset, list_elem_ptr);
325
326 while ((list_thread_count > 0) && (list_elem_ptr != 0) &&
327 (list_elem_ptr != prev_list_elem_ptr) &&
328 (tasks_found < thread_list_size)) {
329 /* Get the location of the thread structure. */
330 rtos->thread_details[tasks_found].threadid = 0;
331 retval = target_read_buffer(rtos->target,
332 list_elem_ptr + param->list_elem_content_offset,
333 param->pointer_width,
334 (uint8_t *)&(rtos->thread_details[tasks_found].threadid));
335 if (retval != ERROR_OK) {
336 LOG_ERROR("Error reading thread list item object in FreeRTOS thread list");
337 free(list_of_lists);
338 return retval;
339 }
340 LOG_DEBUG("FreeRTOS: Read Thread ID at 0x%" PRIx64 ", value 0x%" PRIx64 "\r\n",
341 list_elem_ptr + param->list_elem_content_offset,
342 rtos->thread_details[tasks_found].threadid);
343
344 /* get thread name */
345
346 #define FREERTOS_THREAD_NAME_STR_SIZE (200)
347 char tmp_str[FREERTOS_THREAD_NAME_STR_SIZE];
348
349 /* Read the thread name */
350 retval = target_read_buffer(rtos->target,
351 rtos->thread_details[tasks_found].threadid + param->thread_name_offset,
352 FREERTOS_THREAD_NAME_STR_SIZE,
353 (uint8_t *)&tmp_str);
354 if (retval != ERROR_OK) {
355 LOG_ERROR("Error reading first thread item location in FreeRTOS thread list");
356 free(list_of_lists);
357 return retval;
358 }
359 tmp_str[FREERTOS_THREAD_NAME_STR_SIZE-1] = '\x00';
360 LOG_DEBUG("FreeRTOS: Read Thread Name at 0x%" PRIx64 ", value \"%s\"\r\n",
361 rtos->thread_details[tasks_found].threadid + param->thread_name_offset,
362 tmp_str);
363
364 if (tmp_str[0] == '\x00')
365 strcpy(tmp_str, "No Name");
366
367 rtos->thread_details[tasks_found].thread_name_str =
368 malloc(strlen(tmp_str)+1);
369 strcpy(rtos->thread_details[tasks_found].thread_name_str, tmp_str);
370 rtos->thread_details[tasks_found].exists = true;
371
372 if (rtos->thread_details[tasks_found].threadid == rtos->current_thread) {
373 char running_str[] = "State: Running";
374 rtos->thread_details[tasks_found].extra_info_str = malloc(
375 sizeof(running_str));
376 strcpy(rtos->thread_details[tasks_found].extra_info_str,
377 running_str);
378 } else
379 rtos->thread_details[tasks_found].extra_info_str = NULL;
380
381 tasks_found++;
382 list_thread_count--;
383
384 prev_list_elem_ptr = list_elem_ptr;
385 list_elem_ptr = 0;
386 retval = target_read_buffer(rtos->target,
387 prev_list_elem_ptr + param->list_elem_next_offset,
388 param->pointer_width,
389 (uint8_t *)&list_elem_ptr);
390 if (retval != ERROR_OK) {
391 LOG_ERROR("Error reading next thread item location in FreeRTOS thread list");
392 free(list_of_lists);
393 return retval;
394 }
395 LOG_DEBUG("FreeRTOS: Read next thread location at 0x%" PRIx64 ", value 0x%" PRIx64 "\r\n",
396 prev_list_elem_ptr + param->list_elem_next_offset,
397 list_elem_ptr);
398 }
399 }
400
401 free(list_of_lists);
402 rtos->thread_count = tasks_found;
403 return 0;
404 }
405
406 static int FreeRTOS_get_thread_reg_list(struct rtos *rtos, int64_t thread_id,
407 struct rtos_reg **reg_list, int *num_regs)
408 {
409 int retval;
410 const struct FreeRTOS_params *param;
411 int64_t stack_ptr = 0;
412
413 if (rtos == NULL)
414 return -1;
415
416 if (thread_id == 0)
417 return -2;
418
419 if (rtos->rtos_specific_params == NULL)
420 return -1;
421
422 param = (const struct FreeRTOS_params *) rtos->rtos_specific_params;
423
424 /* Read the stack pointer */
425 retval = target_read_buffer(rtos->target,
426 thread_id + param->thread_stack_offset,
427 param->pointer_width,
428 (uint8_t *)&stack_ptr);
429 if (retval != ERROR_OK) {
430 LOG_ERROR("Error reading stack frame from FreeRTOS thread");
431 return retval;
432 }
433 LOG_DEBUG("FreeRTOS: Read stack pointer at 0x%" PRIx64 ", value 0x%" PRIx64 "\r\n",
434 thread_id + param->thread_stack_offset,
435 stack_ptr);
436
437 /* Check for armv7m with *enabled* FPU, i.e. a Cortex-M4F */
438 int cm4_fpu_enabled = 0;
439 struct armv7m_common *armv7m_target = target_to_armv7m(rtos->target);
440 if (is_armv7m(armv7m_target)) {
441 if (armv7m_target->fp_feature == FPv4_SP) {
442 /* Found ARM v7m target which includes a FPU */
443 uint32_t cpacr;
444
445 retval = target_read_u32(rtos->target, FPU_CPACR, &cpacr);
446 if (retval != ERROR_OK) {
447 LOG_ERROR("Could not read CPACR register to check FPU state");
448 return -1;
449 }
450
451 /* Check if CP10 and CP11 are set to full access. */
452 if (cpacr & 0x00F00000) {
453 /* Found target with enabled FPU */
454 cm4_fpu_enabled = 1;
455 }
456 }
457 }
458
459 if (cm4_fpu_enabled == 1) {
460 /* Read the LR to decide between stacking with or without FPU */
461 uint32_t LR_svc = 0;
462 retval = target_read_buffer(rtos->target,
463 stack_ptr + 0x20,
464 param->pointer_width,
465 (uint8_t *)&LR_svc);
466 if (retval != ERROR_OK) {
467 LOG_OUTPUT("Error reading stack frame from FreeRTOS thread\r\n");
468 return retval;
469 }
470 if ((LR_svc & 0x10) == 0)
471 return rtos_generic_stack_read(rtos->target, param->stacking_info_cm4f_fpu, stack_ptr, reg_list, num_regs);
472 else
473 return rtos_generic_stack_read(rtos->target, param->stacking_info_cm4f, stack_ptr, reg_list, num_regs);
474 } else
475 return rtos_generic_stack_read(rtos->target, param->stacking_info_cm3, stack_ptr, reg_list, num_regs);
476 }
477
478 static int FreeRTOS_get_symbol_list_to_lookup(symbol_table_elem_t *symbol_list[])
479 {
480 unsigned int i;
481 *symbol_list = calloc(
482 ARRAY_SIZE(FreeRTOS_symbol_list), sizeof(symbol_table_elem_t));
483
484 for (i = 0; i < ARRAY_SIZE(FreeRTOS_symbol_list); i++) {
485 (*symbol_list)[i].symbol_name = FreeRTOS_symbol_list[i].name;
486 (*symbol_list)[i].optional = FreeRTOS_symbol_list[i].optional;
487 }
488
489 return 0;
490 }
491
492 #if 0
493
494 static int FreeRTOS_set_current_thread(struct rtos *rtos, threadid_t thread_id)
495 {
496 return 0;
497 }
498
499 static int FreeRTOS_get_thread_ascii_info(struct rtos *rtos, threadid_t thread_id, char **info)
500 {
501 int retval;
502 const struct FreeRTOS_params *param;
503
504 if (rtos == NULL)
505 return -1;
506
507 if (thread_id == 0)
508 return -2;
509
510 if (rtos->rtos_specific_params == NULL)
511 return -3;
512
513 param = (const struct FreeRTOS_params *) rtos->rtos_specific_params;
514
515 #define FREERTOS_THREAD_NAME_STR_SIZE (200)
516 char tmp_str[FREERTOS_THREAD_NAME_STR_SIZE];
517
518 /* Read the thread name */
519 retval = target_read_buffer(rtos->target,
520 thread_id + param->thread_name_offset,
521 FREERTOS_THREAD_NAME_STR_SIZE,
522 (uint8_t *)&tmp_str);
523 if (retval != ERROR_OK) {
524 LOG_ERROR("Error reading first thread item location in FreeRTOS thread list");
525 return retval;
526 }
527 tmp_str[FREERTOS_THREAD_NAME_STR_SIZE-1] = '\x00';
528
529 if (tmp_str[0] == '\x00')
530 strcpy(tmp_str, "No Name");
531
532 *info = malloc(strlen(tmp_str)+1);
533 strcpy(*info, tmp_str);
534 return 0;
535 }
536
537 #endif
538
539 static bool FreeRTOS_detect_rtos(struct target *target)
540 {
541 if ((target->rtos->symbols != NULL) &&
542 (target->rtos->symbols[FreeRTOS_VAL_pxReadyTasksLists].address != 0)) {
543 /* looks like FreeRTOS */
544 return true;
545 }
546 return false;
547 }
548
549 static int FreeRTOS_create(struct target *target)
550 {
551 int i = 0;
552 while ((i < FREERTOS_NUM_PARAMS) &&
553 (0 != strcmp(FreeRTOS_params_list[i].target_name, target->type->name))) {
554 i++;
555 }
556 if (i >= FREERTOS_NUM_PARAMS) {
557 LOG_ERROR("Could not find target in FreeRTOS compatibility list");
558 return -1;
559 }
560
561 target->rtos->rtos_specific_params = (void *) &FreeRTOS_params_list[i];
562 return 0;
563 }

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)