83961eb9573cedb5683858999d678fba19de94e0
[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->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 int64_t max_used_priority = 0;
248 retval = target_read_buffer(rtos->target,
249 rtos->symbols[FreeRTOS_VAL_uxTopUsedPriority].address,
250 param->pointer_width,
251 (uint8_t *)&max_used_priority);
252 if (retval != ERROR_OK)
253 return retval;
254 LOG_DEBUG("FreeRTOS: Read uxTopUsedPriority at 0x%" PRIx64 ", value %" PRId64 "\r\n",
255 rtos->symbols[FreeRTOS_VAL_uxTopUsedPriority].address,
256 max_used_priority);
257 if (max_used_priority > FREERTOS_MAX_PRIORITIES) {
258 LOG_ERROR("FreeRTOS maximum used priority is unreasonably big, not proceeding: %" PRId64 "",
259 max_used_priority);
260 return ERROR_FAIL;
261 }
262
263 symbol_address_t *list_of_lists =
264 malloc(sizeof(symbol_address_t) *
265 (max_used_priority+1 + 5));
266 if (!list_of_lists) {
267 LOG_ERROR("Error allocating memory for %" PRId64 " priorities", max_used_priority);
268 return ERROR_FAIL;
269 }
270
271 int num_lists;
272 for (num_lists = 0; num_lists <= max_used_priority; num_lists++)
273 list_of_lists[num_lists] = rtos->symbols[FreeRTOS_VAL_pxReadyTasksLists].address +
274 num_lists * param->list_width;
275
276 list_of_lists[num_lists++] = rtos->symbols[FreeRTOS_VAL_xDelayedTaskList1].address;
277 list_of_lists[num_lists++] = rtos->symbols[FreeRTOS_VAL_xDelayedTaskList2].address;
278 list_of_lists[num_lists++] = rtos->symbols[FreeRTOS_VAL_xPendingReadyList].address;
279 list_of_lists[num_lists++] = rtos->symbols[FreeRTOS_VAL_xSuspendedTaskList].address;
280 list_of_lists[num_lists++] = rtos->symbols[FreeRTOS_VAL_xTasksWaitingTermination].address;
281
282 for (i = 0; i < num_lists; i++) {
283 if (list_of_lists[i] == 0)
284 continue;
285
286 /* Read the number of threads in this list */
287 int64_t list_thread_count = 0;
288 retval = target_read_buffer(rtos->target,
289 list_of_lists[i],
290 param->thread_count_width,
291 (uint8_t *)&list_thread_count);
292 if (retval != ERROR_OK) {
293 LOG_ERROR("Error reading number of threads in FreeRTOS thread list");
294 free(list_of_lists);
295 return retval;
296 }
297 LOG_DEBUG("FreeRTOS: Read thread count for list %d at 0x%" PRIx64 ", value %" PRId64 "\r\n",
298 i, list_of_lists[i], list_thread_count);
299
300 if (list_thread_count == 0)
301 continue;
302
303 /* Read the location of first list item */
304 uint64_t prev_list_elem_ptr = -1;
305 uint64_t list_elem_ptr = 0;
306 retval = target_read_buffer(rtos->target,
307 list_of_lists[i] + param->list_next_offset,
308 param->pointer_width,
309 (uint8_t *)&list_elem_ptr);
310 if (retval != ERROR_OK) {
311 LOG_ERROR("Error reading first thread item location in FreeRTOS thread list");
312 free(list_of_lists);
313 return retval;
314 }
315 LOG_DEBUG("FreeRTOS: Read first item for list %d at 0x%" PRIx64 ", value 0x%" PRIx64 "\r\n",
316 i, list_of_lists[i] + param->list_next_offset, list_elem_ptr);
317
318 while ((list_thread_count > 0) && (list_elem_ptr != 0) &&
319 (list_elem_ptr != prev_list_elem_ptr) &&
320 (tasks_found < thread_list_size)) {
321 /* Get the location of the thread structure. */
322 rtos->thread_details[tasks_found].threadid = 0;
323 retval = target_read_buffer(rtos->target,
324 list_elem_ptr + param->list_elem_content_offset,
325 param->pointer_width,
326 (uint8_t *)&(rtos->thread_details[tasks_found].threadid));
327 if (retval != ERROR_OK) {
328 LOG_ERROR("Error reading thread list item object in FreeRTOS thread list");
329 free(list_of_lists);
330 return retval;
331 }
332 LOG_DEBUG("FreeRTOS: Read Thread ID at 0x%" PRIx64 ", value 0x%" PRIx64 "\r\n",
333 list_elem_ptr + param->list_elem_content_offset,
334 rtos->thread_details[tasks_found].threadid);
335
336 /* get thread name */
337
338 #define FREERTOS_THREAD_NAME_STR_SIZE (200)
339 char tmp_str[FREERTOS_THREAD_NAME_STR_SIZE];
340
341 /* Read the thread name */
342 retval = target_read_buffer(rtos->target,
343 rtos->thread_details[tasks_found].threadid + param->thread_name_offset,
344 FREERTOS_THREAD_NAME_STR_SIZE,
345 (uint8_t *)&tmp_str);
346 if (retval != ERROR_OK) {
347 LOG_ERROR("Error reading first thread item location in FreeRTOS thread list");
348 free(list_of_lists);
349 return retval;
350 }
351 tmp_str[FREERTOS_THREAD_NAME_STR_SIZE-1] = '\x00';
352 LOG_DEBUG("FreeRTOS: Read Thread Name at 0x%" PRIx64 ", value \"%s\"\r\n",
353 rtos->thread_details[tasks_found].threadid + param->thread_name_offset,
354 tmp_str);
355
356 if (tmp_str[0] == '\x00')
357 strcpy(tmp_str, "No Name");
358
359 rtos->thread_details[tasks_found].thread_name_str =
360 malloc(strlen(tmp_str)+1);
361 strcpy(rtos->thread_details[tasks_found].thread_name_str, tmp_str);
362 rtos->thread_details[tasks_found].exists = true;
363
364 if (rtos->thread_details[tasks_found].threadid == rtos->current_thread) {
365 char running_str[] = "State: Running";
366 rtos->thread_details[tasks_found].extra_info_str = malloc(
367 sizeof(running_str));
368 strcpy(rtos->thread_details[tasks_found].extra_info_str,
369 running_str);
370 } else
371 rtos->thread_details[tasks_found].extra_info_str = NULL;
372
373 tasks_found++;
374 list_thread_count--;
375
376 prev_list_elem_ptr = list_elem_ptr;
377 list_elem_ptr = 0;
378 retval = target_read_buffer(rtos->target,
379 prev_list_elem_ptr + param->list_elem_next_offset,
380 param->pointer_width,
381 (uint8_t *)&list_elem_ptr);
382 if (retval != ERROR_OK) {
383 LOG_ERROR("Error reading next thread item location in FreeRTOS thread list");
384 free(list_of_lists);
385 return retval;
386 }
387 LOG_DEBUG("FreeRTOS: Read next thread location at 0x%" PRIx64 ", value 0x%" PRIx64 "\r\n",
388 prev_list_elem_ptr + param->list_elem_next_offset,
389 list_elem_ptr);
390 }
391 }
392
393 free(list_of_lists);
394 rtos->thread_count = tasks_found;
395 return 0;
396 }
397
398 static int FreeRTOS_get_thread_reg_list(struct rtos *rtos, int64_t thread_id, char **hex_reg_list)
399 {
400 int retval;
401 const struct FreeRTOS_params *param;
402 int64_t stack_ptr = 0;
403
404 *hex_reg_list = NULL;
405 if (rtos == NULL)
406 return -1;
407
408 if (thread_id == 0)
409 return -2;
410
411 if (rtos->rtos_specific_params == NULL)
412 return -1;
413
414 param = (const struct FreeRTOS_params *) rtos->rtos_specific_params;
415
416 /* Read the stack pointer */
417 retval = target_read_buffer(rtos->target,
418 thread_id + param->thread_stack_offset,
419 param->pointer_width,
420 (uint8_t *)&stack_ptr);
421 if (retval != ERROR_OK) {
422 LOG_ERROR("Error reading stack frame from FreeRTOS thread");
423 return retval;
424 }
425 LOG_DEBUG("FreeRTOS: Read stack pointer at 0x%" PRIx64 ", value 0x%" PRIx64 "\r\n",
426 thread_id + param->thread_stack_offset,
427 stack_ptr);
428
429 /* Check for armv7m with *enabled* FPU, i.e. a Cortex-M4F */
430 int cm4_fpu_enabled = 0;
431 struct armv7m_common *armv7m_target = target_to_armv7m(rtos->target);
432 if (is_armv7m(armv7m_target)) {
433 if (armv7m_target->fp_feature == FPv4_SP) {
434 /* Found ARM v7m target which includes a FPU */
435 uint32_t cpacr;
436
437 retval = target_read_u32(rtos->target, FPU_CPACR, &cpacr);
438 if (retval != ERROR_OK) {
439 LOG_ERROR("Could not read CPACR register to check FPU state");
440 return -1;
441 }
442
443 /* Check if CP10 and CP11 are set to full access. */
444 if (cpacr & 0x00F00000) {
445 /* Found target with enabled FPU */
446 cm4_fpu_enabled = 1;
447 }
448 }
449 }
450
451 if (cm4_fpu_enabled == 1) {
452 /* Read the LR to decide between stacking with or without FPU */
453 uint32_t LR_svc = 0;
454 retval = target_read_buffer(rtos->target,
455 stack_ptr + 0x20,
456 param->pointer_width,
457 (uint8_t *)&LR_svc);
458 if (retval != ERROR_OK) {
459 LOG_OUTPUT("Error reading stack frame from FreeRTOS thread\r\n");
460 return retval;
461 }
462 if ((LR_svc & 0x10) == 0)
463 return rtos_generic_stack_read(rtos->target, param->stacking_info_cm4f_fpu, stack_ptr, hex_reg_list);
464 else
465 return rtos_generic_stack_read(rtos->target, param->stacking_info_cm4f, stack_ptr, hex_reg_list);
466 } else
467 return rtos_generic_stack_read(rtos->target, param->stacking_info_cm3, stack_ptr, hex_reg_list);
468 }
469
470 static int FreeRTOS_get_symbol_list_to_lookup(symbol_table_elem_t *symbol_list[])
471 {
472 unsigned int i;
473 *symbol_list = calloc(
474 ARRAY_SIZE(FreeRTOS_symbol_list), sizeof(symbol_table_elem_t));
475
476 for (i = 0; i < ARRAY_SIZE(FreeRTOS_symbol_list); i++) {
477 (*symbol_list)[i].symbol_name = FreeRTOS_symbol_list[i].name;
478 (*symbol_list)[i].optional = FreeRTOS_symbol_list[i].optional;
479 }
480
481 return 0;
482 }
483
484 #if 0
485
486 static int FreeRTOS_set_current_thread(struct rtos *rtos, threadid_t thread_id)
487 {
488 return 0;
489 }
490
491 static int FreeRTOS_get_thread_ascii_info(struct rtos *rtos, threadid_t thread_id, char **info)
492 {
493 int retval;
494 const struct FreeRTOS_params *param;
495
496 if (rtos == NULL)
497 return -1;
498
499 if (thread_id == 0)
500 return -2;
501
502 if (rtos->rtos_specific_params == NULL)
503 return -3;
504
505 param = (const struct FreeRTOS_params *) rtos->rtos_specific_params;
506
507 #define FREERTOS_THREAD_NAME_STR_SIZE (200)
508 char tmp_str[FREERTOS_THREAD_NAME_STR_SIZE];
509
510 /* Read the thread name */
511 retval = target_read_buffer(rtos->target,
512 thread_id + param->thread_name_offset,
513 FREERTOS_THREAD_NAME_STR_SIZE,
514 (uint8_t *)&tmp_str);
515 if (retval != ERROR_OK) {
516 LOG_ERROR("Error reading first thread item location in FreeRTOS thread list");
517 return retval;
518 }
519 tmp_str[FREERTOS_THREAD_NAME_STR_SIZE-1] = '\x00';
520
521 if (tmp_str[0] == '\x00')
522 strcpy(tmp_str, "No Name");
523
524 *info = malloc(strlen(tmp_str)+1);
525 strcpy(*info, tmp_str);
526 return 0;
527 }
528
529 #endif
530
531 static int FreeRTOS_detect_rtos(struct target *target)
532 {
533 if ((target->rtos->symbols != NULL) &&
534 (target->rtos->symbols[FreeRTOS_VAL_pxReadyTasksLists].address != 0)) {
535 /* looks like FreeRTOS */
536 return 1;
537 }
538 return 0;
539 }
540
541 static int FreeRTOS_create(struct target *target)
542 {
543 int i = 0;
544 while ((i < FREERTOS_NUM_PARAMS) &&
545 (0 != strcmp(FreeRTOS_params_list[i].target_name, target->type->name))) {
546 i++;
547 }
548 if (i >= FREERTOS_NUM_PARAMS) {
549 LOG_ERROR("Could not find target in FreeRTOS compatibility list");
550 return -1;
551 }
552
553 target->rtos->rtos_specific_params = (void *) &FreeRTOS_params_list[i];
554 return 0;
555 }

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)