rtos: add debug prints for qSymbol lookup
[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 #define FREERTOS_MAX_PRIORITIES 63
35
36 /* FIXME: none of the _width parameters are actually observed properly!
37 * you WILL need to edit more if you actually attempt to target a 8/16/64
38 * bit target!
39 */
40
41 struct freertos_params {
42 const char *target_name;
43 const unsigned char thread_count_width;
44 const unsigned char pointer_width;
45 const unsigned char list_next_offset;
46 const unsigned char list_width;
47 const unsigned char list_elem_next_offset;
48 const unsigned char list_elem_content_offset;
49 const unsigned char thread_stack_offset;
50 const unsigned char thread_name_offset;
51 const struct rtos_register_stacking *stacking_info_cm3;
52 const struct rtos_register_stacking *stacking_info_cm4f;
53 const struct rtos_register_stacking *stacking_info_cm4f_fpu;
54 };
55
56 static const struct freertos_params freertos_params_list[] = {
57 {
58 "cortex_m", /* target_name */
59 4, /* thread_count_width; */
60 4, /* pointer_width; */
61 16, /* list_next_offset; */
62 20, /* list_width; */
63 8, /* list_elem_next_offset; */
64 12, /* list_elem_content_offset */
65 0, /* thread_stack_offset; */
66 52, /* thread_name_offset; */
67 &rtos_standard_cortex_m3_stacking, /* stacking_info */
68 &rtos_standard_cortex_m4f_stacking,
69 &rtos_standard_cortex_m4f_fpu_stacking,
70 },
71 {
72 "hla_target", /* target_name */
73 4, /* thread_count_width; */
74 4, /* pointer_width; */
75 16, /* list_next_offset; */
76 20, /* list_width; */
77 8, /* list_elem_next_offset; */
78 12, /* list_elem_content_offset */
79 0, /* thread_stack_offset; */
80 52, /* thread_name_offset; */
81 &rtos_standard_cortex_m3_stacking, /* stacking_info */
82 &rtos_standard_cortex_m4f_stacking,
83 &rtos_standard_cortex_m4f_fpu_stacking,
84 },
85 {
86 "nds32_v3", /* target_name */
87 4, /* thread_count_width; */
88 4, /* pointer_width; */
89 16, /* list_next_offset; */
90 20, /* list_width; */
91 8, /* list_elem_next_offset; */
92 12, /* list_elem_content_offset */
93 0, /* thread_stack_offset; */
94 52, /* thread_name_offset; */
95 &rtos_standard_nds32_n1068_stacking, /* stacking_info */
96 &rtos_standard_cortex_m4f_stacking,
97 &rtos_standard_cortex_m4f_fpu_stacking,
98 },
99 };
100
101 static bool freertos_detect_rtos(struct target *target);
102 static int freertos_create(struct target *target);
103 static int freertos_update_threads(struct rtos *rtos);
104 static int freertos_get_thread_reg_list(struct rtos *rtos, int64_t thread_id,
105 struct rtos_reg **reg_list, int *num_regs);
106 static int freertos_get_symbol_list_to_lookup(struct symbol_table_elem *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_PX_CURRENT_TCB = 0,
120 FREERTOS_VAL_PX_READY_TASKS_LISTS = 1,
121 FREERTOS_VAL_X_DELAYED_TASK_LIST1 = 2,
122 FREERTOS_VAL_X_DELAYED_TASK_LIST2 = 3,
123 FREERTOS_VAL_PX_DELAYED_TASK_LIST = 4,
124 FREERTOS_VAL_PX_OVERFLOW_DELAYED_TASK_LIST = 5,
125 FREERTOS_VAL_X_PENDING_READY_LIST = 6,
126 FREERTOS_VAL_X_TASKS_WAITING_TERMINATION = 7,
127 FREERTOS_VAL_X_SUSPENDED_TASK_LIST = 8,
128 FREERTOS_VAL_UX_CURRENT_NUMBER_OF_TASKS = 9,
129 FREERTOS_VAL_UX_TOP_USED_PRIORITY = 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 retval;
160 unsigned int tasks_found = 0;
161 const struct freertos_params *param;
162
163 if (!rtos->rtos_specific_params)
164 return -1;
165
166 param = (const struct freertos_params *) rtos->rtos_specific_params;
167
168 if (!rtos->symbols) {
169 LOG_ERROR("No symbols for FreeRTOS");
170 return -3;
171 }
172
173 if (rtos->symbols[FREERTOS_VAL_UX_CURRENT_NUMBER_OF_TASKS].address == 0) {
174 LOG_ERROR("Don't have the number of threads in FreeRTOS");
175 return -2;
176 }
177
178 uint32_t thread_list_size = 0;
179 retval = target_read_u32(rtos->target,
180 rtos->symbols[FREERTOS_VAL_UX_CURRENT_NUMBER_OF_TASKS].address,
181 &thread_list_size);
182 LOG_DEBUG("FreeRTOS: Read uxCurrentNumberOfTasks at 0x%" PRIx64 ", value %" PRIu32,
183 rtos->symbols[FREERTOS_VAL_UX_CURRENT_NUMBER_OF_TASKS].address,
184 thread_list_size);
185
186 if (retval != ERROR_OK) {
187 LOG_ERROR("Could not read FreeRTOS thread count from target");
188 return retval;
189 }
190
191 /* wipe out previous thread details if any */
192 rtos_free_threadlist(rtos);
193
194 /* read the current thread */
195 uint32_t pointer_casts_are_bad;
196 retval = target_read_u32(rtos->target,
197 rtos->symbols[FREERTOS_VAL_PX_CURRENT_TCB].address,
198 &pointer_casts_are_bad);
199 if (retval != ERROR_OK) {
200 LOG_ERROR("Error reading current thread in FreeRTOS thread list");
201 return retval;
202 }
203 rtos->current_thread = pointer_casts_are_bad;
204 LOG_DEBUG("FreeRTOS: Read pxCurrentTCB at 0x%" PRIx64 ", value 0x%" PRIx64,
205 rtos->symbols[FREERTOS_VAL_PX_CURRENT_TCB].address,
206 rtos->current_thread);
207
208 if ((thread_list_size == 0) || (rtos->current_thread == 0)) {
209 /* Either : No RTOS threads - there is always at least the current execution though */
210 /* OR : No current thread - all threads suspended - show the current execution
211 * of idling */
212 char tmp_str[] = "Current Execution";
213 thread_list_size++;
214 tasks_found++;
215 rtos->thread_details = malloc(
216 sizeof(struct thread_detail) * thread_list_size);
217 if (!rtos->thread_details) {
218 LOG_ERROR("Error allocating memory for %d threads", thread_list_size);
219 return ERROR_FAIL;
220 }
221 rtos->thread_details->threadid = 1;
222 rtos->thread_details->exists = true;
223 rtos->thread_details->extra_info_str = NULL;
224 rtos->thread_details->thread_name_str = malloc(sizeof(tmp_str));
225 strcpy(rtos->thread_details->thread_name_str, tmp_str);
226
227 if (thread_list_size == 1) {
228 rtos->thread_count = 1;
229 return ERROR_OK;
230 }
231 } else {
232 /* create space for new thread details */
233 rtos->thread_details = malloc(
234 sizeof(struct thread_detail) * thread_list_size);
235 if (!rtos->thread_details) {
236 LOG_ERROR("Error allocating memory for %d threads", thread_list_size);
237 return ERROR_FAIL;
238 }
239 }
240
241 /* Find out how many lists are needed to be read from pxReadyTasksLists, */
242 if (rtos->symbols[FREERTOS_VAL_UX_TOP_USED_PRIORITY].address == 0) {
243 LOG_ERROR("FreeRTOS: uxTopUsedPriority is not defined, consult the OpenOCD manual for a work-around");
244 return ERROR_FAIL;
245 }
246 uint32_t top_used_priority = 0;
247 retval = target_read_u32(rtos->target,
248 rtos->symbols[FREERTOS_VAL_UX_TOP_USED_PRIORITY].address,
249 &top_used_priority);
250 if (retval != ERROR_OK)
251 return retval;
252 LOG_DEBUG("FreeRTOS: Read uxTopUsedPriority at 0x%" PRIx64 ", value %" PRIu32,
253 rtos->symbols[FREERTOS_VAL_UX_TOP_USED_PRIORITY].address,
254 top_used_priority);
255 if (top_used_priority > FREERTOS_MAX_PRIORITIES) {
256 LOG_ERROR("FreeRTOS top used priority is unreasonably big, not proceeding: %" PRIu32,
257 top_used_priority);
258 return ERROR_FAIL;
259 }
260
261 /* uxTopUsedPriority was defined as configMAX_PRIORITIES - 1
262 * in old FreeRTOS versions (before V7.5.3)
263 * Use contrib/rtos-helpers/FreeRTOS-openocd.c to get compatible symbol
264 * in newer FreeRTOS versions.
265 * Here we restore the original configMAX_PRIORITIES value */
266 unsigned int config_max_priorities = top_used_priority + 1;
267
268 symbol_address_t *list_of_lists =
269 malloc(sizeof(symbol_address_t) * (config_max_priorities + 5));
270 if (!list_of_lists) {
271 LOG_ERROR("Error allocating memory for %u priorities", config_max_priorities);
272 return ERROR_FAIL;
273 }
274
275 unsigned int num_lists;
276 for (num_lists = 0; num_lists < config_max_priorities; num_lists++)
277 list_of_lists[num_lists] = rtos->symbols[FREERTOS_VAL_PX_READY_TASKS_LISTS].address +
278 num_lists * param->list_width;
279
280 list_of_lists[num_lists++] = rtos->symbols[FREERTOS_VAL_X_DELAYED_TASK_LIST1].address;
281 list_of_lists[num_lists++] = rtos->symbols[FREERTOS_VAL_X_DELAYED_TASK_LIST2].address;
282 list_of_lists[num_lists++] = rtos->symbols[FREERTOS_VAL_X_PENDING_READY_LIST].address;
283 list_of_lists[num_lists++] = rtos->symbols[FREERTOS_VAL_X_SUSPENDED_TASK_LIST].address;
284 list_of_lists[num_lists++] = rtos->symbols[FREERTOS_VAL_X_TASKS_WAITING_TERMINATION].address;
285
286 for (unsigned int i = 0; i < num_lists; i++) {
287 if (list_of_lists[i] == 0)
288 continue;
289
290 /* Read the number of threads in this list */
291 uint32_t list_thread_count = 0;
292 retval = target_read_u32(rtos->target,
293 list_of_lists[i],
294 &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 %u at 0x%" PRIx64 ", value %" PRIu32,
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 uint32_t prev_list_elem_ptr = -1;
308 uint32_t list_elem_ptr = 0;
309 retval = target_read_u32(rtos->target,
310 list_of_lists[i] + param->list_next_offset,
311 &list_elem_ptr);
312 if (retval != ERROR_OK) {
313 LOG_ERROR("Error reading first thread item location in FreeRTOS thread list");
314 free(list_of_lists);
315 return retval;
316 }
317 LOG_DEBUG("FreeRTOS: Read first item for list %u at 0x%" PRIx64 ", value 0x%" PRIx32,
318 i, list_of_lists[i] + param->list_next_offset, list_elem_ptr);
319
320 while ((list_thread_count > 0) && (list_elem_ptr != 0) &&
321 (list_elem_ptr != prev_list_elem_ptr) &&
322 (tasks_found < thread_list_size)) {
323 /* Get the location of the thread structure. */
324 rtos->thread_details[tasks_found].threadid = 0;
325 retval = target_read_u32(rtos->target,
326 list_elem_ptr + param->list_elem_content_offset,
327 &pointer_casts_are_bad);
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 rtos->thread_details[tasks_found].threadid = pointer_casts_are_bad;
334 LOG_DEBUG("FreeRTOS: Read Thread ID at 0x%" PRIx32 ", value 0x%" PRIx64,
335 list_elem_ptr + param->list_elem_content_offset,
336 rtos->thread_details[tasks_found].threadid);
337
338 /* get thread name */
339
340 #define FREERTOS_THREAD_NAME_STR_SIZE (200)
341 char tmp_str[FREERTOS_THREAD_NAME_STR_SIZE];
342
343 /* Read the thread name */
344 retval = target_read_buffer(rtos->target,
345 rtos->thread_details[tasks_found].threadid + param->thread_name_offset,
346 FREERTOS_THREAD_NAME_STR_SIZE,
347 (uint8_t *)&tmp_str);
348 if (retval != ERROR_OK) {
349 LOG_ERROR("Error reading first thread item location in FreeRTOS thread list");
350 free(list_of_lists);
351 return retval;
352 }
353 tmp_str[FREERTOS_THREAD_NAME_STR_SIZE-1] = '\x00';
354 LOG_DEBUG("FreeRTOS: Read Thread Name at 0x%" PRIx64 ", value '%s'",
355 rtos->thread_details[tasks_found].threadid + param->thread_name_offset,
356 tmp_str);
357
358 if (tmp_str[0] == '\x00')
359 strcpy(tmp_str, "No Name");
360
361 rtos->thread_details[tasks_found].thread_name_str =
362 malloc(strlen(tmp_str)+1);
363 strcpy(rtos->thread_details[tasks_found].thread_name_str, tmp_str);
364 rtos->thread_details[tasks_found].exists = true;
365
366 if (rtos->thread_details[tasks_found].threadid == rtos->current_thread) {
367 char running_str[] = "State: 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_u32(rtos->target,
381 prev_list_elem_ptr + param->list_elem_next_offset,
382 &list_elem_ptr);
383 if (retval != ERROR_OK) {
384 LOG_ERROR("Error reading next thread item location in FreeRTOS thread list");
385 free(list_of_lists);
386 return retval;
387 }
388 LOG_DEBUG("FreeRTOS: Read next thread location at 0x%" PRIx32 ", value 0x%" PRIx32,
389 prev_list_elem_ptr + param->list_elem_next_offset,
390 list_elem_ptr);
391 }
392 }
393
394 free(list_of_lists);
395 rtos->thread_count = tasks_found;
396 return 0;
397 }
398
399 static int freertos_get_thread_reg_list(struct rtos *rtos, int64_t thread_id,
400 struct rtos_reg **reg_list, int *num_regs)
401 {
402 int retval;
403 const struct freertos_params *param;
404 int64_t stack_ptr = 0;
405
406 if (!rtos)
407 return -1;
408
409 if (thread_id == 0)
410 return -2;
411
412 if (!rtos->rtos_specific_params)
413 return -1;
414
415 param = (const struct freertos_params *) rtos->rtos_specific_params;
416
417 /* Read the stack pointer */
418 uint32_t pointer_casts_are_bad;
419 retval = target_read_u32(rtos->target,
420 thread_id + param->thread_stack_offset,
421 &pointer_casts_are_bad);
422 if (retval != ERROR_OK) {
423 LOG_ERROR("Error reading stack frame from FreeRTOS thread");
424 return retval;
425 }
426 stack_ptr = pointer_casts_are_bad;
427 LOG_DEBUG("FreeRTOS: Read stack pointer at 0x%" PRIx64 ", value 0x%" PRIx64,
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_u32(rtos->target,
457 stack_ptr + 0x20,
458 &lr_svc);
459 if (retval != ERROR_OK) {
460 LOG_OUTPUT("Error reading stack frame from FreeRTOS thread");
461 return retval;
462 }
463 if ((lr_svc & 0x10) == 0)
464 return rtos_generic_stack_read(rtos->target, param->stacking_info_cm4f_fpu, stack_ptr, reg_list, num_regs);
465 else
466 return rtos_generic_stack_read(rtos->target, param->stacking_info_cm4f, stack_ptr, reg_list, num_regs);
467 } else
468 return rtos_generic_stack_read(rtos->target, param->stacking_info_cm3, stack_ptr, reg_list, num_regs);
469 }
470
471 static int freertos_get_symbol_list_to_lookup(struct symbol_table_elem *symbol_list[])
472 {
473 unsigned int i;
474 *symbol_list = calloc(
475 ARRAY_SIZE(freertos_symbol_list), sizeof(struct symbol_table_elem));
476
477 for (i = 0; i < ARRAY_SIZE(freertos_symbol_list); i++) {
478 (*symbol_list)[i].symbol_name = freertos_symbol_list[i].name;
479 (*symbol_list)[i].optional = freertos_symbol_list[i].optional;
480 }
481
482 return 0;
483 }
484
485 #if 0
486
487 static int freertos_set_current_thread(struct rtos *rtos, threadid_t thread_id)
488 {
489 return 0;
490 }
491
492 static int freertos_get_thread_ascii_info(struct rtos *rtos, threadid_t thread_id, char **info)
493 {
494 int retval;
495 const struct freertos_params *param;
496
497 if (!rtos)
498 return -1;
499
500 if (thread_id == 0)
501 return -2;
502
503 if (!rtos->rtos_specific_params)
504 return -3;
505
506 param = (const struct freertos_params *) rtos->rtos_specific_params;
507
508 #define FREERTOS_THREAD_NAME_STR_SIZE (200)
509 char tmp_str[FREERTOS_THREAD_NAME_STR_SIZE];
510
511 /* Read the thread name */
512 retval = target_read_buffer(rtos->target,
513 thread_id + param->thread_name_offset,
514 FREERTOS_THREAD_NAME_STR_SIZE,
515 (uint8_t *)&tmp_str);
516 if (retval != ERROR_OK) {
517 LOG_ERROR("Error reading first thread item location in FreeRTOS thread list");
518 return retval;
519 }
520 tmp_str[FREERTOS_THREAD_NAME_STR_SIZE-1] = '\x00';
521
522 if (tmp_str[0] == '\x00')
523 strcpy(tmp_str, "No Name");
524
525 *info = malloc(strlen(tmp_str)+1);
526 strcpy(*info, tmp_str);
527 return 0;
528 }
529
530 #endif
531
532 static bool freertos_detect_rtos(struct target *target)
533 {
534 if ((target->rtos->symbols) &&
535 (target->rtos->symbols[FREERTOS_VAL_PX_READY_TASKS_LISTS].address != 0)) {
536 /* looks like FreeRTOS */
537 return true;
538 }
539 return false;
540 }
541
542 static int freertos_create(struct target *target)
543 {
544 for (unsigned int i = 0; i < ARRAY_SIZE(freertos_params_list); i++)
545 if (strcmp(freertos_params_list[i].target_name, target->type->name) == 0) {
546 target->rtos->rtos_specific_params = (void *)&freertos_params_list[i];
547 return 0;
548 }
549
550 LOG_ERROR("Could not find target in FreeRTOS compatibility list");
551 return -1;
552 }

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)