fix flash bank auto_probe() fail with multiple targets
[openocd.git] / src / flash / nor / core.c
1 /***************************************************************************
2 * Copyright (C) 2005 by Dominic Rath <Dominic.Rath@gmx.de> *
3 * Copyright (C) 2007-2010 Øyvind Harboe <oyvind.harboe@zylin.com> *
4 * Copyright (C) 2008 by Spencer Oliver <spen@spen-soft.co.uk> *
5 * Copyright (C) 2009 Zachary T Welch <zw@superlucidity.net> *
6 * Copyright (C) 2010 by Antonio Borneo <borneo.antonio@gmail.com> *
7 * *
8 * This program is free software; you can redistribute it and/or modify *
9 * it under the terms of the GNU General Public License as published by *
10 * the Free Software Foundation; either version 2 of the License, or *
11 * (at your option) any later version. *
12 * *
13 * This program is distributed in the hope that it will be useful, *
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16 * GNU General Public License for more details. *
17 * *
18 * You should have received a copy of the GNU General Public License *
19 * along with this program; if not, write to the *
20 * Free Software Foundation, Inc., *
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
22 ***************************************************************************/
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27 #include <flash/common.h>
28 #include <flash/nor/core.h>
29 #include <flash/nor/imp.h>
30 #include <target/image.h>
31
32 /**
33 * @file
34 * Upper level of NOR flash framework.
35 * The lower level interfaces are to drivers. These upper level ones
36 * primarily support access from Tcl scripts or from GDB.
37 */
38
39 static struct flash_bank *flash_banks;
40
41 int flash_driver_erase(struct flash_bank *bank, int first, int last)
42 {
43 int retval;
44
45 retval = bank->driver->erase(bank, first, last);
46 if (retval != ERROR_OK)
47 LOG_ERROR("failed erasing sectors %d to %d", first, last);
48
49 return retval;
50 }
51
52 int flash_driver_protect(struct flash_bank *bank, int set, int first, int last)
53 {
54 int retval;
55
56 /* callers may not supply illegal parameters ... */
57 if (first < 0 || first > last || last >= bank->num_sectors) {
58 LOG_ERROR("illegal sector range");
59 return ERROR_FAIL;
60 }
61
62 /* force "set" to 0/1 */
63 set = !!set;
64
65 /* DANGER!
66 *
67 * We must not use any cached information about protection state!!!!
68 *
69 * There are a million things that could change the protect state:
70 *
71 * the target could have reset, power cycled, been hot plugged,
72 * the application could have run, etc.
73 *
74 * Drivers only receive valid sector range.
75 */
76 retval = bank->driver->protect(bank, set, first, last);
77 if (retval != ERROR_OK)
78 LOG_ERROR("failed setting protection for areas %d to %d", first, last);
79
80 return retval;
81 }
82
83 int flash_driver_write(struct flash_bank *bank,
84 uint8_t *buffer, uint32_t offset, uint32_t count)
85 {
86 int retval;
87
88 retval = bank->driver->write(bank, buffer, offset, count);
89 if (retval != ERROR_OK) {
90 LOG_ERROR(
91 "error writing to flash at address 0x%08" PRIx32 " at offset 0x%8.8" PRIx32,
92 bank->base,
93 offset);
94 }
95
96 return retval;
97 }
98
99 int flash_driver_read(struct flash_bank *bank,
100 uint8_t *buffer, uint32_t offset, uint32_t count)
101 {
102 int retval;
103
104 LOG_DEBUG("call flash_driver_read()");
105
106 retval = bank->driver->read(bank, buffer, offset, count);
107 if (retval != ERROR_OK) {
108 LOG_ERROR(
109 "error reading to flash at address 0x%08" PRIx32 " at offset 0x%8.8" PRIx32,
110 bank->base,
111 offset);
112 }
113
114 return retval;
115 }
116
117 int default_flash_read(struct flash_bank *bank,
118 uint8_t *buffer, uint32_t offset, uint32_t count)
119 {
120 return target_read_buffer(bank->target, offset + bank->base, count, buffer);
121 }
122
123 void flash_bank_add(struct flash_bank *bank)
124 {
125 /* put flash bank in linked list */
126 unsigned bank_num = 0;
127 if (flash_banks) {
128 /* find last flash bank */
129 struct flash_bank *p = flash_banks;
130 while (NULL != p->next) {
131 bank_num += 1;
132 p = p->next;
133 }
134 p->next = bank;
135 bank_num += 1;
136 } else
137 flash_banks = bank;
138
139 bank->bank_number = bank_num;
140 }
141
142 struct flash_bank *flash_bank_list(void)
143 {
144 return flash_banks;
145 }
146
147 struct flash_bank *get_flash_bank_by_num_noprobe(int num)
148 {
149 struct flash_bank *p;
150 int i = 0;
151
152 for (p = flash_banks; p; p = p->next) {
153 if (i++ == num)
154 return p;
155 }
156 LOG_ERROR("flash bank %d does not exist", num);
157 return NULL;
158 }
159
160 int flash_get_bank_count(void)
161 {
162 struct flash_bank *p;
163 int i = 0;
164 for (p = flash_banks; p; p = p->next)
165 i++;
166 return i;
167 }
168
169 struct flash_bank *get_flash_bank_by_name_noprobe(const char *name)
170 {
171 unsigned requested = get_flash_name_index(name);
172 unsigned found = 0;
173
174 struct flash_bank *bank;
175 for (bank = flash_banks; NULL != bank; bank = bank->next) {
176 if (strcmp(bank->name, name) == 0)
177 return bank;
178 if (!flash_driver_name_matches(bank->driver->name, name))
179 continue;
180 if (++found < requested)
181 continue;
182 return bank;
183 }
184 return NULL;
185 }
186
187 int get_flash_bank_by_name(const char *name, struct flash_bank **bank_result)
188 {
189 struct flash_bank *bank;
190 int retval;
191
192 bank = get_flash_bank_by_name_noprobe(name);
193 if (bank != NULL) {
194 retval = bank->driver->auto_probe(bank);
195
196 if (retval != ERROR_OK) {
197 LOG_ERROR("auto_probe failed");
198 return retval;
199 }
200 }
201
202 *bank_result = bank;
203 return ERROR_OK;
204 }
205
206 int get_flash_bank_by_num(int num, struct flash_bank **bank)
207 {
208 struct flash_bank *p = get_flash_bank_by_num_noprobe(num);
209 int retval;
210
211 if (p == NULL)
212 return ERROR_FAIL;
213
214 retval = p->driver->auto_probe(p);
215
216 if (retval != ERROR_OK) {
217 LOG_ERROR("auto_probe failed");
218 return retval;
219 }
220 *bank = p;
221 return ERROR_OK;
222 }
223
224 /* lookup flash bank by address, bank not found is success, but
225 * result_bank is set to NULL. */
226 int get_flash_bank_by_addr(struct target *target,
227 uint32_t addr,
228 bool check,
229 struct flash_bank **result_bank)
230 {
231 struct flash_bank *c;
232
233 /* cycle through bank list */
234 for (c = flash_banks; c; c = c->next) {
235 if (c->target != target)
236 continue;
237
238 int retval;
239 retval = c->driver->auto_probe(c);
240
241 if (retval != ERROR_OK) {
242 LOG_ERROR("auto_probe failed");
243 return retval;
244 }
245 /* check whether address belongs to this flash bank */
246 if ((addr >= c->base) && (addr <= c->base + (c->size - 1))) {
247 *result_bank = c;
248 return ERROR_OK;
249 }
250 }
251 *result_bank = NULL;
252 if (check) {
253 LOG_ERROR("No flash at address 0x%08" PRIx32, addr);
254 return ERROR_FAIL;
255 }
256 return ERROR_OK;
257 }
258
259 static int default_flash_mem_blank_check(struct flash_bank *bank)
260 {
261 struct target *target = bank->target;
262 const int buffer_size = 1024;
263 int i;
264 uint32_t nBytes;
265 int retval = ERROR_OK;
266
267 if (bank->target->state != TARGET_HALTED) {
268 LOG_ERROR("Target not halted");
269 return ERROR_TARGET_NOT_HALTED;
270 }
271
272 uint8_t *buffer = malloc(buffer_size);
273
274 for (i = 0; i < bank->num_sectors; i++) {
275 uint32_t j;
276 bank->sectors[i].is_erased = 1;
277
278 for (j = 0; j < bank->sectors[i].size; j += buffer_size) {
279 uint32_t chunk;
280 chunk = buffer_size;
281 if (chunk > (j - bank->sectors[i].size))
282 chunk = (j - bank->sectors[i].size);
283
284 retval = target_read_memory(target,
285 bank->base + bank->sectors[i].offset + j,
286 4,
287 chunk/4,
288 buffer);
289 if (retval != ERROR_OK)
290 goto done;
291
292 for (nBytes = 0; nBytes < chunk; nBytes++) {
293 if (buffer[nBytes] != 0xFF) {
294 bank->sectors[i].is_erased = 0;
295 break;
296 }
297 }
298 }
299 }
300
301 done:
302 free(buffer);
303
304 return retval;
305 }
306
307 int default_flash_blank_check(struct flash_bank *bank)
308 {
309 struct target *target = bank->target;
310 int i;
311 int retval;
312 int fast_check = 0;
313 uint32_t blank;
314
315 if (bank->target->state != TARGET_HALTED) {
316 LOG_ERROR("Target not halted");
317 return ERROR_TARGET_NOT_HALTED;
318 }
319
320 for (i = 0; i < bank->num_sectors; i++) {
321 uint32_t address = bank->base + bank->sectors[i].offset;
322 uint32_t size = bank->sectors[i].size;
323
324 retval = target_blank_check_memory(target, address, size, &blank);
325 if (retval != ERROR_OK) {
326 fast_check = 0;
327 break;
328 }
329 if (blank == 0xFF)
330 bank->sectors[i].is_erased = 1;
331 else
332 bank->sectors[i].is_erased = 0;
333 fast_check = 1;
334 }
335
336 if (!fast_check) {
337 LOG_USER("Running slow fallback erase check - add working memory");
338 return default_flash_mem_blank_check(bank);
339 }
340
341 return ERROR_OK;
342 }
343
344 /* Manipulate given flash region, selecting the bank according to target
345 * and address. Maps an address range to a set of sectors, and issues
346 * the callback() on that set ... e.g. to erase or unprotect its members.
347 *
348 * (Note a current bad assumption: that protection operates on the same
349 * size sectors as erase operations use.)
350 *
351 * The "pad_reason" parameter is a kind of boolean: when it's NULL, the
352 * range must fit those sectors exactly. This is clearly safe; it can't
353 * erase data which the caller said to leave alone, for example. If it's
354 * non-NULL, rather than failing, extra data in the first and/or last
355 * sectors will be added to the range, and that reason string is used when
356 * warning about those additions.
357 */
358 static int flash_iterate_address_range_inner(struct target *target,
359 char *pad_reason, uint32_t addr, uint32_t length,
360 int (*callback)(struct flash_bank *bank, int first, int last))
361 {
362 struct flash_bank *c;
363 uint32_t last_addr = addr + length; /* first address AFTER end */
364 int first = -1;
365 int last = -1;
366 int i;
367
368 int retval = get_flash_bank_by_addr(target, addr, true, &c);
369 if (retval != ERROR_OK)
370 return retval;
371
372 if (c->size == 0 || c->num_sectors == 0) {
373 LOG_ERROR("Bank is invalid");
374 return ERROR_FLASH_BANK_INVALID;
375 }
376
377 if (length == 0) {
378 /* special case, erase whole bank when length is zero */
379 if (addr != c->base) {
380 LOG_ERROR("Whole bank access must start at beginning of bank.");
381 return ERROR_FLASH_DST_BREAKS_ALIGNMENT;
382 }
383
384 return callback(c, 0, c->num_sectors - 1);
385 }
386
387 /* check whether it all fits in this bank */
388 if (addr + length - 1 > c->base + c->size - 1) {
389 LOG_ERROR("Flash access does not fit into bank.");
390 return ERROR_FLASH_DST_BREAKS_ALIGNMENT;
391 }
392
393 /** @todo: handle erasures that cross into adjacent banks */
394
395 addr -= c->base;
396 last_addr -= c->base;
397
398 for (i = 0; i < c->num_sectors; i++) {
399 struct flash_sector *f = c->sectors + i;
400 uint32_t end = f->offset + f->size;
401
402 /* start only on a sector boundary */
403 if (first < 0) {
404 /* scanned past the first sector? */
405 if (addr < f->offset)
406 break;
407
408 /* is this the first sector? */
409 if (addr == f->offset)
410 first = i;
411
412 /* Does this need head-padding? If so, pad and warn;
413 * or else force an error.
414 *
415 * Such padding can make trouble, since *WE* can't
416 * ever know if that data was in use. The warning
417 * should help users sort out messes later.
418 */
419 else if (addr < end && pad_reason) {
420 /* FIXME say how many bytes (e.g. 80 KB) */
421 LOG_WARNING("Adding extra %s range, "
422 "%#8.8x to %#8.8x",
423 pad_reason,
424 (unsigned) f->offset,
425 (unsigned) addr - 1);
426 first = i;
427 } else
428 continue;
429 }
430
431 /* is this (also?) the last sector? */
432 if (last_addr == end) {
433 last = i;
434 break;
435 }
436
437 /* Does this need tail-padding? If so, pad and warn;
438 * or else force an error.
439 */
440 if (last_addr < end && pad_reason) {
441 /* FIXME say how many bytes (e.g. 80 KB) */
442 LOG_WARNING("Adding extra %s range, "
443 "%#8.8x to %#8.8x",
444 pad_reason,
445 (unsigned) last_addr,
446 (unsigned) end - 1);
447 last = i;
448 break;
449 }
450
451 /* MUST finish on a sector boundary */
452 if (last_addr <= f->offset)
453 break;
454 }
455
456 /* invalid start or end address? */
457 if (first == -1 || last == -1) {
458 LOG_ERROR("address range 0x%8.8x .. 0x%8.8x "
459 "is not sector-aligned",
460 (unsigned) (c->base + addr),
461 (unsigned) (c->base + last_addr - 1));
462 return ERROR_FLASH_DST_BREAKS_ALIGNMENT;
463 }
464
465 /* The NOR driver may trim this range down, based on what
466 * sectors are already erased/unprotected. GDB currently
467 * blocks such optimizations.
468 */
469 return callback(c, first, last);
470 }
471
472 /* The inner fn only handles a single bank, we could be spanning
473 * multiple chips.
474 */
475 static int flash_iterate_address_range(struct target *target,
476 char *pad_reason, uint32_t addr, uint32_t length,
477 int (*callback)(struct flash_bank *bank, int first, int last))
478 {
479 struct flash_bank *c;
480 int retval = ERROR_OK;
481
482 /* Danger! zero-length iterations means entire bank! */
483 do {
484 retval = get_flash_bank_by_addr(target, addr, true, &c);
485 if (retval != ERROR_OK)
486 return retval;
487
488 uint32_t cur_length = length;
489 /* check whether it all fits in this bank */
490 if (addr + length - 1 > c->base + c->size - 1) {
491 LOG_DEBUG("iterating over more than one flash bank.");
492 cur_length = c->base + c->size - addr;
493 }
494 retval = flash_iterate_address_range_inner(target,
495 pad_reason, addr, cur_length,
496 callback);
497 if (retval != ERROR_OK)
498 break;
499
500 length -= cur_length;
501 addr += cur_length;
502 } while (length > 0);
503
504 return retval;
505 }
506
507 int flash_erase_address_range(struct target *target,
508 bool pad, uint32_t addr, uint32_t length)
509 {
510 return flash_iterate_address_range(target, pad ? "erase" : NULL,
511 addr, length, &flash_driver_erase);
512 }
513
514 static int flash_driver_unprotect(struct flash_bank *bank, int first, int last)
515 {
516 return flash_driver_protect(bank, 0, first, last);
517 }
518
519 int flash_unlock_address_range(struct target *target, uint32_t addr, uint32_t length)
520 {
521 /* By default, pad to sector boundaries ... the real issue here
522 * is that our (only) caller *permanently* removes protection,
523 * and doesn't restore it.
524 */
525 return flash_iterate_address_range(target, "unprotect",
526 addr, length, &flash_driver_unprotect);
527 }
528
529 static int compare_section(const void *a, const void *b)
530 {
531 struct imagesection *b1, *b2;
532 b1 = *((struct imagesection **)a);
533 b2 = *((struct imagesection **)b);
534
535 if (b1->base_address == b2->base_address)
536 return 0;
537 else if (b1->base_address > b2->base_address)
538 return 1;
539 else
540 return -1;
541 }
542
543 int flash_write_unlock(struct target *target, struct image *image,
544 uint32_t *written, int erase, bool unlock)
545 {
546 int retval = ERROR_OK;
547
548 int section;
549 uint32_t section_offset;
550 struct flash_bank *c;
551 int *padding;
552
553 section = 0;
554 section_offset = 0;
555
556 if (written)
557 *written = 0;
558
559 if (erase) {
560 /* assume all sectors need erasing - stops any problems
561 * when flash_write is called multiple times */
562
563 flash_set_dirty();
564 }
565
566 /* allocate padding array */
567 padding = calloc(image->num_sections, sizeof(*padding));
568
569 /* This fn requires all sections to be in ascending order of addresses,
570 * whereas an image can have sections out of order. */
571 struct imagesection **sections = malloc(sizeof(struct imagesection *) *
572 image->num_sections);
573 int i;
574 for (i = 0; i < image->num_sections; i++)
575 sections[i] = &image->sections[i];
576
577 qsort(sections, image->num_sections, sizeof(struct imagesection *),
578 compare_section);
579
580 /* loop until we reach end of the image */
581 while (section < image->num_sections) {
582 uint32_t buffer_size;
583 uint8_t *buffer;
584 int section_last;
585 uint32_t run_address = sections[section]->base_address + section_offset;
586 uint32_t run_size = sections[section]->size - section_offset;
587 int pad_bytes = 0;
588
589 if (sections[section]->size == 0) {
590 LOG_WARNING("empty section %d", section);
591 section++;
592 section_offset = 0;
593 continue;
594 }
595
596 /* find the corresponding flash bank */
597 retval = get_flash_bank_by_addr(target, run_address, false, &c);
598 if (retval != ERROR_OK)
599 goto done;
600 if (c == NULL) {
601 LOG_WARNING("no flash bank found for address %" PRIx32, run_address);
602 section++; /* and skip it */
603 section_offset = 0;
604 continue;
605 }
606
607 /* collect consecutive sections which fall into the same bank */
608 section_last = section;
609 padding[section] = 0;
610 while ((run_address + run_size - 1 < c->base + c->size - 1) &&
611 (section_last + 1 < image->num_sections)) {
612 /* sections are sorted */
613 assert(sections[section_last + 1]->base_address >= c->base);
614 if (sections[section_last + 1]->base_address >= (c->base + c->size)) {
615 /* Done with this bank */
616 break;
617 }
618
619 /* FIXME This needlessly touches sectors BETWEEN the
620 * sections it's writing. Without auto erase, it just
621 * writes ones. That WILL INVALIDATE data in cases
622 * like Stellaris Tempest chips, corrupting internal
623 * ECC codes; and at least FreeScale suggests issues
624 * with that approach (in HC11 documentation).
625 *
626 * With auto erase enabled, data in those sectors will
627 * be needlessly destroyed; and some of the limited
628 * number of flash erase cycles will be wasted...
629 *
630 * In both cases, the extra writes slow things down.
631 */
632
633 /* if we have multiple sections within our image,
634 * flash programming could fail due to alignment issues
635 * attempt to rebuild a consecutive buffer for the flash loader */
636 pad_bytes = (sections[section_last + 1]->base_address) - (run_address + run_size);
637 padding[section_last] = pad_bytes;
638 run_size += sections[++section_last]->size;
639 run_size += pad_bytes;
640
641 if (pad_bytes > 0)
642 LOG_INFO("Padding image section %d with %d bytes",
643 section_last-1,
644 pad_bytes);
645 }
646
647 if (run_address + run_size - 1 > c->base + c->size - 1) {
648 /* If we have more than one flash chip back to back, then we limit
649 * the current write operation to the current chip.
650 */
651 LOG_DEBUG("Truncate flash run size to the current flash chip.");
652
653 run_size = c->base + c->size - run_address;
654 assert(run_size > 0);
655 }
656
657 /* If we're applying any sector automagic, then pad this
658 * (maybe-combined) segment to the end of its last sector.
659 */
660 if (unlock || erase) {
661 int sector;
662 uint32_t offset_start = run_address - c->base;
663 uint32_t offset_end = offset_start + run_size;
664 uint32_t end = offset_end, delta;
665
666 for (sector = 0; sector < c->num_sectors; sector++) {
667 end = c->sectors[sector].offset
668 + c->sectors[sector].size;
669 if (offset_end <= end)
670 break;
671 }
672
673 delta = end - offset_end;
674 padding[section_last] += delta;
675 run_size += delta;
676 }
677
678 /* allocate buffer */
679 buffer = malloc(run_size);
680 if (buffer == NULL) {
681 LOG_ERROR("Out of memory for flash bank buffer");
682 retval = ERROR_FAIL;
683 goto done;
684 }
685 buffer_size = 0;
686
687 /* read sections to the buffer */
688 while (buffer_size < run_size) {
689 size_t size_read;
690
691 size_read = run_size - buffer_size;
692 if (size_read > sections[section]->size - section_offset)
693 size_read = sections[section]->size - section_offset;
694
695 /* KLUDGE!
696 *
697 * #¤%#"%¤% we have to figure out the section # from the sorted
698 * list of pointers to sections to invoke image_read_section()...
699 */
700 intptr_t diff = (intptr_t)sections[section] - (intptr_t)image->sections;
701 int t_section_num = diff / sizeof(struct imagesection);
702
703 LOG_DEBUG("image_read_section: section = %d, t_section_num = %d, "
704 "section_offset = %d, buffer_size = %d, size_read = %d",
705 (int)section, (int)t_section_num, (int)section_offset,
706 (int)buffer_size, (int)size_read);
707 retval = image_read_section(image, t_section_num, section_offset,
708 size_read, buffer + buffer_size, &size_read);
709 if (retval != ERROR_OK || size_read == 0) {
710 free(buffer);
711 goto done;
712 }
713
714 /* see if we need to pad the section */
715 while (padding[section]--)
716 (buffer + buffer_size)[size_read++] = c->default_padded_value;
717
718 buffer_size += size_read;
719 section_offset += size_read;
720
721 if (section_offset >= sections[section]->size) {
722 section++;
723 section_offset = 0;
724 }
725 }
726
727 retval = ERROR_OK;
728
729 if (unlock)
730 retval = flash_unlock_address_range(target, run_address, run_size);
731 if (retval == ERROR_OK) {
732 if (erase) {
733 /* calculate and erase sectors */
734 retval = flash_erase_address_range(target,
735 true, run_address, run_size);
736 }
737 }
738
739 if (retval == ERROR_OK) {
740 /* write flash sectors */
741 retval = flash_driver_write(c, buffer, run_address - c->base, run_size);
742 }
743
744 free(buffer);
745
746 if (retval != ERROR_OK) {
747 /* abort operation */
748 goto done;
749 }
750
751 if (written != NULL)
752 *written += run_size; /* add run size to total written counter */
753 }
754
755 done:
756 free(sections);
757 free(padding);
758
759 return retval;
760 }
761
762 int flash_write(struct target *target, struct image *image,
763 uint32_t *written, int erase)
764 {
765 return flash_write_unlock(target, image, written, erase, false);
766 }

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)