FreeRTOS: properly read on big endian systems.
[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 /* FIXME: none of the _width parameters are actually observed properly!
41 * you WILL need to edit more if you actually attempt to target a 8/16/64
42 * bit target!
43 */
44
45 struct FreeRTOS_params {
46 const char *target_name;
47 const unsigned char thread_count_width;
48 const unsigned char pointer_width;
49 const unsigned char list_next_offset;
50 const unsigned char list_width;
51 const unsigned char list_elem_next_offset;
52 const unsigned char list_elem_content_offset;
53 const unsigned char thread_stack_offset;
54 const unsigned char thread_name_offset;
55 const struct rtos_register_stacking *stacking_info_cm3;
56 const struct rtos_register_stacking *stacking_info_cm4f;
57 const struct rtos_register_stacking *stacking_info_cm4f_fpu;
58 };
59
60 static const struct FreeRTOS_params FreeRTOS_params_list[] = {
61 {
62 "cortex_m", /* target_name */
63 4, /* thread_count_width; */
64 4, /* pointer_width; */
65 16, /* list_next_offset; */
66 20, /* list_width; */
67 8, /* list_elem_next_offset; */
68 12, /* list_elem_content_offset */
69 0, /* thread_stack_offset; */
70 52, /* thread_name_offset; */
71 &rtos_standard_Cortex_M3_stacking, /* stacking_info */
72 &rtos_standard_Cortex_M4F_stacking,
73 &rtos_standard_Cortex_M4F_FPU_stacking,
74 },
75 {
76 "hla_target", /* target_name */
77 4, /* thread_count_width; */
78 4, /* pointer_width; */
79 16, /* list_next_offset; */
80 20, /* list_width; */
81 8, /* list_elem_next_offset; */
82 12, /* list_elem_content_offset */
83 0, /* thread_stack_offset; */
84 52, /* thread_name_offset; */
85 &rtos_standard_Cortex_M3_stacking, /* stacking_info */
86 &rtos_standard_Cortex_M4F_stacking,
87 &rtos_standard_Cortex_M4F_FPU_stacking,
88 },
89 {
90 "nds32_v3", /* target_name */
91 4, /* thread_count_width; */
92 4, /* pointer_width; */
93 16, /* list_next_offset; */
94 20, /* list_width; */
95 8, /* list_elem_next_offset; */
96 12, /* list_elem_content_offset */
97 0, /* thread_stack_offset; */
98 52, /* thread_name_offset; */
99 &rtos_standard_NDS32_N1068_stacking, /* stacking_info */
100 &rtos_standard_Cortex_M4F_stacking,
101 &rtos_standard_Cortex_M4F_FPU_stacking,
102 },
103 };
104
105 #define FREERTOS_NUM_PARAMS ((int)(sizeof(FreeRTOS_params_list)/sizeof(struct FreeRTOS_params)))
106
107 static bool FreeRTOS_detect_rtos(struct target *target);
108 static int FreeRTOS_create(struct target *target);
109 static int FreeRTOS_update_threads(struct rtos *rtos);
110 static int FreeRTOS_get_thread_reg_list(struct rtos *rtos, int64_t thread_id,
111 struct rtos_reg **reg_list, int *num_regs);
112 static int FreeRTOS_get_symbol_list_to_lookup(symbol_table_elem_t *symbol_list[]);
113
114 struct rtos_type FreeRTOS_rtos = {
115 .name = "FreeRTOS",
116
117 .detect_rtos = FreeRTOS_detect_rtos,
118 .create = FreeRTOS_create,
119 .update_threads = FreeRTOS_update_threads,
120 .get_thread_reg_list = FreeRTOS_get_thread_reg_list,
121 .get_symbol_list_to_lookup = FreeRTOS_get_symbol_list_to_lookup,
122 };
123
124 enum FreeRTOS_symbol_values {
125 FreeRTOS_VAL_pxCurrentTCB = 0,
126 FreeRTOS_VAL_pxReadyTasksLists = 1,
127 FreeRTOS_VAL_xDelayedTaskList1 = 2,
128 FreeRTOS_VAL_xDelayedTaskList2 = 3,
129 FreeRTOS_VAL_pxDelayedTaskList = 4,
130 FreeRTOS_VAL_pxOverflowDelayedTaskList = 5,
131 FreeRTOS_VAL_xPendingReadyList = 6,
132 FreeRTOS_VAL_xTasksWaitingTermination = 7,
133 FreeRTOS_VAL_xSuspendedTaskList = 8,
134 FreeRTOS_VAL_uxCurrentNumberOfTasks = 9,
135 FreeRTOS_VAL_uxTopUsedPriority = 10,
136 };
137
138 struct symbols {
139 const char *name;
140 bool optional;
141 };
142
143 static const struct symbols FreeRTOS_symbol_list[] = {
144 { "pxCurrentTCB", false },
145 { "pxReadyTasksLists", false },
146 { "xDelayedTaskList1", false },
147 { "xDelayedTaskList2", false },
148 { "pxDelayedTaskList", false },
149 { "pxOverflowDelayedTaskList", false },
150 { "xPendingReadyList", false },
151 { "xTasksWaitingTermination", true }, /* Only if INCLUDE_vTaskDelete */
152 { "xSuspendedTaskList", true }, /* Only if INCLUDE_vTaskSuspend */
153 { "uxCurrentNumberOfTasks", false },
154 { "uxTopUsedPriority", true }, /* Unavailable since v7.5.3 */
155 { NULL, false }
156 };
157
158 /* TODO: */
159 /* this is not safe for little endian yet */
160 /* may be problems reading if sizes are not 32 bit long integers. */
161 /* test mallocs for failure */
162
163 static int FreeRTOS_update_threads(struct rtos *rtos)
164 {
165 int retval;
166 unsigned int tasks_found = 0;
167 const struct FreeRTOS_params *param;
168
169 if (rtos->rtos_specific_params == NULL)
170 return -1;
171
172 param = (const struct FreeRTOS_params *) rtos->rtos_specific_params;
173
174 if (rtos->symbols == NULL) {
175 LOG_ERROR("No symbols for FreeRTOS");
176 return -3;
177 }
178
179 if (rtos->symbols[FreeRTOS_VAL_uxCurrentNumberOfTasks].address == 0) {
180 LOG_ERROR("Don't have the number of threads in FreeRTOS");
181 return -2;
182 }
183
184 uint32_t thread_list_size = 0;
185 retval = target_read_u32(rtos->target,
186 rtos->symbols[FreeRTOS_VAL_uxCurrentNumberOfTasks].address,
187 &thread_list_size);
188 LOG_DEBUG("FreeRTOS: Read uxCurrentNumberOfTasks at 0x%" PRIx64 ", value %" PRIu32 "\r\n",
189 rtos->symbols[FreeRTOS_VAL_uxCurrentNumberOfTasks].address,
190 thread_list_size);
191
192 if (retval != ERROR_OK) {
193 LOG_ERROR("Could not read FreeRTOS thread count from target");
194 return retval;
195 }
196
197 /* wipe out previous thread details if any */
198 rtos_free_threadlist(rtos);
199
200 /* read the current thread */
201 uint32_t pointer_casts_are_bad;
202 retval = target_read_u32(rtos->target,
203 rtos->symbols[FreeRTOS_VAL_pxCurrentTCB].address,
204 &pointer_casts_are_bad);
205 if (retval != ERROR_OK) {
206 LOG_ERROR("Error reading current thread in FreeRTOS thread list");
207 return retval;
208 }
209 rtos->current_thread = pointer_casts_are_bad;
210 LOG_DEBUG("FreeRTOS: Read pxCurrentTCB at 0x%" PRIx64 ", value 0x%" PRIx64 "\r\n",
211 rtos->symbols[FreeRTOS_VAL_pxCurrentTCB].address,
212 rtos->current_thread);
213
214 if ((thread_list_size == 0) || (rtos->current_thread == 0)) {
215 /* Either : No RTOS threads - there is always at least the current execution though */
216 /* OR : No current thread - all threads suspended - show the current execution
217 * of idling */
218 char tmp_str[] = "Current Execution";
219 thread_list_size++;
220 tasks_found++;
221 rtos->thread_details = malloc(
222 sizeof(struct thread_detail) * thread_list_size);
223 if (!rtos->thread_details) {
224 LOG_ERROR("Error allocating memory for %d threads", thread_list_size);
225 return ERROR_FAIL;
226 }
227 rtos->thread_details->threadid = 1;
228 rtos->thread_details->exists = true;
229 rtos->thread_details->extra_info_str = NULL;
230 rtos->thread_details->thread_name_str = malloc(sizeof(tmp_str));
231 strcpy(rtos->thread_details->thread_name_str, tmp_str);
232
233 if (thread_list_size == 1) {
234 rtos->thread_count = 1;
235 return ERROR_OK;
236 }
237 } else {
238 /* create space for new thread details */
239 rtos->thread_details = malloc(
240 sizeof(struct thread_detail) * thread_list_size);
241 if (!rtos->thread_details) {
242 LOG_ERROR("Error allocating memory for %d threads", thread_list_size);
243 return ERROR_FAIL;
244 }
245 }
246
247 /* Find out how many lists are needed to be read from pxReadyTasksLists, */
248 if (rtos->symbols[FreeRTOS_VAL_uxTopUsedPriority].address == 0) {
249 LOG_ERROR("FreeRTOS: uxTopUsedPriority is not defined, consult the OpenOCD manual for a work-around");
250 return ERROR_FAIL;
251 }
252 uint32_t top_used_priority = 0;
253 retval = target_read_u32(rtos->target,
254 rtos->symbols[FreeRTOS_VAL_uxTopUsedPriority].address,
255 &top_used_priority);
256 if (retval != ERROR_OK)
257 return retval;
258 LOG_DEBUG("FreeRTOS: Read uxTopUsedPriority at 0x%" PRIx64 ", value %" PRIu32 "\r\n",
259 rtos->symbols[FreeRTOS_VAL_uxTopUsedPriority].address,
260 top_used_priority);
261 if (top_used_priority > FREERTOS_MAX_PRIORITIES) {
262 LOG_ERROR("FreeRTOS top used priority is unreasonably big, not proceeding: %" PRIu32,
263 top_used_priority);
264 return ERROR_FAIL;
265 }
266
267 /* uxTopUsedPriority was defined as configMAX_PRIORITIES - 1
268 * in old FreeRTOS versions (before V7.5.3)
269 * Use contrib/rtos-helpers/FreeRTOS-openocd.c to get compatible symbol
270 * in newer FreeRTOS versions.
271 * Here we restore the original configMAX_PRIORITIES value */
272 unsigned int config_max_priorities = top_used_priority + 1;
273
274 symbol_address_t *list_of_lists =
275 malloc(sizeof(symbol_address_t) * (config_max_priorities + 5));
276 if (!list_of_lists) {
277 LOG_ERROR("Error allocating memory for %u priorities", config_max_priorities);
278 return ERROR_FAIL;
279 }
280
281 unsigned int num_lists;
282 for (num_lists = 0; num_lists < config_max_priorities; num_lists++)
283 list_of_lists[num_lists] = rtos->symbols[FreeRTOS_VAL_pxReadyTasksLists].address +
284 num_lists * param->list_width;
285
286 list_of_lists[num_lists++] = rtos->symbols[FreeRTOS_VAL_xDelayedTaskList1].address;
287 list_of_lists[num_lists++] = rtos->symbols[FreeRTOS_VAL_xDelayedTaskList2].address;
288 list_of_lists[num_lists++] = rtos->symbols[FreeRTOS_VAL_xPendingReadyList].address;
289 list_of_lists[num_lists++] = rtos->symbols[FreeRTOS_VAL_xSuspendedTaskList].address;
290 list_of_lists[num_lists++] = rtos->symbols[FreeRTOS_VAL_xTasksWaitingTermination].address;
291
292 for (unsigned int i = 0; i < num_lists; i++) {
293 if (list_of_lists[i] == 0)
294 continue;
295
296 /* Read the number of threads in this list */
297 uint32_t list_thread_count = 0;
298 retval = target_read_u32(rtos->target,
299 list_of_lists[i],
300 &list_thread_count);
301 if (retval != ERROR_OK) {
302 LOG_ERROR("Error reading number of threads in FreeRTOS thread list");
303 free(list_of_lists);
304 return retval;
305 }
306 LOG_DEBUG("FreeRTOS: Read thread count for list %u at 0x%" PRIx64 ", value %" PRIu32 "\r\n",
307 i, list_of_lists[i], list_thread_count);
308
309 if (list_thread_count == 0)
310 continue;
311
312 /* Read the location of first list item */
313 uint32_t prev_list_elem_ptr = -1;
314 uint32_t list_elem_ptr = 0;
315 retval = target_read_u32(rtos->target,
316 list_of_lists[i] + param->list_next_offset,
317 &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%" PRIx32 "\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_u32(rtos->target,
332 list_elem_ptr + param->list_elem_content_offset,
333 &pointer_casts_are_bad);
334 if (retval != ERROR_OK) {
335 LOG_ERROR("Error reading thread list item object in FreeRTOS thread list");
336 free(list_of_lists);
337 return retval;
338 }
339 rtos->thread_details[tasks_found].threadid = pointer_casts_are_bad;
340 LOG_DEBUG("FreeRTOS: Read Thread ID at 0x%" PRIx32 ", 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_u32(rtos->target,
387 prev_list_elem_ptr + param->list_elem_next_offset,
388 &list_elem_ptr);
389 if (retval != ERROR_OK) {
390 LOG_ERROR("Error reading next thread item location in FreeRTOS thread list");
391 free(list_of_lists);
392 return retval;
393 }
394 LOG_DEBUG("FreeRTOS: Read next thread location at 0x%" PRIx32 ", value 0x%" PRIx32 "\r\n",
395 prev_list_elem_ptr + param->list_elem_next_offset,
396 list_elem_ptr);
397 }
398 }
399
400 free(list_of_lists);
401 rtos->thread_count = tasks_found;
402 return 0;
403 }
404
405 static int FreeRTOS_get_thread_reg_list(struct rtos *rtos, int64_t thread_id,
406 struct rtos_reg **reg_list, int *num_regs)
407 {
408 int retval;
409 const struct FreeRTOS_params *param;
410 int64_t stack_ptr = 0;
411
412 if (rtos == NULL)
413 return -1;
414
415 if (thread_id == 0)
416 return -2;
417
418 if (rtos->rtos_specific_params == NULL)
419 return -1;
420
421 param = (const struct FreeRTOS_params *) rtos->rtos_specific_params;
422
423 /* Read the stack pointer */
424 uint32_t pointer_casts_are_bad;
425 retval = target_read_u32(rtos->target,
426 thread_id + param->thread_stack_offset,
427 &pointer_casts_are_bad);
428 if (retval != ERROR_OK) {
429 LOG_ERROR("Error reading stack frame from FreeRTOS thread");
430 return retval;
431 }
432 stack_ptr = pointer_casts_are_bad;
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_u32(rtos->target,
463 stack_ptr + 0x20,
464 &LR_svc);
465 if (retval != ERROR_OK) {
466 LOG_OUTPUT("Error reading stack frame from FreeRTOS thread\r\n");
467 return retval;
468 }
469 if ((LR_svc & 0x10) == 0)
470 return rtos_generic_stack_read(rtos->target, param->stacking_info_cm4f_fpu, stack_ptr, reg_list, num_regs);
471 else
472 return rtos_generic_stack_read(rtos->target, param->stacking_info_cm4f, stack_ptr, reg_list, num_regs);
473 } else
474 return rtos_generic_stack_read(rtos->target, param->stacking_info_cm3, stack_ptr, reg_list, num_regs);
475 }
476
477 static int FreeRTOS_get_symbol_list_to_lookup(symbol_table_elem_t *symbol_list[])
478 {
479 unsigned int i;
480 *symbol_list = calloc(
481 ARRAY_SIZE(FreeRTOS_symbol_list), sizeof(symbol_table_elem_t));
482
483 for (i = 0; i < ARRAY_SIZE(FreeRTOS_symbol_list); i++) {
484 (*symbol_list)[i].symbol_name = FreeRTOS_symbol_list[i].name;
485 (*symbol_list)[i].optional = FreeRTOS_symbol_list[i].optional;
486 }
487
488 return 0;
489 }
490
491 #if 0
492
493 static int FreeRTOS_set_current_thread(struct rtos *rtos, threadid_t thread_id)
494 {
495 return 0;
496 }
497
498 static int FreeRTOS_get_thread_ascii_info(struct rtos *rtos, threadid_t thread_id, char **info)
499 {
500 int retval;
501 const struct FreeRTOS_params *param;
502
503 if (rtos == NULL)
504 return -1;
505
506 if (thread_id == 0)
507 return -2;
508
509 if (rtos->rtos_specific_params == NULL)
510 return -3;
511
512 param = (const struct FreeRTOS_params *) rtos->rtos_specific_params;
513
514 #define FREERTOS_THREAD_NAME_STR_SIZE (200)
515 char tmp_str[FREERTOS_THREAD_NAME_STR_SIZE];
516
517 /* Read the thread name */
518 retval = target_read_buffer(rtos->target,
519 thread_id + param->thread_name_offset,
520 FREERTOS_THREAD_NAME_STR_SIZE,
521 (uint8_t *)&tmp_str);
522 if (retval != ERROR_OK) {
523 LOG_ERROR("Error reading first thread item location in FreeRTOS thread list");
524 return retval;
525 }
526 tmp_str[FREERTOS_THREAD_NAME_STR_SIZE-1] = '\x00';
527
528 if (tmp_str[0] == '\x00')
529 strcpy(tmp_str, "No Name");
530
531 *info = malloc(strlen(tmp_str)+1);
532 strcpy(*info, tmp_str);
533 return 0;
534 }
535
536 #endif
537
538 static bool FreeRTOS_detect_rtos(struct target *target)
539 {
540 if ((target->rtos->symbols != NULL) &&
541 (target->rtos->symbols[FreeRTOS_VAL_pxReadyTasksLists].address != 0)) {
542 /* looks like FreeRTOS */
543 return true;
544 }
545 return false;
546 }
547
548 static int FreeRTOS_create(struct target *target)
549 {
550 int i = 0;
551 while ((i < FREERTOS_NUM_PARAMS) &&
552 (0 != strcmp(FreeRTOS_params_list[i].target_name, target->type->name))) {
553 i++;
554 }
555 if (i >= FREERTOS_NUM_PARAMS) {
556 LOG_ERROR("Could not find target in FreeRTOS compatibility list");
557 return -1;
558 }
559
560 target->rtos->rtos_specific_params = (void *) &FreeRTOS_params_list[i];
561 return 0;
562 }

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)