1 /***************************************************************************
2 * Copyright (C) 2017 by Intel Corporation
3 * Leandro Pereira <leandro.pereira@intel.com>
4 * Daniel Glöckner <dg@emlix.com>*
5 * Copyright (C) 2021 by Synopsys, Inc.
6 * Evgeniy Didin <didin@synopsys.com>
8 * SPDX-License-Identifier: GPL-2.0-or-later *
9 ***************************************************************************/
15 #include <helper/time_support.h>
16 #include <jtag/jtag.h>
18 #include "helper/log.h"
19 #include "helper/types.h"
21 #include "rtos_standard_stackings.h"
22 #include "target/target.h"
23 #include "target/target_type.h"
24 #include "target/armv7m.h"
25 #include "target/arc.h"
27 #define UNIMPLEMENTED 0xFFFFFFFFU
29 /* ARC specific defines */
30 #define ARC_AUX_SEC_BUILD_REG 0xdb
31 #define ARC_REG_NUM 38
33 /* ARM specific defines */
34 #define ARM_XPSR_OFFSET 28
36 struct zephyr_thread
{
37 uint32_t ptr
, next_ptr
;
39 uint32_t stack_pointer
;
53 OFFSET_T_USER_OPTIONS
,
55 OFFSET_T_STACK_POINTER
,
58 OFFSET_T_PREEMPT_FLOAT
,
63 struct zephyr_params
{
64 const char *target_name
;
66 uint8_t pointer_width
;
68 uint32_t offsets
[OFFSET_MAX
];
69 const struct rtos_register_stacking
*callee_saved_stacking
;
70 const struct rtos_register_stacking
*cpu_saved_nofp_stacking
;
71 const struct rtos_register_stacking
*cpu_saved_fp_stacking
;
72 int (*get_cpu_state
)(struct rtos
*rtos
, target_addr_t
*addr
,
73 struct zephyr_params
*params
,
74 struct rtos_reg
*callee_saved_reg_list
,
75 struct rtos_reg
**reg_list
, int *num_regs
);
78 static const struct stack_register_offset arm_callee_saved
[] = {
79 { ARMV7M_R13
, 32, 32 },
83 { ARMV7M_R7
, 12, 32 },
84 { ARMV7M_R8
, 16, 32 },
85 { ARMV7M_R9
, 20, 32 },
86 { ARMV7M_R10
, 24, 32 },
87 { ARMV7M_R11
, 28, 32 },
90 static const struct stack_register_offset arc_callee_saved
[] = {
108 static const struct rtos_register_stacking arm_callee_saved_stacking
= {
109 .stack_registers_size
= 36,
110 .stack_growth_direction
= -1,
111 .num_output_registers
= ARRAY_SIZE(arm_callee_saved
),
112 .register_offsets
= arm_callee_saved
,
115 static const struct rtos_register_stacking arc_callee_saved_stacking
= {
116 .stack_registers_size
= 64,
117 .stack_growth_direction
= -1,
118 .num_output_registers
= ARRAY_SIZE(arc_callee_saved
),
119 .register_offsets
= arc_callee_saved
,
122 static const struct stack_register_offset arm_cpu_saved
[] = {
123 { ARMV7M_R0
, 0, 32 },
124 { ARMV7M_R1
, 4, 32 },
125 { ARMV7M_R2
, 8, 32 },
126 { ARMV7M_R3
, 12, 32 },
127 { ARMV7M_R4
, -1, 32 },
128 { ARMV7M_R5
, -1, 32 },
129 { ARMV7M_R6
, -1, 32 },
130 { ARMV7M_R7
, -1, 32 },
131 { ARMV7M_R8
, -1, 32 },
132 { ARMV7M_R9
, -1, 32 },
133 { ARMV7M_R10
, -1, 32 },
134 { ARMV7M_R11
, -1, 32 },
135 { ARMV7M_R12
, 16, 32 },
136 { ARMV7M_R13
, -2, 32 },
137 { ARMV7M_R14
, 20, 32 },
138 { ARMV7M_PC
, 24, 32 },
139 { ARMV7M_xPSR
, 28, 32 },
142 static struct stack_register_offset arc_cpu_saved
[] = {
172 { ARC_ILINK
, -1, 32 },
174 { ARC_BLINK
, 0, 32 },
175 { ARC_LP_COUNT
, -1, 32 },
178 { ARC_LP_START
, -1, 32 },
179 { ARC_LP_END
, -1, 32 },
180 { ARC_STATUS32
, 4, 32 }
184 enum zephyr_symbol_values
{
186 ZEPHYR_VAL__KERNEL_OPENOCD_OFFSETS
,
187 ZEPHYR_VAL__KERNEL_OPENOCD_SIZE_T_SIZE
,
188 ZEPHYR_VAL__KERNEL_OPENOCD_NUM_OFFSETS
,
192 static target_addr_t
zephyr_cortex_m_stack_align(struct target
*target
,
193 const uint8_t *stack_data
,
194 const struct rtos_register_stacking
*stacking
, target_addr_t stack_ptr
)
196 return rtos_cortex_m_stack_align(target
, stack_data
, stacking
,
197 stack_ptr
, ARM_XPSR_OFFSET
);
200 static const struct rtos_register_stacking arm_cpu_saved_nofp_stacking
= {
201 .stack_registers_size
= 32,
202 .stack_growth_direction
= -1,
203 .num_output_registers
= ARRAY_SIZE(arm_cpu_saved
),
204 .calculate_process_stack
= zephyr_cortex_m_stack_align
,
205 .register_offsets
= arm_cpu_saved
,
208 static const struct rtos_register_stacking arm_cpu_saved_fp_stacking
= {
209 .stack_registers_size
= 32 + 18 * 4,
210 .stack_growth_direction
= -1,
211 .num_output_registers
= ARRAY_SIZE(arm_cpu_saved
),
212 .calculate_process_stack
= zephyr_cortex_m_stack_align
,
213 .register_offsets
= arm_cpu_saved
,
216 /* stack_registers_size is 8 because besides caller registers
217 * there are only blink and Status32 registers on stack left */
218 static struct rtos_register_stacking arc_cpu_saved_stacking
= {
219 .stack_registers_size
= 8,
220 .stack_growth_direction
= -1,
221 .num_output_registers
= ARRAY_SIZE(arc_cpu_saved
),
222 .register_offsets
= arc_cpu_saved
,
225 /* ARCv2 specific implementation */
226 static int zephyr_get_arc_state(struct rtos
*rtos
, target_addr_t
*addr
,
227 struct zephyr_params
*params
,
228 struct rtos_reg
*callee_saved_reg_list
,
229 struct rtos_reg
**reg_list
, int *num_regs
)
232 uint32_t real_stack_addr
;
234 int num_callee_saved_regs
;
235 const struct rtos_register_stacking
*stacking
;
237 /* Getting real stack address from Kernel thread struct */
238 retval
= target_read_u32(rtos
->target
, *addr
, &real_stack_addr
);
239 if (retval
!= ERROR_OK
)
242 /* Getting callee registers */
243 retval
= rtos_generic_stack_read(rtos
->target
,
244 params
->callee_saved_stacking
,
245 real_stack_addr
, &callee_saved_reg_list
,
246 &num_callee_saved_regs
);
247 if (retval
!= ERROR_OK
)
250 stacking
= params
->cpu_saved_nofp_stacking
;
252 /* Getting blink and status32 registers */
253 retval
= rtos_generic_stack_read(rtos
->target
, stacking
,
254 real_stack_addr
+ num_callee_saved_regs
* 4,
256 if (retval
!= ERROR_OK
)
259 for (int i
= 0; i
< num_callee_saved_regs
; i
++)
260 buf_cpy(callee_saved_reg_list
[i
].value
,
261 (*reg_list
)[callee_saved_reg_list
[i
].number
].value
,
262 callee_saved_reg_list
[i
].size
);
264 /* The blink, sp, pc offsets in arc_cpu_saved structure may be changed,
265 * but the registers number shall not. So the next code searches the
266 * offsetst of these registers in arc_cpu_saved structure. */
267 unsigned short blink_offset
= 0, pc_offset
= 0, sp_offset
= 0;
268 for (size_t i
= 0; i
< ARRAY_SIZE(arc_cpu_saved
); i
++) {
269 if (arc_cpu_saved
[i
].number
== ARC_BLINK
)
271 if (arc_cpu_saved
[i
].number
== ARC_SP
)
273 if (arc_cpu_saved
[i
].number
== ARC_PC
)
277 if (blink_offset
== 0 || sp_offset
== 0 || pc_offset
== 0) {
278 LOG_ERROR("Basic registers offsets are missing, check <arc_cpu_saved> struct");
282 /* Put blink value into PC */
283 buf_cpy((*reg_list
)[blink_offset
].value
,
284 (*reg_list
)[pc_offset
].value
, sizeof((*reg_list
)[blink_offset
].value
));
286 /* Put address after callee/caller in SP. */
289 stack_top
= real_stack_addr
+ num_callee_saved_regs
* 4
290 + arc_cpu_saved_stacking
.stack_registers_size
;
291 buf_cpy(&stack_top
, (*reg_list
)[sp_offset
].value
, sizeof(stack_top
));
296 /* ARM Cortex-M-specific implementation */
297 static int zephyr_get_arm_state(struct rtos
*rtos
, target_addr_t
*addr
,
298 struct zephyr_params
*params
,
299 struct rtos_reg
*callee_saved_reg_list
,
300 struct rtos_reg
**reg_list
, int *num_regs
)
304 int num_callee_saved_regs
;
305 const struct rtos_register_stacking
*stacking
;
307 retval
= rtos_generic_stack_read(rtos
->target
,
308 params
->callee_saved_stacking
,
309 *addr
, &callee_saved_reg_list
,
310 &num_callee_saved_regs
);
311 if (retval
!= ERROR_OK
)
314 *addr
= target_buffer_get_u32(rtos
->target
,
315 callee_saved_reg_list
[0].value
);
317 if (params
->offsets
[OFFSET_T_PREEMPT_FLOAT
] != UNIMPLEMENTED
)
318 stacking
= params
->cpu_saved_fp_stacking
;
320 stacking
= params
->cpu_saved_nofp_stacking
;
322 retval
= rtos_generic_stack_read(rtos
->target
, stacking
, *addr
, reg_list
,
324 if (retval
!= ERROR_OK
)
327 for (int i
= 1; i
< num_callee_saved_regs
; i
++)
328 buf_cpy(callee_saved_reg_list
[i
].value
,
329 (*reg_list
)[callee_saved_reg_list
[i
].number
].value
,
330 callee_saved_reg_list
[i
].size
);
334 static struct zephyr_params zephyr_params_list
[] = {
336 .target_name
= "cortex_m",
338 .callee_saved_stacking
= &arm_callee_saved_stacking
,
339 .cpu_saved_nofp_stacking
= &arm_cpu_saved_nofp_stacking
,
340 .cpu_saved_fp_stacking
= &arm_cpu_saved_fp_stacking
,
341 .get_cpu_state
= &zephyr_get_arm_state
,
344 .target_name
= "cortex_r4",
346 .callee_saved_stacking
= &arm_callee_saved_stacking
,
347 .cpu_saved_nofp_stacking
= &arm_cpu_saved_nofp_stacking
,
348 .cpu_saved_fp_stacking
= &arm_cpu_saved_fp_stacking
,
349 .get_cpu_state
= &zephyr_get_arm_state
,
352 .target_name
= "hla_target",
354 .callee_saved_stacking
= &arm_callee_saved_stacking
,
355 .cpu_saved_nofp_stacking
= &arm_cpu_saved_nofp_stacking
,
356 .cpu_saved_fp_stacking
= &arm_cpu_saved_fp_stacking
,
357 .get_cpu_state
= &zephyr_get_arm_state
,
361 .target_name
= "arcv2",
363 .callee_saved_stacking
= &arc_callee_saved_stacking
,
364 .cpu_saved_nofp_stacking
= &arc_cpu_saved_stacking
,
365 .get_cpu_state
= &zephyr_get_arc_state
,
372 static const struct symbol_table_elem zephyr_symbol_list
[] = {
374 .symbol_name
= "_kernel",
378 .symbol_name
= "_kernel_openocd_offsets",
382 .symbol_name
= "_kernel_openocd_size_t_size",
386 .symbol_name
= "_kernel_openocd_num_offsets",
394 static bool zephyr_detect_rtos(struct target
*target
)
396 if (!target
->rtos
->symbols
) {
397 LOG_INFO("Zephyr: no symbols while detecting RTOS");
401 for (enum zephyr_symbol_values symbol
= ZEPHYR_VAL__KERNEL
;
402 symbol
!= ZEPHYR_VAL_COUNT
; symbol
++) {
403 LOG_INFO("Zephyr: does it have symbol %d (%s)?", symbol
,
404 target
->rtos
->symbols
[symbol
].optional
? "optional" : "mandatory");
406 if (target
->rtos
->symbols
[symbol
].optional
)
408 if (target
->rtos
->symbols
[symbol
].address
== 0)
412 LOG_INFO("Zephyr: all mandatory symbols found");
417 static int zephyr_create(struct target
*target
)
421 name
= target_type_name(target
);
423 LOG_INFO("Zephyr: looking for target: %s", name
);
425 /* ARC specific, check if EM target has security subsystem
426 * In case of ARC_HAS_SECURE zephyr option enabled
427 * the thread stack contains blink,sec_stat,status32 register
428 * values. If ARC_HAS_SECURE is disabled, only blink and status32
429 * register values are saved on stack. */
430 if (!strcmp(name
, "arcv2")) {
432 struct arc_common
*arc
= target_to_arc(target
);
433 /* Reading SEC_BUILD bcr */
434 CHECK_RETVAL(arc_jtag_read_aux_reg_one(&arc
->jtag_info
, ARC_AUX_SEC_BUILD_REG
, &value
));
436 LOG_DEBUG("ARC EM board has security subsystem, changing offsets");
437 arc_cpu_saved
[ARC_REG_NUM
- 1].offset
= 8;
438 /* After reading callee registers in stack
439 * now blink,sec_stat,status32 registers
441 arc_cpu_saved_stacking
.stack_registers_size
= 12;
445 for (struct zephyr_params
*p
= zephyr_params_list
; p
->target_name
; p
++) {
446 if (!strcmp(p
->target_name
, name
)) {
447 LOG_INFO("Zephyr: target known, params at %p", p
);
448 target
->rtos
->rtos_specific_params
= p
;
453 LOG_ERROR("Could not find target in Zephyr compatibility list");
457 struct zephyr_array
{
462 static void zephyr_array_init(struct zephyr_array
*array
)
468 static void zephyr_array_free(struct zephyr_array
*array
)
471 zephyr_array_init(array
);
474 static void *zephyr_array_append(struct zephyr_array
*array
, size_t size
)
476 if (!(array
->elements
% 16)) {
477 void *ptr
= realloc(array
->ptr
, (array
->elements
+ 16) * size
);
480 LOG_ERROR("Out of memory");
487 return (unsigned char *)array
->ptr
+ (array
->elements
++) * size
;
490 static void *zephyr_array_detach_ptr(struct zephyr_array
*array
)
492 void *ptr
= array
->ptr
;
494 zephyr_array_init(array
);
499 static uint32_t zephyr_kptr(const struct rtos
*rtos
, enum zephyr_offsets off
)
501 const struct zephyr_params
*params
= rtos
->rtos_specific_params
;
503 return rtos
->symbols
[ZEPHYR_VAL__KERNEL
].address
+ params
->offsets
[off
];
506 static int zephyr_fetch_thread(const struct rtos
*rtos
,
507 struct zephyr_thread
*thread
, uint32_t ptr
)
509 const struct zephyr_params
*param
= rtos
->rtos_specific_params
;
514 retval
= target_read_u32(rtos
->target
, ptr
+ param
->offsets
[OFFSET_T_ENTRY
],
516 if (retval
!= ERROR_OK
)
519 retval
= target_read_u32(rtos
->target
,
520 ptr
+ param
->offsets
[OFFSET_T_NEXT_THREAD
],
522 if (retval
!= ERROR_OK
)
525 retval
= target_read_u32(rtos
->target
,
526 ptr
+ param
->offsets
[OFFSET_T_STACK_POINTER
],
527 &thread
->stack_pointer
);
528 if (retval
!= ERROR_OK
)
531 retval
= target_read_u8(rtos
->target
, ptr
+ param
->offsets
[OFFSET_T_STATE
],
533 if (retval
!= ERROR_OK
)
536 retval
= target_read_u8(rtos
->target
,
537 ptr
+ param
->offsets
[OFFSET_T_USER_OPTIONS
],
538 &thread
->user_options
);
539 if (retval
!= ERROR_OK
)
543 retval
= target_read_u8(rtos
->target
,
544 ptr
+ param
->offsets
[OFFSET_T_PRIO
], &prio
);
545 if (retval
!= ERROR_OK
)
549 thread
->name
[0] = '\0';
550 if (param
->offsets
[OFFSET_T_NAME
] != UNIMPLEMENTED
) {
551 retval
= target_read_buffer(rtos
->target
,
552 ptr
+ param
->offsets
[OFFSET_T_NAME
],
553 sizeof(thread
->name
) - 1, (uint8_t *)thread
->name
);
554 if (retval
!= ERROR_OK
)
557 thread
->name
[sizeof(thread
->name
) - 1] = '\0';
560 LOG_DEBUG("Fetched thread%" PRIx32
": {entry@0x%" PRIx32
561 ", state=%" PRIu8
", useropts=%" PRIu8
", prio=%" PRId8
"}",
562 ptr
, thread
->entry
, thread
->state
, thread
->user_options
, thread
->prio
);
567 static int zephyr_fetch_thread_list(struct rtos
*rtos
, uint32_t current_thread
)
569 struct zephyr_array thread_array
;
570 struct zephyr_thread thread
;
571 struct thread_detail
*td
;
572 int64_t curr_id
= -1;
576 retval
= target_read_u32(rtos
->target
, zephyr_kptr(rtos
, OFFSET_K_THREADS
),
578 if (retval
!= ERROR_OK
) {
579 LOG_ERROR("Could not fetch current thread pointer");
583 zephyr_array_init(&thread_array
);
585 for (; curr
; curr
= thread
.next_ptr
) {
586 retval
= zephyr_fetch_thread(rtos
, &thread
, curr
);
587 if (retval
!= ERROR_OK
)
590 td
= zephyr_array_append(&thread_array
, sizeof(*td
));
594 td
->threadid
= thread
.ptr
;
598 td
->thread_name_str
= strdup(thread
.name
);
600 td
->thread_name_str
= alloc_printf("thr_%" PRIx32
"_%" PRIx32
,
601 thread
.entry
, thread
.ptr
);
602 td
->extra_info_str
= alloc_printf("prio:%" PRId8
",useropts:%" PRIu8
,
603 thread
.prio
, thread
.user_options
);
604 if (!td
->thread_name_str
|| !td
->extra_info_str
)
607 if (td
->threadid
== current_thread
)
608 curr_id
= (int64_t)thread_array
.elements
- 1;
611 LOG_DEBUG("Got information for %zu threads", thread_array
.elements
);
613 rtos_free_threadlist(rtos
);
615 rtos
->thread_count
= (int)thread_array
.elements
;
616 rtos
->thread_details
= zephyr_array_detach_ptr(&thread_array
);
618 rtos
->current_threadid
= curr_id
;
619 rtos
->current_thread
= current_thread
;
624 td
= thread_array
.ptr
;
625 for (size_t i
= 0; i
< thread_array
.elements
; i
++) {
626 free(td
[i
].thread_name_str
);
627 free(td
[i
].extra_info_str
);
630 zephyr_array_free(&thread_array
);
635 static int zephyr_update_threads(struct rtos
*rtos
)
637 struct zephyr_params
*param
;
640 if (!rtos
->rtos_specific_params
)
643 param
= (struct zephyr_params
*)rtos
->rtos_specific_params
;
645 if (!rtos
->symbols
) {
646 LOG_ERROR("No symbols for Zephyr");
650 if (rtos
->symbols
[ZEPHYR_VAL__KERNEL
].address
== 0) {
651 LOG_ERROR("Can't obtain kernel struct from Zephyr");
655 if (rtos
->symbols
[ZEPHYR_VAL__KERNEL_OPENOCD_OFFSETS
].address
== 0) {
656 LOG_ERROR("Please build Zephyr with CONFIG_OPENOCD option set");
660 retval
= target_read_u8(rtos
->target
,
661 rtos
->symbols
[ZEPHYR_VAL__KERNEL_OPENOCD_SIZE_T_SIZE
].address
,
663 if (retval
!= ERROR_OK
) {
664 LOG_ERROR("Couldn't determine size of size_t from host");
668 if (param
->size_width
!= 4) {
669 LOG_ERROR("Only size_t of 4 bytes are supported");
673 if (rtos
->symbols
[ZEPHYR_VAL__KERNEL_OPENOCD_NUM_OFFSETS
].address
) {
674 retval
= target_read_u32(rtos
->target
,
675 rtos
->symbols
[ZEPHYR_VAL__KERNEL_OPENOCD_NUM_OFFSETS
].address
,
676 ¶m
->num_offsets
);
677 if (retval
!= ERROR_OK
) {
678 LOG_ERROR("Couldn't not fetch number of offsets from Zephyr");
682 if (param
->num_offsets
<= OFFSET_T_STACK_POINTER
) {
683 LOG_ERROR("Number of offsets too small");
687 retval
= target_read_u32(rtos
->target
,
688 rtos
->symbols
[ZEPHYR_VAL__KERNEL_OPENOCD_OFFSETS
].address
,
689 ¶m
->offsets
[OFFSET_VERSION
]);
690 if (retval
!= ERROR_OK
) {
691 LOG_ERROR("Couldn't not fetch offsets from Zephyr");
695 if (param
->offsets
[OFFSET_VERSION
] > 1) {
696 LOG_ERROR("Unexpected OpenOCD support version %" PRIu32
,
697 param
->offsets
[OFFSET_VERSION
]);
700 switch (param
->offsets
[OFFSET_VERSION
]) {
702 param
->num_offsets
= OFFSET_T_STACK_POINTER
+ 1;
705 param
->num_offsets
= OFFSET_T_COOP_FLOAT
+ 1;
709 /* We can fetch the whole array for version 0, as they're supposed
712 address
= rtos
->symbols
[ZEPHYR_VAL__KERNEL_OPENOCD_OFFSETS
].address
;
713 for (size_t i
= 0; i
< OFFSET_MAX
; i
++, address
+= param
->size_width
) {
714 if (i
>= param
->num_offsets
) {
715 param
->offsets
[i
] = UNIMPLEMENTED
;
719 retval
= target_read_u32(rtos
->target
, address
, ¶m
->offsets
[i
]);
720 if (retval
!= ERROR_OK
) {
721 LOG_ERROR("Could not fetch offsets from Zephyr");
726 LOG_DEBUG("Zephyr OpenOCD support version %" PRId32
,
727 param
->offsets
[OFFSET_VERSION
]);
729 uint32_t current_thread
;
730 retval
= target_read_u32(rtos
->target
,
731 zephyr_kptr(rtos
, OFFSET_K_CURR_THREAD
), ¤t_thread
);
732 if (retval
!= ERROR_OK
) {
733 LOG_ERROR("Could not obtain current thread ID");
737 retval
= zephyr_fetch_thread_list(rtos
, current_thread
);
738 if (retval
!= ERROR_OK
) {
739 LOG_ERROR("Could not obtain thread list");
746 static int zephyr_get_thread_reg_list(struct rtos
*rtos
, int64_t thread_id
,
747 struct rtos_reg
**reg_list
, int *num_regs
)
749 struct zephyr_params
*params
;
750 struct rtos_reg
*callee_saved_reg_list
= NULL
;
754 LOG_INFO("Getting thread %" PRId64
" reg list", thread_id
);
762 params
= rtos
->rtos_specific_params
;
766 addr
= thread_id
+ params
->offsets
[OFFSET_T_STACK_POINTER
]
767 - params
->callee_saved_stacking
->register_offsets
[0].offset
;
769 retval
= params
->get_cpu_state(rtos
, &addr
, params
, callee_saved_reg_list
, reg_list
, num_regs
);
771 free(callee_saved_reg_list
);
776 static int zephyr_get_symbol_list_to_lookup(struct symbol_table_elem
**symbol_list
)
778 *symbol_list
= malloc(sizeof(zephyr_symbol_list
));
780 LOG_ERROR("Out of memory");
784 memcpy(*symbol_list
, zephyr_symbol_list
, sizeof(zephyr_symbol_list
));
788 struct rtos_type zephyr_rtos
= {
791 .detect_rtos
= zephyr_detect_rtos
,
792 .create
= zephyr_create
,
793 .update_threads
= zephyr_update_threads
,
794 .get_thread_reg_list
= zephyr_get_thread_reg_list
,
795 .get_symbol_list_to_lookup
= zephyr_get_symbol_list_to_lookup
,
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)