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

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)