jtag: linuxgpiod: drop extra parenthesis
[openocd.git] / src / target / armv7a.c
1 // SPDX-License-Identifier: GPL-2.0-or-later
2
3 /***************************************************************************
4 * Copyright (C) 2009 by David Brownell *
5 * *
6 * Copyright (C) ST-Ericsson SA 2011 michel.jaouen@stericsson.com *
7 ***************************************************************************/
8
9 #ifdef HAVE_CONFIG_H
10 #include "config.h"
11 #endif
12
13 #include <helper/replacements.h>
14
15 #include "armv7a.h"
16 #include "armv7a_mmu.h"
17 #include "arm_disassembler.h"
18
19 #include "register.h"
20 #include <helper/binarybuffer.h>
21 #include <helper/command.h>
22
23 #include <stdlib.h>
24 #include <string.h>
25 #include <unistd.h>
26
27 #include "arm_opcodes.h"
28 #include "target.h"
29 #include "target_type.h"
30 #include "smp.h"
31
32 static void armv7a_show_fault_registers(struct target *target)
33 {
34 uint32_t dfsr, ifsr, dfar, ifar;
35 struct armv7a_common *armv7a = target_to_armv7a(target);
36 struct arm_dpm *dpm = armv7a->arm.dpm;
37 int retval;
38
39 retval = dpm->prepare(dpm);
40 if (retval != ERROR_OK)
41 return;
42
43 /* ARMV4_5_MRC(cpnum, op1, r0, crn, crm, op2) */
44
45 /* c5/c0 - {data, instruction} fault status registers */
46 retval = dpm->instr_read_data_r0(dpm,
47 ARMV4_5_MRC(15, 0, 0, 5, 0, 0),
48 &dfsr);
49 if (retval != ERROR_OK)
50 goto done;
51
52 retval = dpm->instr_read_data_r0(dpm,
53 ARMV4_5_MRC(15, 0, 0, 5, 0, 1),
54 &ifsr);
55 if (retval != ERROR_OK)
56 goto done;
57
58 /* c6/c0 - {data, instruction} fault address registers */
59 retval = dpm->instr_read_data_r0(dpm,
60 ARMV4_5_MRC(15, 0, 0, 6, 0, 0),
61 &dfar);
62 if (retval != ERROR_OK)
63 goto done;
64
65 retval = dpm->instr_read_data_r0(dpm,
66 ARMV4_5_MRC(15, 0, 0, 6, 0, 2),
67 &ifar);
68 if (retval != ERROR_OK)
69 goto done;
70
71 LOG_USER("Data fault registers DFSR: %8.8" PRIx32
72 ", DFAR: %8.8" PRIx32, dfsr, dfar);
73 LOG_USER("Instruction fault registers IFSR: %8.8" PRIx32
74 ", IFAR: %8.8" PRIx32, ifsr, ifar);
75
76 done:
77 /* (void) */ dpm->finish(dpm);
78 }
79
80
81 /* retrieve main id register */
82 static int armv7a_read_midr(struct target *target)
83 {
84 int retval = ERROR_FAIL;
85 struct armv7a_common *armv7a = target_to_armv7a(target);
86 struct arm_dpm *dpm = armv7a->arm.dpm;
87 uint32_t midr;
88 retval = dpm->prepare(dpm);
89 if (retval != ERROR_OK)
90 goto done;
91 /* MRC p15,0,<Rd>,c0,c0,0; read main id register*/
92
93 retval = dpm->instr_read_data_r0(dpm,
94 ARMV4_5_MRC(15, 0, 0, 0, 0, 0),
95 &midr);
96 if (retval != ERROR_OK)
97 goto done;
98
99 armv7a->rev = (midr & 0xf);
100 armv7a->partnum = (midr >> 4) & 0xfff;
101 armv7a->arch = (midr >> 16) & 0xf;
102 armv7a->variant = (midr >> 20) & 0xf;
103 armv7a->implementor = (midr >> 24) & 0xff;
104 LOG_DEBUG("%s rev %" PRIx32 ", partnum %" PRIx32 ", arch %" PRIx32
105 ", variant %" PRIx32 ", implementor %" PRIx32,
106 target->cmd_name,
107 armv7a->rev,
108 armv7a->partnum,
109 armv7a->arch,
110 armv7a->variant,
111 armv7a->implementor);
112
113 done:
114 dpm->finish(dpm);
115 return retval;
116 }
117
118 int armv7a_read_ttbcr(struct target *target)
119 {
120 struct armv7a_common *armv7a = target_to_armv7a(target);
121 struct arm_dpm *dpm = armv7a->arm.dpm;
122 uint32_t ttbcr, ttbcr_n;
123 int ttbidx;
124 int retval;
125
126 retval = dpm->prepare(dpm);
127 if (retval != ERROR_OK)
128 goto done;
129
130 /* MRC p15,0,<Rt>,c2,c0,2 ; Read CP15 Translation Table Base Control Register*/
131 retval = dpm->instr_read_data_r0(dpm,
132 ARMV4_5_MRC(15, 0, 0, 2, 0, 2),
133 &ttbcr);
134 if (retval != ERROR_OK)
135 goto done;
136
137 LOG_DEBUG("ttbcr %" PRIx32, ttbcr);
138
139 ttbcr_n = ttbcr & 0x7;
140 armv7a->armv7a_mmu.ttbcr = ttbcr;
141 armv7a->armv7a_mmu.cached = 1;
142
143 for (ttbidx = 0; ttbidx < 2; ttbidx++) {
144 /* MRC p15,0,<Rt>,c2,c0,ttbidx */
145 retval = dpm->instr_read_data_r0(dpm,
146 ARMV4_5_MRC(15, 0, 0, 2, 0, ttbidx),
147 &armv7a->armv7a_mmu.ttbr[ttbidx]);
148 if (retval != ERROR_OK)
149 goto done;
150 }
151
152 /*
153 * ARM Architecture Reference Manual (ARMv7-A and ARMv7-R edition),
154 * document # ARM DDI 0406C
155 */
156 armv7a->armv7a_mmu.ttbr_range[0] = 0xffffffff >> ttbcr_n;
157 armv7a->armv7a_mmu.ttbr_range[1] = 0xffffffff;
158 armv7a->armv7a_mmu.ttbr_mask[0] = 0xffffffff << (14 - ttbcr_n);
159 armv7a->armv7a_mmu.ttbr_mask[1] = 0xffffffff << 14;
160 armv7a->armv7a_mmu.cached = 1;
161
162 retval = armv7a_read_midr(target);
163 if (retval != ERROR_OK)
164 goto done;
165
166 /* FIXME: why this special case based on part number? */
167 if ((armv7a->partnum & 0xf) == 0) {
168 /* ARM DDI 0344H , ARM DDI 0407F */
169 armv7a->armv7a_mmu.ttbr_mask[0] = 7 << (32 - ttbcr_n);
170 }
171
172 LOG_DEBUG("ttbr1 %s, ttbr0_mask %" PRIx32 " ttbr1_mask %" PRIx32,
173 (ttbcr_n != 0) ? "used" : "not used",
174 armv7a->armv7a_mmu.ttbr_mask[0],
175 armv7a->armv7a_mmu.ttbr_mask[1]);
176
177 done:
178 dpm->finish(dpm);
179 return retval;
180 }
181
182 /* FIXME: remove it */
183 static int armv7a_l2x_cache_init(struct target *target, uint32_t base, uint32_t way)
184 {
185 struct armv7a_l2x_cache *l2x_cache;
186 struct target_list *head;
187
188 struct armv7a_common *armv7a = target_to_armv7a(target);
189 l2x_cache = calloc(1, sizeof(struct armv7a_l2x_cache));
190 l2x_cache->base = base;
191 l2x_cache->way = way;
192 /*LOG_INFO("cache l2 initialized base %x way %d",
193 l2x_cache->base,l2x_cache->way);*/
194 if (armv7a->armv7a_mmu.armv7a_cache.outer_cache)
195 LOG_INFO("outer cache already initialized\n");
196 armv7a->armv7a_mmu.armv7a_cache.outer_cache = l2x_cache;
197 /* initialize all target in this cluster (smp target)
198 * l2 cache must be configured after smp declaration */
199 foreach_smp_target(head, target->smp_targets) {
200 struct target *curr = head->target;
201 if (curr != target) {
202 armv7a = target_to_armv7a(curr);
203 if (armv7a->armv7a_mmu.armv7a_cache.outer_cache)
204 LOG_ERROR("smp target : outer cache already initialized\n");
205 armv7a->armv7a_mmu.armv7a_cache.outer_cache = l2x_cache;
206 }
207 }
208 return JIM_OK;
209 }
210
211 /* FIXME: remove it */
212 COMMAND_HANDLER(handle_cache_l2x)
213 {
214 struct target *target = get_current_target(CMD_CTX);
215 uint32_t base, way;
216
217 if (CMD_ARGC != 2)
218 return ERROR_COMMAND_SYNTAX_ERROR;
219
220 /* command_print(CMD, "%s %s", CMD_ARGV[0], CMD_ARGV[1]); */
221 COMMAND_PARSE_NUMBER(u32, CMD_ARGV[0], base);
222 COMMAND_PARSE_NUMBER(u32, CMD_ARGV[1], way);
223
224 /* AP address is in bits 31:24 of DP_SELECT */
225 armv7a_l2x_cache_init(target, base, way);
226
227 return ERROR_OK;
228 }
229
230 int armv7a_handle_cache_info_command(struct command_invocation *cmd,
231 struct armv7a_cache_common *armv7a_cache)
232 {
233 struct armv7a_l2x_cache *l2x_cache = (struct armv7a_l2x_cache *)
234 (armv7a_cache->outer_cache);
235
236 int cl;
237
238 if (armv7a_cache->info == -1) {
239 command_print(cmd, "cache not yet identified");
240 return ERROR_OK;
241 }
242
243 for (cl = 0; cl < armv7a_cache->loc; cl++) {
244 struct armv7a_arch_cache *arch = &(armv7a_cache->arch[cl]);
245
246 if (arch->ctype & 1) {
247 command_print(cmd,
248 "L%d I-Cache: linelen %" PRIu32
249 ", associativity %" PRIu32
250 ", nsets %" PRIu32
251 ", cachesize %" PRIu32 " KBytes",
252 cl+1,
253 arch->i_size.linelen,
254 arch->i_size.associativity,
255 arch->i_size.nsets,
256 arch->i_size.cachesize);
257 }
258
259 if (arch->ctype >= 2) {
260 command_print(cmd,
261 "L%d D-Cache: linelen %" PRIu32
262 ", associativity %" PRIu32
263 ", nsets %" PRIu32
264 ", cachesize %" PRIu32 " KBytes",
265 cl+1,
266 arch->d_u_size.linelen,
267 arch->d_u_size.associativity,
268 arch->d_u_size.nsets,
269 arch->d_u_size.cachesize);
270 }
271 }
272
273 if (l2x_cache)
274 command_print(cmd, "Outer unified cache Base Address 0x%" PRIx32 ", %" PRIu32 " ways",
275 l2x_cache->base, l2x_cache->way);
276
277 return ERROR_OK;
278 }
279
280 /* retrieve core id cluster id */
281 static int armv7a_read_mpidr(struct target *target)
282 {
283 int retval = ERROR_FAIL;
284 struct armv7a_common *armv7a = target_to_armv7a(target);
285 struct arm_dpm *dpm = armv7a->arm.dpm;
286 uint32_t mpidr;
287 retval = dpm->prepare(dpm);
288 if (retval != ERROR_OK)
289 goto done;
290 /* MRC p15,0,<Rd>,c0,c0,5; read Multiprocessor ID register*/
291
292 retval = dpm->instr_read_data_r0(dpm,
293 ARMV4_5_MRC(15, 0, 0, 0, 0, 5),
294 &mpidr);
295 if (retval != ERROR_OK)
296 goto done;
297
298 /* Is register in Multiprocessing Extensions register format? */
299 if (mpidr & MPIDR_MP_EXT) {
300 LOG_DEBUG("%s: MPIDR 0x%" PRIx32, target_name(target), mpidr);
301 armv7a->multi_processor_system = (mpidr >> 30) & 1;
302 armv7a->multi_threading_processor = (mpidr >> 24) & 1;
303 armv7a->level2_id = (mpidr >> 16) & 0xf;
304 armv7a->cluster_id = (mpidr >> 8) & 0xf;
305 armv7a->cpu_id = mpidr & 0xf;
306 LOG_INFO("%s: MPIDR level2 %x, cluster %x, core %x, %s, %s",
307 target_name(target),
308 armv7a->level2_id,
309 armv7a->cluster_id,
310 armv7a->cpu_id,
311 armv7a->multi_processor_system == 0 ? "multi core" : "mono core",
312 armv7a->multi_threading_processor == 1 ? "SMT" : "no SMT");
313
314 } else
315 LOG_ERROR("MPIDR not in multiprocessor format");
316
317 done:
318 dpm->finish(dpm);
319 return retval;
320
321
322 }
323
324 static int get_cache_info(struct arm_dpm *dpm, int cl, int ct, uint32_t *cache_reg)
325 {
326 int retval = ERROR_OK;
327
328 /* select cache level */
329 retval = dpm->instr_write_data_r0(dpm,
330 ARMV4_5_MCR(15, 2, 0, 0, 0, 0),
331 (cl << 1) | (ct == 1 ? 1 : 0));
332 if (retval != ERROR_OK)
333 goto done;
334
335 retval = dpm->instr_read_data_r0(dpm,
336 ARMV4_5_MRC(15, 1, 0, 0, 0, 0),
337 cache_reg);
338 done:
339 return retval;
340 }
341
342 static struct armv7a_cachesize decode_cache_reg(uint32_t cache_reg)
343 {
344 struct armv7a_cachesize size;
345 int i = 0;
346
347 size.linelen = 16 << (cache_reg & 0x7);
348 size.associativity = ((cache_reg >> 3) & 0x3ff) + 1;
349 size.nsets = ((cache_reg >> 13) & 0x7fff) + 1;
350 size.cachesize = size.linelen * size.associativity * size.nsets / 1024;
351
352 /* compute info for set way operation on cache */
353 size.index_shift = (cache_reg & 0x7) + 4;
354 size.index = (cache_reg >> 13) & 0x7fff;
355 size.way = ((cache_reg >> 3) & 0x3ff);
356
357 while (((size.way << i) & 0x80000000) == 0)
358 i++;
359 size.way_shift = i;
360
361 return size;
362 }
363
364 int armv7a_identify_cache(struct target *target)
365 {
366 /* read cache descriptor */
367 int retval = ERROR_FAIL;
368 struct armv7a_common *armv7a = target_to_armv7a(target);
369 struct arm_dpm *dpm = armv7a->arm.dpm;
370 uint32_t csselr, clidr, ctr;
371 uint32_t cache_reg;
372 int cl, ctype;
373 struct armv7a_cache_common *cache =
374 &(armv7a->armv7a_mmu.armv7a_cache);
375
376 retval = dpm->prepare(dpm);
377 if (retval != ERROR_OK)
378 goto done;
379
380 /* retrieve CTR
381 * mrc p15, 0, r0, c0, c0, 1 @ read ctr */
382 retval = dpm->instr_read_data_r0(dpm,
383 ARMV4_5_MRC(15, 0, 0, 0, 0, 1),
384 &ctr);
385 if (retval != ERROR_OK)
386 goto done;
387
388 cache->iminline = 4UL << (ctr & 0xf);
389 cache->dminline = 4UL << ((ctr & 0xf0000) >> 16);
390 LOG_DEBUG("ctr %" PRIx32 " ctr.iminline %" PRIu32 " ctr.dminline %" PRIu32,
391 ctr, cache->iminline, cache->dminline);
392
393 /* retrieve CLIDR
394 * mrc p15, 1, r0, c0, c0, 1 @ read clidr */
395 retval = dpm->instr_read_data_r0(dpm,
396 ARMV4_5_MRC(15, 1, 0, 0, 0, 1),
397 &clidr);
398 if (retval != ERROR_OK)
399 goto done;
400
401 cache->loc = (clidr & 0x7000000) >> 24;
402 LOG_DEBUG("Number of cache levels to PoC %" PRId32, cache->loc);
403
404 /* retrieve selected cache for later restore
405 * MRC p15, 2,<Rd>, c0, c0, 0; Read CSSELR */
406 retval = dpm->instr_read_data_r0(dpm,
407 ARMV4_5_MRC(15, 2, 0, 0, 0, 0),
408 &csselr);
409 if (retval != ERROR_OK)
410 goto done;
411
412 /* retrieve all available inner caches */
413 for (cl = 0; cl < cache->loc; clidr >>= 3, cl++) {
414
415 /* isolate cache type at current level */
416 ctype = clidr & 7;
417
418 /* skip reserved values */
419 if (ctype > CACHE_LEVEL_HAS_UNIFIED_CACHE)
420 continue;
421
422 /* separate d or unified d/i cache at this level ? */
423 if (ctype & (CACHE_LEVEL_HAS_UNIFIED_CACHE | CACHE_LEVEL_HAS_D_CACHE)) {
424 /* retrieve d-cache info */
425 retval = get_cache_info(dpm, cl, 0, &cache_reg);
426 if (retval != ERROR_OK)
427 goto done;
428 cache->arch[cl].d_u_size = decode_cache_reg(cache_reg);
429
430 LOG_DEBUG("data/unified cache index %" PRIu32 " << %" PRIu32 ", way %" PRIu32 " << %" PRIu32,
431 cache->arch[cl].d_u_size.index,
432 cache->arch[cl].d_u_size.index_shift,
433 cache->arch[cl].d_u_size.way,
434 cache->arch[cl].d_u_size.way_shift);
435
436 LOG_DEBUG("cacheline %" PRIu32 " bytes %" PRIu32 " KBytes asso %" PRIu32 " ways",
437 cache->arch[cl].d_u_size.linelen,
438 cache->arch[cl].d_u_size.cachesize,
439 cache->arch[cl].d_u_size.associativity);
440 }
441
442 /* separate i-cache at this level ? */
443 if (ctype & CACHE_LEVEL_HAS_I_CACHE) {
444 /* retrieve i-cache info */
445 retval = get_cache_info(dpm, cl, 1, &cache_reg);
446 if (retval != ERROR_OK)
447 goto done;
448 cache->arch[cl].i_size = decode_cache_reg(cache_reg);
449
450 LOG_DEBUG("instruction cache index %" PRIu32 " << %" PRIu32 ", way %" PRIu32 " << %" PRIu32,
451 cache->arch[cl].i_size.index,
452 cache->arch[cl].i_size.index_shift,
453 cache->arch[cl].i_size.way,
454 cache->arch[cl].i_size.way_shift);
455
456 LOG_DEBUG("cacheline %" PRIu32 " bytes %" PRIu32 " KBytes asso %" PRIu32 " ways",
457 cache->arch[cl].i_size.linelen,
458 cache->arch[cl].i_size.cachesize,
459 cache->arch[cl].i_size.associativity);
460 }
461
462 cache->arch[cl].ctype = ctype;
463 }
464
465 /* restore selected cache */
466 dpm->instr_write_data_r0(dpm,
467 ARMV4_5_MRC(15, 2, 0, 0, 0, 0),
468 csselr);
469
470 if (retval != ERROR_OK)
471 goto done;
472
473 /* if no l2 cache initialize l1 data cache flush function function */
474 if (!armv7a->armv7a_mmu.armv7a_cache.flush_all_data_cache) {
475 armv7a->armv7a_mmu.armv7a_cache.flush_all_data_cache =
476 armv7a_cache_auto_flush_all_data;
477 }
478
479 armv7a->armv7a_mmu.armv7a_cache.info = 1;
480 done:
481 dpm->finish(dpm);
482 armv7a_read_mpidr(target);
483 return retval;
484
485 }
486
487 static int armv7a_setup_semihosting(struct target *target, int enable)
488 {
489 struct armv7a_common *armv7a = target_to_armv7a(target);
490 uint32_t vcr;
491 int ret;
492
493 ret = mem_ap_read_atomic_u32(armv7a->debug_ap,
494 armv7a->debug_base + CPUDBG_VCR,
495 &vcr);
496 if (ret < 0) {
497 LOG_ERROR("Failed to read VCR register\n");
498 return ret;
499 }
500
501 if (enable)
502 vcr |= DBG_VCR_SVC_MASK;
503 else
504 vcr &= ~DBG_VCR_SVC_MASK;
505
506 ret = mem_ap_write_atomic_u32(armv7a->debug_ap,
507 armv7a->debug_base + CPUDBG_VCR,
508 vcr);
509 if (ret < 0)
510 LOG_ERROR("Failed to write VCR register\n");
511
512 return ret;
513 }
514
515 int armv7a_init_arch_info(struct target *target, struct armv7a_common *armv7a)
516 {
517 struct arm *arm = &armv7a->arm;
518 arm->arch_info = armv7a;
519 target->arch_info = &armv7a->arm;
520 arm->setup_semihosting = armv7a_setup_semihosting;
521 /* target is useful in all function arm v4 5 compatible */
522 armv7a->arm.target = target;
523 armv7a->arm.common_magic = ARM_COMMON_MAGIC;
524 armv7a->common_magic = ARMV7_COMMON_MAGIC;
525 armv7a->armv7a_mmu.armv7a_cache.info = -1;
526 armv7a->armv7a_mmu.armv7a_cache.outer_cache = NULL;
527 armv7a->armv7a_mmu.armv7a_cache.flush_all_data_cache = NULL;
528 armv7a->armv7a_mmu.armv7a_cache.auto_cache_enabled = 1;
529 return ERROR_OK;
530 }
531
532 int armv7a_arch_state(struct target *target)
533 {
534 static const char *state[] = {
535 "disabled", "enabled"
536 };
537
538 struct armv7a_common *armv7a = target_to_armv7a(target);
539 struct arm *arm = &armv7a->arm;
540
541 if (armv7a->common_magic != ARMV7_COMMON_MAGIC) {
542 LOG_ERROR("BUG: called for a non-ARMv7A target");
543 return ERROR_COMMAND_SYNTAX_ERROR;
544 }
545
546 arm_arch_state(target);
547
548 if (armv7a->is_armv7r) {
549 LOG_USER("D-Cache: %s, I-Cache: %s",
550 state[armv7a->armv7a_mmu.armv7a_cache.d_u_cache_enabled],
551 state[armv7a->armv7a_mmu.armv7a_cache.i_cache_enabled]);
552 } else {
553 LOG_USER("MMU: %s, D-Cache: %s, I-Cache: %s",
554 state[armv7a->armv7a_mmu.mmu_enabled],
555 state[armv7a->armv7a_mmu.armv7a_cache.d_u_cache_enabled],
556 state[armv7a->armv7a_mmu.armv7a_cache.i_cache_enabled]);
557 }
558
559 if (arm->core_mode == ARM_MODE_ABT)
560 armv7a_show_fault_registers(target);
561
562 return ERROR_OK;
563 }
564
565 static const struct command_registration l2_cache_commands[] = {
566 {
567 .name = "l2x",
568 .handler = handle_cache_l2x,
569 .mode = COMMAND_EXEC,
570 .help = "configure l2x cache",
571 .usage = "[base_addr] [number_of_way]",
572 },
573 COMMAND_REGISTRATION_DONE
574
575 };
576
577 static const struct command_registration l2x_cache_command_handlers[] = {
578 {
579 .name = "cache_config",
580 .mode = COMMAND_EXEC,
581 .help = "cache configuration for a target",
582 .usage = "",
583 .chain = l2_cache_commands,
584 },
585 COMMAND_REGISTRATION_DONE
586 };
587
588 const struct command_registration armv7a_command_handlers[] = {
589 {
590 .chain = l2x_cache_command_handlers,
591 },
592 {
593 .chain = arm7a_cache_command_handlers,
594 },
595 COMMAND_REGISTRATION_DONE
596 };

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)