target/stm32f4x.cfg: Add STM32F410/F412/F469.
[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 int 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, char **hex_reg_list);
106 static int FreeRTOS_get_symbol_list_to_lookup(symbol_table_elem_t *symbol_list[]);
107
108 struct rtos_type FreeRTOS_rtos = {
109 .name = "FreeRTOS",
110
111 .detect_rtos = FreeRTOS_detect_rtos,
112 .create = FreeRTOS_create,
113 .update_threads = FreeRTOS_update_threads,
114 .get_thread_reg_list = FreeRTOS_get_thread_reg_list,
115 .get_symbol_list_to_lookup = FreeRTOS_get_symbol_list_to_lookup,
116 };
117
118 enum FreeRTOS_symbol_values {
119 FreeRTOS_VAL_pxCurrentTCB = 0,
120 FreeRTOS_VAL_pxReadyTasksLists = 1,
121 FreeRTOS_VAL_xDelayedTaskList1 = 2,
122 FreeRTOS_VAL_xDelayedTaskList2 = 3,
123 FreeRTOS_VAL_pxDelayedTaskList = 4,
124 FreeRTOS_VAL_pxOverflowDelayedTaskList = 5,
125 FreeRTOS_VAL_xPendingReadyList = 6,
126 FreeRTOS_VAL_xTasksWaitingTermination = 7,
127 FreeRTOS_VAL_xSuspendedTaskList = 8,
128 FreeRTOS_VAL_uxCurrentNumberOfTasks = 9,
129 FreeRTOS_VAL_uxTopUsedPriority = 10,
130 };
131
132 struct symbols {
133 const char *name;
134 bool optional;
135 };
136
137 static const struct symbols FreeRTOS_symbol_list[] = {
138 { "pxCurrentTCB", false },
139 { "pxReadyTasksLists", false },
140 { "xDelayedTaskList1", false },
141 { "xDelayedTaskList2", false },
142 { "pxDelayedTaskList", false },
143 { "pxOverflowDelayedTaskList", false },
144 { "xPendingReadyList", false },
145 { "xTasksWaitingTermination", true }, /* Only if INCLUDE_vTaskDelete */
146 { "xSuspendedTaskList", true }, /* Only if INCLUDE_vTaskSuspend */
147 { "uxCurrentNumberOfTasks", false },
148 { "uxTopUsedPriority", true }, /* Unavailable since v7.5.3 */
149 { NULL, false }
150 };
151
152 /* TODO: */
153 /* this is not safe for little endian yet */
154 /* may be problems reading if sizes are not 32 bit long integers. */
155 /* test mallocs for failure */
156
157 static int FreeRTOS_update_threads(struct rtos *rtos)
158 {
159 int i = 0;
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->display_str = NULL;
225 rtos->thread_details->extra_info_str = NULL;
226 rtos->thread_details->thread_name_str = malloc(sizeof(tmp_str));
227 strcpy(rtos->thread_details->thread_name_str, tmp_str);
228
229 if (thread_list_size == 1) {
230 rtos->thread_count = 1;
231 return ERROR_OK;
232 }
233 } else {
234 /* create space for new thread details */
235 rtos->thread_details = malloc(
236 sizeof(struct thread_detail) * thread_list_size);
237 if (!rtos->thread_details) {
238 LOG_ERROR("Error allocating memory for %d threads", thread_list_size);
239 return ERROR_FAIL;
240 }
241 }
242
243 /* Find out how many lists are needed to be read from pxReadyTasksLists, */
244 if (rtos->symbols[FreeRTOS_VAL_uxTopUsedPriority].address == 0) {
245 LOG_ERROR("FreeRTOS: uxTopUsedPriority is not defined, consult the OpenOCD manual for a work-around");
246 return ERROR_FAIL;
247 }
248 int64_t max_used_priority = 0;
249 retval = target_read_buffer(rtos->target,
250 rtos->symbols[FreeRTOS_VAL_uxTopUsedPriority].address,
251 param->pointer_width,
252 (uint8_t *)&max_used_priority);
253 if (retval != ERROR_OK)
254 return retval;
255 LOG_DEBUG("FreeRTOS: Read uxTopUsedPriority at 0x%" PRIx64 ", value %" PRId64 "\r\n",
256 rtos->symbols[FreeRTOS_VAL_uxTopUsedPriority].address,
257 max_used_priority);
258 if (max_used_priority > FREERTOS_MAX_PRIORITIES) {
259 LOG_ERROR("FreeRTOS maximum used priority is unreasonably big, not proceeding: %" PRId64 "",
260 max_used_priority);
261 return ERROR_FAIL;
262 }
263
264 symbol_address_t *list_of_lists =
265 malloc(sizeof(symbol_address_t) *
266 (max_used_priority+1 + 5));
267 if (!list_of_lists) {
268 LOG_ERROR("Error allocating memory for %" PRId64 " priorities", max_used_priority);
269 return ERROR_FAIL;
270 }
271
272 int num_lists;
273 for (num_lists = 0; num_lists <= max_used_priority; num_lists++)
274 list_of_lists[num_lists] = rtos->symbols[FreeRTOS_VAL_pxReadyTasksLists].address +
275 num_lists * param->list_width;
276
277 list_of_lists[num_lists++] = rtos->symbols[FreeRTOS_VAL_xDelayedTaskList1].address;
278 list_of_lists[num_lists++] = rtos->symbols[FreeRTOS_VAL_xDelayedTaskList2].address;
279 list_of_lists[num_lists++] = rtos->symbols[FreeRTOS_VAL_xPendingReadyList].address;
280 list_of_lists[num_lists++] = rtos->symbols[FreeRTOS_VAL_xSuspendedTaskList].address;
281 list_of_lists[num_lists++] = rtos->symbols[FreeRTOS_VAL_xTasksWaitingTermination].address;
282
283 for (i = 0; i < num_lists; i++) {
284 if (list_of_lists[i] == 0)
285 continue;
286
287 /* Read the number of threads in this list */
288 int64_t list_thread_count = 0;
289 retval = target_read_buffer(rtos->target,
290 list_of_lists[i],
291 param->thread_count_width,
292 (uint8_t *)&list_thread_count);
293 if (retval != ERROR_OK) {
294 LOG_ERROR("Error reading number of threads in FreeRTOS thread list");
295 free(list_of_lists);
296 return retval;
297 }
298 LOG_DEBUG("FreeRTOS: Read thread count for list %d at 0x%" PRIx64 ", value %" PRId64 "\r\n",
299 i, list_of_lists[i], list_thread_count);
300
301 if (list_thread_count == 0)
302 continue;
303
304 /* Read the location of first list item */
305 uint64_t prev_list_elem_ptr = -1;
306 uint64_t list_elem_ptr = 0;
307 retval = target_read_buffer(rtos->target,
308 list_of_lists[i] + param->list_next_offset,
309 param->pointer_width,
310 (uint8_t *)&list_elem_ptr);
311 if (retval != ERROR_OK) {
312 LOG_ERROR("Error reading first thread item location in FreeRTOS thread list");
313 free(list_of_lists);
314 return retval;
315 }
316 LOG_DEBUG("FreeRTOS: Read first item for list %d at 0x%" PRIx64 ", value 0x%" PRIx64 "\r\n",
317 i, list_of_lists[i] + param->list_next_offset, list_elem_ptr);
318
319 while ((list_thread_count > 0) && (list_elem_ptr != 0) &&
320 (list_elem_ptr != prev_list_elem_ptr) &&
321 (tasks_found < thread_list_size)) {
322 /* Get the location of the thread structure. */
323 rtos->thread_details[tasks_found].threadid = 0;
324 retval = target_read_buffer(rtos->target,
325 list_elem_ptr + param->list_elem_content_offset,
326 param->pointer_width,
327 (uint8_t *)&(rtos->thread_details[tasks_found].threadid));
328 if (retval != ERROR_OK) {
329 LOG_ERROR("Error reading thread list item object in FreeRTOS thread list");
330 free(list_of_lists);
331 return retval;
332 }
333 LOG_DEBUG("FreeRTOS: Read Thread ID at 0x%" PRIx64 ", value 0x%" PRIx64 "\r\n",
334 list_elem_ptr + param->list_elem_content_offset,
335 rtos->thread_details[tasks_found].threadid);
336
337 /* get thread name */
338
339 #define FREERTOS_THREAD_NAME_STR_SIZE (200)
340 char tmp_str[FREERTOS_THREAD_NAME_STR_SIZE];
341
342 /* Read the thread name */
343 retval = target_read_buffer(rtos->target,
344 rtos->thread_details[tasks_found].threadid + param->thread_name_offset,
345 FREERTOS_THREAD_NAME_STR_SIZE,
346 (uint8_t *)&tmp_str);
347 if (retval != ERROR_OK) {
348 LOG_ERROR("Error reading first thread item location in FreeRTOS thread list");
349 free(list_of_lists);
350 return retval;
351 }
352 tmp_str[FREERTOS_THREAD_NAME_STR_SIZE-1] = '\x00';
353 LOG_DEBUG("FreeRTOS: Read Thread Name at 0x%" PRIx64 ", value \"%s\"\r\n",
354 rtos->thread_details[tasks_found].threadid + param->thread_name_offset,
355 tmp_str);
356
357 if (tmp_str[0] == '\x00')
358 strcpy(tmp_str, "No Name");
359
360 rtos->thread_details[tasks_found].thread_name_str =
361 malloc(strlen(tmp_str)+1);
362 strcpy(rtos->thread_details[tasks_found].thread_name_str, tmp_str);
363 rtos->thread_details[tasks_found].display_str = NULL;
364 rtos->thread_details[tasks_found].exists = true;
365
366 if (rtos->thread_details[tasks_found].threadid == rtos->current_thread) {
367 char running_str[] = "Running";
368 rtos->thread_details[tasks_found].extra_info_str = malloc(
369 sizeof(running_str));
370 strcpy(rtos->thread_details[tasks_found].extra_info_str,
371 running_str);
372 } else
373 rtos->thread_details[tasks_found].extra_info_str = NULL;
374
375 tasks_found++;
376 list_thread_count--;
377
378 prev_list_elem_ptr = list_elem_ptr;
379 list_elem_ptr = 0;
380 retval = target_read_buffer(rtos->target,
381 prev_list_elem_ptr + param->list_elem_next_offset,
382 param->pointer_width,
383 (uint8_t *)&list_elem_ptr);
384 if (retval != ERROR_OK) {
385 LOG_ERROR("Error reading next thread item location in FreeRTOS thread list");
386 free(list_of_lists);
387 return retval;
388 }
389 LOG_DEBUG("FreeRTOS: Read next thread location at 0x%" PRIx64 ", value 0x%" PRIx64 "\r\n",
390 prev_list_elem_ptr + param->list_elem_next_offset,
391 list_elem_ptr);
392 }
393 }
394
395 free(list_of_lists);
396 rtos->thread_count = tasks_found;
397 return 0;
398 }
399
400 static int FreeRTOS_get_thread_reg_list(struct rtos *rtos, int64_t thread_id, char **hex_reg_list)
401 {
402 int retval;
403 const struct FreeRTOS_params *param;
404 int64_t stack_ptr = 0;
405
406 *hex_reg_list = NULL;
407 if (rtos == NULL)
408 return -1;
409
410 if (thread_id == 0)
411 return -2;
412
413 if (rtos->rtos_specific_params == NULL)
414 return -1;
415
416 param = (const struct FreeRTOS_params *) rtos->rtos_specific_params;
417
418 /* Read the stack pointer */
419 retval = target_read_buffer(rtos->target,
420 thread_id + param->thread_stack_offset,
421 param->pointer_width,
422 (uint8_t *)&stack_ptr);
423 if (retval != ERROR_OK) {
424 LOG_ERROR("Error reading stack frame from FreeRTOS thread");
425 return retval;
426 }
427 LOG_DEBUG("FreeRTOS: Read stack pointer at 0x%" PRIx64 ", value 0x%" PRIx64 "\r\n",
428 thread_id + param->thread_stack_offset,
429 stack_ptr);
430
431 /* Check for armv7m with *enabled* FPU, i.e. a Cortex-M4F */
432 int cm4_fpu_enabled = 0;
433 struct armv7m_common *armv7m_target = target_to_armv7m(rtos->target);
434 if (is_armv7m(armv7m_target)) {
435 if (armv7m_target->fp_feature == FPv4_SP) {
436 /* Found ARM v7m target which includes a FPU */
437 uint32_t cpacr;
438
439 retval = target_read_u32(rtos->target, FPU_CPACR, &cpacr);
440 if (retval != ERROR_OK) {
441 LOG_ERROR("Could not read CPACR register to check FPU state");
442 return -1;
443 }
444
445 /* Check if CP10 and CP11 are set to full access. */
446 if (cpacr & 0x00F00000) {
447 /* Found target with enabled FPU */
448 cm4_fpu_enabled = 1;
449 }
450 }
451 }
452
453 if (cm4_fpu_enabled == 1) {
454 /* Read the LR to decide between stacking with or without FPU */
455 uint32_t LR_svc = 0;
456 retval = target_read_buffer(rtos->target,
457 stack_ptr + 0x20,
458 param->pointer_width,
459 (uint8_t *)&LR_svc);
460 if (retval != ERROR_OK) {
461 LOG_OUTPUT("Error reading stack frame from FreeRTOS thread\r\n");
462 return retval;
463 }
464 if ((LR_svc & 0x10) == 0)
465 return rtos_generic_stack_read(rtos->target, param->stacking_info_cm4f_fpu, stack_ptr, hex_reg_list);
466 else
467 return rtos_generic_stack_read(rtos->target, param->stacking_info_cm4f, stack_ptr, hex_reg_list);
468 } else
469 return rtos_generic_stack_read(rtos->target, param->stacking_info_cm3, stack_ptr, hex_reg_list);
470 }
471
472 static int FreeRTOS_get_symbol_list_to_lookup(symbol_table_elem_t *symbol_list[])
473 {
474 unsigned int i;
475 *symbol_list = calloc(
476 ARRAY_SIZE(FreeRTOS_symbol_list), sizeof(symbol_table_elem_t));
477
478 for (i = 0; i < ARRAY_SIZE(FreeRTOS_symbol_list); i++) {
479 (*symbol_list)[i].symbol_name = FreeRTOS_symbol_list[i].name;
480 (*symbol_list)[i].optional = FreeRTOS_symbol_list[i].optional;
481 }
482
483 return 0;
484 }
485
486 #if 0
487
488 static int FreeRTOS_set_current_thread(struct rtos *rtos, threadid_t thread_id)
489 {
490 return 0;
491 }
492
493 static int FreeRTOS_get_thread_ascii_info(struct rtos *rtos, threadid_t thread_id, char **info)
494 {
495 int retval;
496 const struct FreeRTOS_params *param;
497
498 if (rtos == NULL)
499 return -1;
500
501 if (thread_id == 0)
502 return -2;
503
504 if (rtos->rtos_specific_params == NULL)
505 return -3;
506
507 param = (const struct FreeRTOS_params *) rtos->rtos_specific_params;
508
509 #define FREERTOS_THREAD_NAME_STR_SIZE (200)
510 char tmp_str[FREERTOS_THREAD_NAME_STR_SIZE];
511
512 /* Read the thread name */
513 retval = target_read_buffer(rtos->target,
514 thread_id + param->thread_name_offset,
515 FREERTOS_THREAD_NAME_STR_SIZE,
516 (uint8_t *)&tmp_str);
517 if (retval != ERROR_OK) {
518 LOG_ERROR("Error reading first thread item location in FreeRTOS thread list");
519 return retval;
520 }
521 tmp_str[FREERTOS_THREAD_NAME_STR_SIZE-1] = '\x00';
522
523 if (tmp_str[0] == '\x00')
524 strcpy(tmp_str, "No Name");
525
526 *info = malloc(strlen(tmp_str)+1);
527 strcpy(*info, tmp_str);
528 return 0;
529 }
530
531 #endif
532
533 static int FreeRTOS_detect_rtos(struct target *target)
534 {
535 if ((target->rtos->symbols != NULL) &&
536 (target->rtos->symbols[FreeRTOS_VAL_pxReadyTasksLists].address != 0)) {
537 /* looks like FreeRTOS */
538 return 1;
539 }
540 return 0;
541 }
542
543 static int FreeRTOS_create(struct target *target)
544 {
545 int i = 0;
546 while ((i < FREERTOS_NUM_PARAMS) &&
547 (0 != strcmp(FreeRTOS_params_list[i].target_name, target->type->name))) {
548 i++;
549 }
550 if (i >= FREERTOS_NUM_PARAMS) {
551 LOG_ERROR("Could not find target in FreeRTOS compatibility list");
552 return -1;
553 }
554
555 target->rtos->rtos_specific_params = (void *) &FreeRTOS_params_list[i];
556 return 0;
557 }

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)