flash/nor/driver.h: typo fix
[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 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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 int retval;
236 retval = c->driver->auto_probe(c);
237
238 if (retval != ERROR_OK) {
239 LOG_ERROR("auto_probe failed");
240 return retval;
241 }
242 /* check whether address belongs to this flash bank */
243 if ((addr >= c->base) && (addr <= c->base + (c->size - 1)) && target == c->target) {
244 *result_bank = c;
245 return ERROR_OK;
246 }
247 }
248 *result_bank = NULL;
249 if (check) {
250 LOG_ERROR("No flash at address 0x%08" PRIx32, addr);
251 return ERROR_FAIL;
252 }
253 return ERROR_OK;
254 }
255
256 static int default_flash_mem_blank_check(struct flash_bank *bank)
257 {
258 struct target *target = bank->target;
259 const int buffer_size = 1024;
260 int i;
261 uint32_t nBytes;
262 int retval = ERROR_OK;
263
264 if (bank->target->state != TARGET_HALTED) {
265 LOG_ERROR("Target not halted");
266 return ERROR_TARGET_NOT_HALTED;
267 }
268
269 uint8_t *buffer = malloc(buffer_size);
270
271 for (i = 0; i < bank->num_sectors; i++) {
272 uint32_t j;
273 bank->sectors[i].is_erased = 1;
274
275 for (j = 0; j < bank->sectors[i].size; j += buffer_size) {
276 uint32_t chunk;
277 chunk = buffer_size;
278 if (chunk > (j - bank->sectors[i].size))
279 chunk = (j - bank->sectors[i].size);
280
281 retval = target_read_memory(target,
282 bank->base + bank->sectors[i].offset + j,
283 4,
284 chunk/4,
285 buffer);
286 if (retval != ERROR_OK)
287 goto done;
288
289 for (nBytes = 0; nBytes < chunk; nBytes++) {
290 if (buffer[nBytes] != 0xFF) {
291 bank->sectors[i].is_erased = 0;
292 break;
293 }
294 }
295 }
296 }
297
298 done:
299 free(buffer);
300
301 return retval;
302 }
303
304 int default_flash_blank_check(struct flash_bank *bank)
305 {
306 struct target *target = bank->target;
307 int i;
308 int retval;
309 int fast_check = 0;
310 uint32_t blank;
311
312 if (bank->target->state != TARGET_HALTED) {
313 LOG_ERROR("Target not halted");
314 return ERROR_TARGET_NOT_HALTED;
315 }
316
317 for (i = 0; i < bank->num_sectors; i++) {
318 uint32_t address = bank->base + bank->sectors[i].offset;
319 uint32_t size = bank->sectors[i].size;
320
321 retval = target_blank_check_memory(target, address, size, &blank);
322 if (retval != ERROR_OK) {
323 fast_check = 0;
324 break;
325 }
326 if (blank == 0xFF)
327 bank->sectors[i].is_erased = 1;
328 else
329 bank->sectors[i].is_erased = 0;
330 fast_check = 1;
331 }
332
333 if (!fast_check) {
334 LOG_USER("Running slow fallback erase check - add working memory");
335 return default_flash_mem_blank_check(bank);
336 }
337
338 return ERROR_OK;
339 }
340
341 /* Manipulate given flash region, selecting the bank according to target
342 * and address. Maps an address range to a set of sectors, and issues
343 * the callback() on that set ... e.g. to erase or unprotect its members.
344 *
345 * (Note a current bad assumption: that protection operates on the same
346 * size sectors as erase operations use.)
347 *
348 * The "pad_reason" parameter is a kind of boolean: when it's NULL, the
349 * range must fit those sectors exactly. This is clearly safe; it can't
350 * erase data which the caller said to leave alone, for example. If it's
351 * non-NULL, rather than failing, extra data in the first and/or last
352 * sectors will be added to the range, and that reason string is used when
353 * warning about those additions.
354 */
355 static int flash_iterate_address_range_inner(struct target *target,
356 char *pad_reason, uint32_t addr, uint32_t length,
357 int (*callback)(struct flash_bank *bank, int first, int last))
358 {
359 struct flash_bank *c;
360 uint32_t last_addr = addr + length; /* first address AFTER end */
361 int first = -1;
362 int last = -1;
363 int i;
364
365 int retval = get_flash_bank_by_addr(target, addr, true, &c);
366 if (retval != ERROR_OK)
367 return retval;
368
369 if (c->size == 0 || c->num_sectors == 0) {
370 LOG_ERROR("Bank is invalid");
371 return ERROR_FLASH_BANK_INVALID;
372 }
373
374 if (length == 0) {
375 /* special case, erase whole bank when length is zero */
376 if (addr != c->base) {
377 LOG_ERROR("Whole bank access must start at beginning of bank.");
378 return ERROR_FLASH_DST_BREAKS_ALIGNMENT;
379 }
380
381 return callback(c, 0, c->num_sectors - 1);
382 }
383
384 /* check whether it all fits in this bank */
385 if (addr + length - 1 > c->base + c->size - 1) {
386 LOG_ERROR("Flash access does not fit into bank.");
387 return ERROR_FLASH_DST_BREAKS_ALIGNMENT;
388 }
389
390 /** @todo: handle erasures that cross into adjacent banks */
391
392 addr -= c->base;
393 last_addr -= c->base;
394
395 for (i = 0; i < c->num_sectors; i++) {
396 struct flash_sector *f = c->sectors + i;
397 uint32_t end = f->offset + f->size;
398
399 /* start only on a sector boundary */
400 if (first < 0) {
401 /* scanned past the first sector? */
402 if (addr < f->offset)
403 break;
404
405 /* is this the first sector? */
406 if (addr == f->offset)
407 first = i;
408
409 /* Does this need head-padding? If so, pad and warn;
410 * or else force an error.
411 *
412 * Such padding can make trouble, since *WE* can't
413 * ever know if that data was in use. The warning
414 * should help users sort out messes later.
415 */
416 else if (addr < end && pad_reason) {
417 /* FIXME say how many bytes (e.g. 80 KB) */
418 LOG_WARNING("Adding extra %s range, "
419 "%#8.8x to %#8.8x",
420 pad_reason,
421 (unsigned) f->offset,
422 (unsigned) addr - 1);
423 first = i;
424 } else
425 continue;
426 }
427
428 /* is this (also?) the last sector? */
429 if (last_addr == end) {
430 last = i;
431 break;
432 }
433
434 /* Does this need tail-padding? If so, pad and warn;
435 * or else force an error.
436 */
437 if (last_addr < end && pad_reason) {
438 /* FIXME say how many bytes (e.g. 80 KB) */
439 LOG_WARNING("Adding extra %s range, "
440 "%#8.8x to %#8.8x",
441 pad_reason,
442 (unsigned) last_addr,
443 (unsigned) end - 1);
444 last = i;
445 break;
446 }
447
448 /* MUST finish on a sector boundary */
449 if (last_addr <= f->offset)
450 break;
451 }
452
453 /* invalid start or end address? */
454 if (first == -1 || last == -1) {
455 LOG_ERROR("address range 0x%8.8x .. 0x%8.8x "
456 "is not sector-aligned",
457 (unsigned) (c->base + addr),
458 (unsigned) (c->base + last_addr - 1));
459 return ERROR_FLASH_DST_BREAKS_ALIGNMENT;
460 }
461
462 /* The NOR driver may trim this range down, based on what
463 * sectors are already erased/unprotected. GDB currently
464 * blocks such optimizations.
465 */
466 return callback(c, first, last);
467 }
468
469 /* The inner fn only handles a single bank, we could be spanning
470 * multiple chips.
471 */
472 static int flash_iterate_address_range(struct target *target,
473 char *pad_reason, uint32_t addr, uint32_t length,
474 int (*callback)(struct flash_bank *bank, int first, int last))
475 {
476 struct flash_bank *c;
477 int retval = ERROR_OK;
478
479 /* Danger! zero-length iterations means entire bank! */
480 do {
481 retval = get_flash_bank_by_addr(target, addr, true, &c);
482 if (retval != ERROR_OK)
483 return retval;
484
485 uint32_t cur_length = length;
486 /* check whether it all fits in this bank */
487 if (addr + length - 1 > c->base + c->size - 1) {
488 LOG_DEBUG("iterating over more than one flash bank.");
489 cur_length = c->base + c->size - addr;
490 }
491 retval = flash_iterate_address_range_inner(target,
492 pad_reason, addr, cur_length,
493 callback);
494 if (retval != ERROR_OK)
495 break;
496
497 length -= cur_length;
498 addr += cur_length;
499 } while (length > 0);
500
501 return retval;
502 }
503
504 int flash_erase_address_range(struct target *target,
505 bool pad, uint32_t addr, uint32_t length)
506 {
507 return flash_iterate_address_range(target, pad ? "erase" : NULL,
508 addr, length, &flash_driver_erase);
509 }
510
511 static int flash_driver_unprotect(struct flash_bank *bank, int first, int last)
512 {
513 return flash_driver_protect(bank, 0, first, last);
514 }
515
516 int flash_unlock_address_range(struct target *target, uint32_t addr, uint32_t length)
517 {
518 /* By default, pad to sector boundaries ... the real issue here
519 * is that our (only) caller *permanently* removes protection,
520 * and doesn't restore it.
521 */
522 return flash_iterate_address_range(target, "unprotect",
523 addr, length, &flash_driver_unprotect);
524 }
525
526 static int compare_section(const void *a, const void *b)
527 {
528 struct imagesection *b1, *b2;
529 b1 = *((struct imagesection **)a);
530 b2 = *((struct imagesection **)b);
531
532 if (b1->base_address == b2->base_address)
533 return 0;
534 else if (b1->base_address > b2->base_address)
535 return 1;
536 else
537 return -1;
538 }
539
540 int flash_write_unlock(struct target *target, struct image *image,
541 uint32_t *written, int erase, bool unlock)
542 {
543 int retval = ERROR_OK;
544
545 int section;
546 uint32_t section_offset;
547 struct flash_bank *c;
548 int *padding;
549
550 section = 0;
551 section_offset = 0;
552
553 if (written)
554 *written = 0;
555
556 if (erase) {
557 /* assume all sectors need erasing - stops any problems
558 * when flash_write is called multiple times */
559
560 flash_set_dirty();
561 }
562
563 /* allocate padding array */
564 padding = calloc(image->num_sections, sizeof(*padding));
565
566 /* This fn requires all sections to be in ascending order of addresses,
567 * whereas an image can have sections out of order. */
568 struct imagesection **sections = malloc(sizeof(struct imagesection *) *
569 image->num_sections);
570 int i;
571 for (i = 0; i < image->num_sections; i++)
572 sections[i] = &image->sections[i];
573
574 qsort(sections, image->num_sections, sizeof(struct imagesection *),
575 compare_section);
576
577 /* loop until we reach end of the image */
578 while (section < image->num_sections) {
579 uint32_t buffer_size;
580 uint8_t *buffer;
581 int section_last;
582 uint32_t run_address = sections[section]->base_address + section_offset;
583 uint32_t run_size = sections[section]->size - section_offset;
584 int pad_bytes = 0;
585
586 if (sections[section]->size == 0) {
587 LOG_WARNING("empty section %d", section);
588 section++;
589 section_offset = 0;
590 continue;
591 }
592
593 /* find the corresponding flash bank */
594 retval = get_flash_bank_by_addr(target, run_address, false, &c);
595 if (retval != ERROR_OK)
596 goto done;
597 if (c == NULL) {
598 LOG_WARNING("no flash bank found for address %x", run_address);
599 section++; /* and skip it */
600 section_offset = 0;
601 continue;
602 }
603
604 /* collect consecutive sections which fall into the same bank */
605 section_last = section;
606 padding[section] = 0;
607 while ((run_address + run_size - 1 < c->base + c->size - 1) &&
608 (section_last + 1 < image->num_sections)) {
609 /* sections are sorted */
610 assert(sections[section_last + 1]->base_address >= c->base);
611 if (sections[section_last + 1]->base_address >= (c->base + c->size)) {
612 /* Done with this bank */
613 break;
614 }
615
616 /* FIXME This needlessly touches sectors BETWEEN the
617 * sections it's writing. Without auto erase, it just
618 * writes ones. That WILL INVALIDATE data in cases
619 * like Stellaris Tempest chips, corrupting internal
620 * ECC codes; and at least FreeScale suggests issues
621 * with that approach (in HC11 documentation).
622 *
623 * With auto erase enabled, data in those sectors will
624 * be needlessly destroyed; and some of the limited
625 * number of flash erase cycles will be wasted...
626 *
627 * In both cases, the extra writes slow things down.
628 */
629
630 /* if we have multiple sections within our image,
631 * flash programming could fail due to alignment issues
632 * attempt to rebuild a consecutive buffer for the flash loader */
633 pad_bytes = (sections[section_last + 1]->base_address) - (run_address + run_size);
634 padding[section_last] = pad_bytes;
635 run_size += sections[++section_last]->size;
636 run_size += pad_bytes;
637
638 if (pad_bytes > 0)
639 LOG_INFO("Padding image section %d with %d bytes",
640 section_last-1,
641 pad_bytes);
642 }
643
644 if (run_address + run_size - 1 > c->base + c->size - 1) {
645 /* If we have more than one flash chip back to back, then we limit
646 * the current write operation to the current chip.
647 */
648 LOG_DEBUG("Truncate flash run size to the current flash chip.");
649
650 run_size = c->base + c->size - run_address;
651 assert(run_size > 0);
652 }
653
654 /* If we're applying any sector automagic, then pad this
655 * (maybe-combined) segment to the end of its last sector.
656 */
657 if (unlock || erase) {
658 int sector;
659 uint32_t offset_start = run_address - c->base;
660 uint32_t offset_end = offset_start + run_size;
661 uint32_t end = offset_end, delta;
662
663 for (sector = 0; sector < c->num_sectors; sector++) {
664 end = c->sectors[sector].offset
665 + c->sectors[sector].size;
666 if (offset_end <= end)
667 break;
668 }
669
670 delta = end - offset_end;
671 padding[section_last] += delta;
672 run_size += delta;
673 }
674
675 /* allocate buffer */
676 buffer = malloc(run_size);
677 if (buffer == NULL) {
678 LOG_ERROR("Out of memory for flash bank buffer");
679 retval = ERROR_FAIL;
680 goto done;
681 }
682 buffer_size = 0;
683
684 /* read sections to the buffer */
685 while (buffer_size < run_size) {
686 size_t size_read;
687
688 size_read = run_size - buffer_size;
689 if (size_read > sections[section]->size - section_offset)
690 size_read = sections[section]->size - section_offset;
691
692 /* KLUDGE!
693 *
694 * #¤%#"%¤% we have to figure out the section # from the sorted
695 * list of pointers to sections to invoke image_read_section()...
696 */
697 intptr_t diff = (intptr_t)sections[section] - (intptr_t)image->sections;
698 int t_section_num = diff / sizeof(struct imagesection);
699
700 LOG_DEBUG("image_read_section: section = %d, t_section_num = %d, "
701 "section_offset = %d, buffer_size = %d, size_read = %d",
702 (int)section, (int)t_section_num, (int)section_offset,
703 (int)buffer_size, (int)size_read);
704 retval = image_read_section(image, t_section_num, section_offset,
705 size_read, buffer + buffer_size, &size_read);
706 if (retval != ERROR_OK || size_read == 0) {
707 free(buffer);
708 goto done;
709 }
710
711 /* see if we need to pad the section */
712 while (padding[section]--)
713 (buffer + buffer_size)[size_read++] = 0xff;
714
715 buffer_size += size_read;
716 section_offset += size_read;
717
718 if (section_offset >= sections[section]->size) {
719 section++;
720 section_offset = 0;
721 }
722 }
723
724 retval = ERROR_OK;
725
726 if (unlock)
727 retval = flash_unlock_address_range(target, run_address, run_size);
728 if (retval == ERROR_OK) {
729 if (erase) {
730 /* calculate and erase sectors */
731 retval = flash_erase_address_range(target,
732 true, run_address, run_size);
733 }
734 }
735
736 if (retval == ERROR_OK) {
737 /* write flash sectors */
738 retval = flash_driver_write(c, buffer, run_address - c->base, run_size);
739 }
740
741 free(buffer);
742
743 if (retval != ERROR_OK) {
744 /* abort operation */
745 goto done;
746 }
747
748 if (written != NULL)
749 *written += run_size; /* add run size to total written counter */
750 }
751
752 done:
753 free(sections);
754 free(padding);
755
756 return retval;
757 }
758
759 int flash_write(struct target *target, struct image *image,
760 uint32_t *written, int erase)
761 {
762 return flash_write_unlock(target, image, written, erase, false);
763 }

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)