xtensa: update XDM register map for TRAX support
[openocd.git] / src / target / xtensa / xtensa_debug_module.c
1 // SPDX-License-Identifier: GPL-2.0-or-later
2
3 /***************************************************************************
4 * Xtensa Debug Module (XDM) Support for OpenOCD *
5 * Copyright (C) 2020-2022 Cadence Design Systems, Inc. *
6 * Copyright (C) 2019 Espressif Systems Ltd. *
7 ***************************************************************************/
8
9 #ifdef HAVE_CONFIG_H
10 #include <config.h>
11 #endif
12
13 #include <helper/align.h>
14 #include "xtensa_debug_module.h"
15
16 #define TAPINS_PWRCTL 0x08
17 #define TAPINS_PWRSTAT 0x09
18 #define TAPINS_NARSEL 0x1C
19 #define TAPINS_IDCODE 0x1E
20 #define TAPINS_BYPASS 0x1F
21
22 #define TAPINS_PWRCTL_LEN 8
23 #define TAPINS_PWRSTAT_LEN 8
24 #define TAPINS_NARSEL_ADRLEN 8
25 #define TAPINS_NARSEL_DATALEN 32
26 #define TAPINS_IDCODE_LEN 32
27 #define TAPINS_BYPASS_LEN 1
28
29 /* Table of power register offsets for APB space */
30 static const struct xtensa_dm_pwr_reg_offsets xdm_pwr_regs[XDMREG_PWRNUM] =
31 XTENSA_DM_PWR_REG_OFFSETS;
32
33 /* Table of debug register offsets for Nexus and APB space */
34 static const struct xtensa_dm_reg_offsets xdm_regs[XDMREG_NUM] =
35 XTENSA_DM_REG_OFFSETS;
36
37 static enum xtensa_dm_reg xtensa_dm_regaddr_to_id(uint32_t addr)
38 {
39 enum xtensa_dm_reg id;
40 uint32_t addr_masked = (addr & (XTENSA_DM_APB_ALIGN - 1));
41 for (id = XDMREG_TRAXID; id < XDMREG_NUM; id++)
42 if (xdm_regs[id].apb == addr_masked)
43 break;
44 return id;
45 }
46
47 static void xtensa_dm_add_set_ir(struct xtensa_debug_module *dm, uint8_t value)
48 {
49 struct scan_field field;
50 uint8_t t[4] = { 0, 0, 0, 0 };
51
52 memset(&field, 0, sizeof(field));
53 field.num_bits = dm->tap->ir_length;
54 field.out_value = t;
55 buf_set_u32(t, 0, field.num_bits, value);
56 jtag_add_ir_scan(dm->tap, &field, TAP_IDLE);
57 }
58
59 static void xtensa_dm_add_dr_scan(struct xtensa_debug_module *dm,
60 int len,
61 const uint8_t *src,
62 uint8_t *dest,
63 tap_state_t endstate)
64 {
65 struct scan_field field;
66
67 memset(&field, 0, sizeof(field));
68 field.num_bits = len;
69 field.out_value = src;
70 field.in_value = dest;
71 jtag_add_dr_scan(dm->tap, 1, &field, endstate);
72 }
73
74 int xtensa_dm_init(struct xtensa_debug_module *dm, const struct xtensa_debug_module_config *cfg)
75 {
76 if (!dm || !cfg)
77 return ERROR_FAIL;
78 if (!IS_ALIGNED(cfg->ap_offset, XTENSA_DM_APB_ALIGN)) {
79 LOG_ERROR("Xtensa DM APB offset must be aligned to a %dKB multiple",
80 XTENSA_DM_APB_ALIGN / 1024);
81 return ERROR_FAIL;
82 }
83
84 dm->pwr_ops = cfg->pwr_ops;
85 dm->dbg_ops = cfg->dbg_ops;
86 dm->tap = cfg->tap;
87 dm->queue_tdi_idle = cfg->queue_tdi_idle;
88 dm->queue_tdi_idle_arg = cfg->queue_tdi_idle_arg;
89 dm->dap = cfg->dap;
90 dm->debug_ap = cfg->debug_ap;
91 dm->debug_apsel = cfg->debug_apsel;
92 dm->ap_offset = cfg->ap_offset;
93 return ERROR_OK;
94 }
95
96 void xtensa_dm_deinit(struct xtensa_debug_module *dm)
97 {
98 if (dm->debug_ap) {
99 dap_put_ap(dm->debug_ap);
100 dm->debug_ap = NULL;
101 }
102 }
103
104 int xtensa_dm_poll(struct xtensa_debug_module *dm)
105 {
106 /* Check if debug_ap is available to prevent segmentation fault.
107 * If the re-examination after an error does not find a MEM-AP
108 * (e.g. the target stopped communicating), debug_ap pointer
109 * can suddenly become NULL.
110 */
111 return (!dm || (dm->dap && !dm->debug_ap)) ? ERROR_FAIL : ERROR_OK;
112 }
113
114 int xtensa_dm_examine(struct xtensa_debug_module *dm)
115 {
116 struct adiv5_dap *swjdp = dm->dap;
117 int retval = ERROR_OK;
118
119 if (swjdp) {
120 LOG_DEBUG("DM examine: DAP AP select %d", dm->debug_apsel);
121 if (dm->debug_ap) {
122 dap_put_ap(dm->debug_ap);
123 dm->debug_ap = NULL;
124 }
125 if (dm->debug_apsel == DP_APSEL_INVALID) {
126 LOG_DEBUG("DM examine: search for APB-type MEM-AP...");
127 /* TODO: Determine whether AP_TYPE_AXI_AP APs can be supported... */
128 retval = dap_find_get_ap(swjdp, AP_TYPE_APB_AP, &dm->debug_ap);
129 if (retval != ERROR_OK) {
130 LOG_ERROR("Could not find MEM-AP to control the core");
131 return retval;
132 }
133 } else {
134 dm->debug_ap = dap_get_ap(swjdp, dm->debug_apsel);
135 }
136
137 /* TODO: Allow a user-specified AP instead of relying on AP_TYPE_APB_AP */
138 dm->debug_apsel = dm->debug_ap->ap_num;
139 LOG_DEBUG("DM examine: Setting apsel to %d", dm->debug_apsel);
140
141 /* Leave (only) generic DAP stuff for debugport_init(); */
142 dm->debug_ap->memaccess_tck = 8;
143
144 retval = mem_ap_init(dm->debug_ap);
145 if (retval != ERROR_OK) {
146 LOG_ERROR("MEM-AP init failed: %d", retval);
147 return retval;
148 }
149
150 /* TODO: how to set autoincrement range? Hard-code it to 1024 bytes for now */
151 dm->debug_ap->tar_autoincr_block = (1 << 10);
152 }
153
154 return retval;
155 }
156
157 int xtensa_dm_queue_enable(struct xtensa_debug_module *dm)
158 {
159 return dm->dbg_ops->queue_reg_write(dm, XDMREG_DCRSET, OCDDCR_ENABLEOCD);
160 }
161
162 int xtensa_dm_queue_reg_read(struct xtensa_debug_module *dm, enum xtensa_dm_reg reg, uint8_t *value)
163 {
164 if (reg >= XDMREG_NUM) {
165 LOG_ERROR("Invalid DBG reg ID %d!", reg);
166 return ERROR_FAIL;
167 }
168 if (dm->dap)
169 /* NOTE: Future optimization: mem_ap_read_u32() offers higher performance with
170 * queued reads, but requires an API change to pass value as a 32-bit pointer.
171 */
172 return mem_ap_read_buf(dm->debug_ap, value, 4, 1, xdm_regs[reg].apb + dm->ap_offset);
173 uint8_t regdata = (xdm_regs[reg].nar << 1) | 0;
174 uint8_t dummy[4] = { 0, 0, 0, 0 };
175 xtensa_dm_add_set_ir(dm, TAPINS_NARSEL);
176 xtensa_dm_add_dr_scan(dm, TAPINS_NARSEL_ADRLEN, &regdata, NULL, TAP_IDLE);
177 xtensa_dm_add_dr_scan(dm, TAPINS_NARSEL_DATALEN, dummy, value, TAP_IDLE);
178 return ERROR_OK;
179 }
180
181 int xtensa_dm_queue_reg_write(struct xtensa_debug_module *dm, enum xtensa_dm_reg reg, uint32_t value)
182 {
183 if (reg >= XDMREG_NUM) {
184 LOG_ERROR("Invalid DBG reg ID %d!", reg);
185 return ERROR_FAIL;
186 }
187 if (dm->dap)
188 return mem_ap_write_u32(dm->debug_ap, xdm_regs[reg].apb + dm->ap_offset, value);
189 uint8_t regdata = (xdm_regs[reg].nar << 1) | 1;
190 uint8_t valdata[] = { value, value >> 8, value >> 16, value >> 24 };
191 xtensa_dm_add_set_ir(dm, TAPINS_NARSEL);
192 xtensa_dm_add_dr_scan(dm, TAPINS_NARSEL_ADRLEN, &regdata, NULL, TAP_IDLE);
193 xtensa_dm_add_dr_scan(dm, TAPINS_NARSEL_DATALEN, valdata, NULL, TAP_IDLE);
194 return ERROR_OK;
195 }
196
197 int xtensa_dm_queue_pwr_reg_read(struct xtensa_debug_module *dm,
198 enum xtensa_dm_pwr_reg reg,
199 uint8_t *data,
200 uint32_t clear)
201 {
202 if (reg >= XDMREG_PWRNUM) {
203 LOG_ERROR("Invalid PWR reg ID %d!", reg);
204 return ERROR_FAIL;
205 }
206 if (dm->dap) {
207 /* NOTE: Future optimization: mem_ap_read_u32() offers higher performance with
208 * queued reads, but requires an API change to pass value as a 32-bit pointer.
209 */
210 uint32_t apbreg = xdm_pwr_regs[reg].apb + dm->ap_offset;
211 int retval = mem_ap_read_buf(dm->debug_ap, data, 4, 1, apbreg);
212 if (retval == ERROR_OK)
213 retval = mem_ap_write_u32(dm->debug_ap, apbreg, clear);
214 return retval;
215 }
216 uint8_t value_clr = (uint8_t)clear;
217 uint8_t tap_insn = (reg == XDMREG_PWRCTL) ? TAPINS_PWRCTL : TAPINS_PWRSTAT;
218 int tap_insn_sz = (reg == XDMREG_PWRCTL) ? TAPINS_PWRCTL_LEN : TAPINS_PWRSTAT_LEN;
219 xtensa_dm_add_set_ir(dm, tap_insn);
220 xtensa_dm_add_dr_scan(dm, tap_insn_sz, &value_clr, data, TAP_IDLE);
221 return ERROR_OK;
222 }
223
224 int xtensa_dm_queue_pwr_reg_write(struct xtensa_debug_module *dm,
225 enum xtensa_dm_pwr_reg reg,
226 uint32_t data)
227 {
228 if (reg >= XDMREG_PWRNUM) {
229 LOG_ERROR("Invalid PWR reg ID %d!", reg);
230 return ERROR_FAIL;
231 }
232 if (dm->dap) {
233 uint32_t apbreg = xdm_pwr_regs[reg].apb + dm->ap_offset;
234 return mem_ap_write_u32(dm->debug_ap, apbreg, data);
235 }
236 uint8_t tap_insn = (reg == XDMREG_PWRCTL) ? TAPINS_PWRCTL : TAPINS_PWRSTAT;
237 int tap_insn_sz = (reg == XDMREG_PWRCTL) ? TAPINS_PWRCTL_LEN : TAPINS_PWRSTAT_LEN;
238 uint8_t value = (uint8_t)data;
239 xtensa_dm_add_set_ir(dm, tap_insn);
240 xtensa_dm_add_dr_scan(dm, tap_insn_sz, &value, NULL, TAP_IDLE);
241 return ERROR_OK;
242 }
243
244 int xtensa_dm_device_id_read(struct xtensa_debug_module *dm)
245 {
246 uint8_t id_buf[sizeof(uint32_t)];
247
248 dm->dbg_ops->queue_reg_read(dm, XDMREG_OCDID, id_buf);
249 xtensa_dm_queue_tdi_idle(dm);
250 int res = xtensa_dm_queue_execute(dm);
251 if (res != ERROR_OK)
252 return res;
253 dm->device_id = buf_get_u32(id_buf, 0, 32);
254 return ERROR_OK;
255 }
256
257 int xtensa_dm_power_status_read(struct xtensa_debug_module *dm, uint32_t clear)
258 {
259 uint8_t stat_buf[sizeof(uint32_t)] = { 0, 0, 0, 0 };
260 uint8_t stath_buf[sizeof(uint32_t)] = { 0, 0, 0, 0 };
261
262 /* TODO: JTAG does not work when PWRCTL_JTAGDEBUGUSE is not set.
263 * It is set in xtensa_examine(), need to move reading of XDMREG_OCDID out of this function */
264 /* dm->dbg_ops->queue_reg_read(dm, XDMREG_OCDID, id_buf);
265 *Read reset state */
266 dm->pwr_ops->queue_reg_read(dm, XDMREG_PWRSTAT, stat_buf, clear);
267 dm->pwr_ops->queue_reg_read(dm, XDMREG_PWRSTAT, stath_buf, clear);
268 xtensa_dm_queue_tdi_idle(dm);
269 int res = xtensa_dm_queue_execute(dm);
270 if (res != ERROR_OK)
271 return res;
272 dm->power_status.stat = buf_get_u32(stat_buf, 0, 32);
273 dm->power_status.stath = buf_get_u32(stath_buf, 0, 32);
274 return res;
275 }
276
277 int xtensa_dm_core_status_read(struct xtensa_debug_module *dm)
278 {
279 uint8_t dsr_buf[sizeof(uint32_t)];
280
281 xtensa_dm_queue_enable(dm);
282 dm->dbg_ops->queue_reg_read(dm, XDMREG_DSR, dsr_buf);
283 xtensa_dm_queue_tdi_idle(dm);
284 int res = xtensa_dm_queue_execute(dm);
285 if (res != ERROR_OK)
286 return res;
287 dm->core_status.dsr = buf_get_u32(dsr_buf, 0, 32);
288 return res;
289 }
290
291 int xtensa_dm_core_status_clear(struct xtensa_debug_module *dm, xtensa_dsr_t bits)
292 {
293 dm->dbg_ops->queue_reg_write(dm, XDMREG_DSR, bits);
294 xtensa_dm_queue_tdi_idle(dm);
295 return xtensa_dm_queue_execute(dm);
296 }
297
298 int xtensa_dm_read(struct xtensa_debug_module *dm, uint32_t addr, uint32_t *val)
299 {
300 enum xtensa_dm_reg reg = xtensa_dm_regaddr_to_id(addr);
301 uint8_t buf[sizeof(uint32_t)];
302 if (reg < XDMREG_NUM) {
303 xtensa_dm_queue_enable(dm);
304 dm->dbg_ops->queue_reg_read(dm, reg, buf);
305 xtensa_dm_queue_tdi_idle(dm);
306 int res = xtensa_dm_queue_execute(dm);
307 if (res == ERROR_OK && val)
308 *val = buf_get_u32(buf, 0, 32);
309 return res;
310 }
311 return ERROR_FAIL;
312 }
313
314 int xtensa_dm_write(struct xtensa_debug_module *dm, uint32_t addr, uint32_t val)
315 {
316 enum xtensa_dm_reg reg = xtensa_dm_regaddr_to_id(addr);
317 if (reg < XDMREG_NUM) {
318 xtensa_dm_queue_enable(dm);
319 dm->dbg_ops->queue_reg_write(dm, reg, val);
320 xtensa_dm_queue_tdi_idle(dm);
321 return xtensa_dm_queue_execute(dm);
322 }
323 return ERROR_FAIL;
324 }
325
326 int xtensa_dm_trace_start(struct xtensa_debug_module *dm, struct xtensa_trace_start_config *cfg)
327 {
328 /*Turn off trace unit so we can start a new trace. */
329 dm->dbg_ops->queue_reg_write(dm, XDMREG_TRAXCTRL, 0);
330 xtensa_dm_queue_tdi_idle(dm);
331 int res = xtensa_dm_queue_execute(dm);
332 if (res != ERROR_OK)
333 return res;
334
335 /*Set up parameters */
336 dm->dbg_ops->queue_reg_write(dm, XDMREG_TRAXADDR, 0);
337 if (cfg->stopmask != XTENSA_STOPMASK_DISABLED) {
338 dm->dbg_ops->queue_reg_write(dm, XDMREG_PCMATCHCTRL,
339 (cfg->stopmask << PCMATCHCTRL_PCML_SHIFT));
340 dm->dbg_ops->queue_reg_write(dm, XDMREG_TRIGGERPC, cfg->stoppc);
341 }
342 dm->dbg_ops->queue_reg_write(dm, XDMREG_DELAYCNT, cfg->after);
343 /*Options are mostly hardcoded for now. ToDo: make this more configurable. */
344 dm->dbg_ops->queue_reg_write(
345 dm,
346 XDMREG_TRAXCTRL,
347 TRAXCTRL_TREN |
348 ((cfg->stopmask != XTENSA_STOPMASK_DISABLED) ? TRAXCTRL_PCMEN : 0) | TRAXCTRL_TMEN |
349 (cfg->after_is_words ? 0 : TRAXCTRL_CNTU) | (0 << TRAXCTRL_SMPER_SHIFT) | TRAXCTRL_PTOWS);
350 xtensa_dm_queue_tdi_idle(dm);
351 return xtensa_dm_queue_execute(dm);
352 }
353
354 int xtensa_dm_trace_stop(struct xtensa_debug_module *dm, bool pto_enable)
355 {
356 uint8_t traxctl_buf[sizeof(uint32_t)];
357 uint32_t traxctl;
358 struct xtensa_trace_status trace_status;
359
360 dm->dbg_ops->queue_reg_read(dm, XDMREG_TRAXCTRL, traxctl_buf);
361 xtensa_dm_queue_tdi_idle(dm);
362 int res = xtensa_dm_queue_execute(dm);
363 if (res != ERROR_OK)
364 return res;
365 traxctl = buf_get_u32(traxctl_buf, 0, 32);
366
367 if (!pto_enable)
368 traxctl &= ~(TRAXCTRL_PTOWS | TRAXCTRL_PTOWT);
369
370 dm->dbg_ops->queue_reg_write(dm, XDMREG_TRAXCTRL, traxctl | TRAXCTRL_TRSTP);
371 xtensa_dm_queue_tdi_idle(dm);
372 res = xtensa_dm_queue_execute(dm);
373 if (res != ERROR_OK)
374 return res;
375
376 /*Check current status of trace hardware */
377 res = xtensa_dm_trace_status_read(dm, &trace_status);
378 if (res != ERROR_OK)
379 return res;
380
381 if (trace_status.stat & TRAXSTAT_TRACT) {
382 LOG_ERROR("Failed to stop tracing (0x%x)!", trace_status.stat);
383 return ERROR_FAIL;
384 }
385 return ERROR_OK;
386 }
387
388 int xtensa_dm_trace_status_read(struct xtensa_debug_module *dm, struct xtensa_trace_status *status)
389 {
390 uint8_t traxstat_buf[sizeof(uint32_t)];
391
392 dm->dbg_ops->queue_reg_read(dm, XDMREG_TRAXSTAT, traxstat_buf);
393 xtensa_dm_queue_tdi_idle(dm);
394 int res = xtensa_dm_queue_execute(dm);
395 if (res == ERROR_OK && status)
396 status->stat = buf_get_u32(traxstat_buf, 0, 32);
397 return res;
398 }
399
400 int xtensa_dm_trace_config_read(struct xtensa_debug_module *dm, struct xtensa_trace_config *config)
401 {
402 uint8_t traxctl_buf[sizeof(uint32_t)];
403 uint8_t memadrstart_buf[sizeof(uint32_t)];
404 uint8_t memadrend_buf[sizeof(uint32_t)];
405 uint8_t adr_buf[sizeof(uint32_t)];
406
407 if (!config)
408 return ERROR_FAIL;
409
410 dm->dbg_ops->queue_reg_read(dm, XDMREG_TRAXCTRL, traxctl_buf);
411 dm->dbg_ops->queue_reg_read(dm, XDMREG_MEMADDRSTART, memadrstart_buf);
412 dm->dbg_ops->queue_reg_read(dm, XDMREG_MEMADDREND, memadrend_buf);
413 dm->dbg_ops->queue_reg_read(dm, XDMREG_TRAXADDR, adr_buf);
414 xtensa_dm_queue_tdi_idle(dm);
415 int res = xtensa_dm_queue_execute(dm);
416 if (res == ERROR_OK) {
417 config->ctrl = buf_get_u32(traxctl_buf, 0, 32);
418 config->memaddr_start = buf_get_u32(memadrstart_buf, 0, 32);
419 config->memaddr_end = buf_get_u32(memadrend_buf, 0, 32);
420 config->addr = buf_get_u32(adr_buf, 0, 32);
421 }
422 return res;
423 }
424
425 int xtensa_dm_trace_data_read(struct xtensa_debug_module *dm, uint8_t *dest, uint32_t size)
426 {
427 if (!dest)
428 return ERROR_FAIL;
429
430 for (unsigned int i = 0; i < size / 4; i++)
431 dm->dbg_ops->queue_reg_read(dm, XDMREG_TRAXDATA, &dest[i * 4]);
432 xtensa_dm_queue_tdi_idle(dm);
433 return xtensa_dm_queue_execute(dm);
434 }
435
436 int xtensa_dm_perfmon_enable(struct xtensa_debug_module *dm, int counter_id,
437 const struct xtensa_perfmon_config *config)
438 {
439 if (!config)
440 return ERROR_FAIL;
441
442 uint8_t pmstat_buf[4];
443 uint32_t pmctrl = ((config->tracelevel) << 4) +
444 (config->select << 8) +
445 (config->mask << 16) +
446 (config->kernelcnt << 3);
447
448 /* enable performance monitor */
449 dm->dbg_ops->queue_reg_write(dm, XDMREG_PMG, 0x1);
450 /* reset counter */
451 dm->dbg_ops->queue_reg_write(dm, XDMREG_PM0 + counter_id, 0);
452 dm->dbg_ops->queue_reg_write(dm, XDMREG_PMCTRL0 + counter_id, pmctrl);
453 dm->dbg_ops->queue_reg_read(dm, XDMREG_PMSTAT0 + counter_id, pmstat_buf);
454 xtensa_dm_queue_tdi_idle(dm);
455 return xtensa_dm_queue_execute(dm);
456 }
457
458 int xtensa_dm_perfmon_dump(struct xtensa_debug_module *dm, int counter_id,
459 struct xtensa_perfmon_result *out_result)
460 {
461 uint8_t pmstat_buf[4];
462 uint8_t pmcount_buf[4];
463
464 dm->dbg_ops->queue_reg_read(dm, XDMREG_PMSTAT0 + counter_id, pmstat_buf);
465 dm->dbg_ops->queue_reg_read(dm, XDMREG_PM0 + counter_id, pmcount_buf);
466 xtensa_dm_queue_tdi_idle(dm);
467 int res = xtensa_dm_queue_execute(dm);
468 if (res == ERROR_OK) {
469 uint32_t stat = buf_get_u32(pmstat_buf, 0, 32);
470 uint64_t result = buf_get_u32(pmcount_buf, 0, 32);
471
472 /* TODO: if counter # counter_id+1 has 'select' set to 1, use its value as the
473 * high 32 bits of the counter. */
474 if (out_result) {
475 out_result->overflow = ((stat & 1) != 0);
476 out_result->value = result;
477 }
478 }
479
480 return res;
481 }

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)