flash: write_image would fail for certain images
[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 * *
7 * This program is free software; you can redistribute it and/or modify *
8 * it under the terms of the GNU General Public License as published by *
9 * the Free Software Foundation; either version 2 of the License, or *
10 * (at your option) any later version. *
11 * *
12 * This program is distributed in the hope that it will be useful, *
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
15 * GNU General Public License for more details. *
16 * *
17 * You should have received a copy of the GNU General Public License *
18 * along with this program; if not, write to the *
19 * Free Software Foundation, Inc., *
20 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
21 ***************************************************************************/
22
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26 #include <flash/common.h>
27 #include <flash/nor/core.h>
28 #include <flash/nor/imp.h>
29 #include <target/image.h>
30
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 {
48 LOG_ERROR("failed erasing sectors %d to %d (%d)", first, last, retval);
49 }
50
51 return retval;
52 }
53
54 int flash_driver_protect(struct flash_bank *bank, int set, int first, int last)
55 {
56 int retval;
57 bool updated = false;
58
59 /* NOTE: "first == last" means (un?)protect just that sector.
60 code including Lower level ddrivers may rely on this "first <= last"
61 * invariant.
62 */
63
64 /* callers may not supply illegal parameters ... */
65 if (first < 0 || first > last || last >= bank->num_sectors)
66 return ERROR_FAIL;
67
68 /* force "set" to 0/1 */
69 set = !!set;
70
71 /*
72 * Filter out what trivial nonsense we can, so drivers don't have to.
73 *
74 * Don't tell drivers to change to the current state... it's needless,
75 * and reducing the amount of work to be done (potentially to nothing)
76 * speeds at least some things up.
77 */
78 scan:
79 for (int i = first; i <= last; i++) {
80 struct flash_sector *sector = bank->sectors + i;
81
82 /* Only filter requests to protect the already-protected, or
83 * to unprotect the already-unprotected. Changing from the
84 * unknown state (-1) to a known one is unwise but allowed;
85 * protection status is best checked first.
86 */
87 if (sector->is_protected != set)
88 continue;
89
90 /* Shrink this range of sectors from the start; don't overrun
91 * the end. Also shrink from the end; don't overun the start.
92 *
93 * REVISIT we could handle discontiguous regions by issuing
94 * more than one driver request. How much would that matter?
95 */
96 if (i == first && i != last) {
97 updated = true;
98 first++;
99 } else if (i == last && i != first) {
100 updated = true;
101 last--;
102 }
103 }
104
105 /* updating the range affects the tests in the scan loop above; so
106 * re-scan, to make sure we didn't miss anything.
107 */
108 if (updated) {
109 updated = false;
110 goto scan;
111 }
112
113 /* Single sector, already protected? Nothing to do!
114 * We may have trimmed our parameters into this degenerate case.
115 *
116 * FIXME repeating the "is_protected==set" test is a giveaway that
117 * this fast-exit belongs earlier, in the trim-it-down loop; mve.
118 * */
119 if (first == last && bank->sectors[first].is_protected == set)
120 return ERROR_OK;
121
122
123 /* Note that we don't pass illegal parameters to drivers; any
124 * trimming just turns one valid range into another one.
125 */
126 retval = bank->driver->protect(bank, set, first, last);
127 if (retval != ERROR_OK)
128 {
129 LOG_ERROR("failed setting protection for areas %d to %d (%d)", first, last, retval);
130 }
131
132 return retval;
133 }
134
135 int flash_driver_write(struct flash_bank *bank,
136 uint8_t *buffer, uint32_t offset, uint32_t count)
137 {
138 int retval;
139
140 retval = bank->driver->write(bank, buffer, offset, count);
141 if (retval != ERROR_OK)
142 {
143 LOG_ERROR("error writing to flash at address 0x%08" PRIx32 " at offset 0x%8.8" PRIx32 " (%d)",
144 bank->base, offset, retval);
145 }
146
147 return retval;
148 }
149
150 void flash_bank_add(struct flash_bank *bank)
151 {
152 /* put flash bank in linked list */
153 unsigned bank_num = 0;
154 if (flash_banks)
155 {
156 /* find last flash bank */
157 struct flash_bank *p = flash_banks;
158 while (NULL != p->next)
159 {
160 bank_num += 1;
161 p = p->next;
162 }
163 p->next = bank;
164 bank_num += 1;
165 }
166 else
167 flash_banks = bank;
168
169 bank->bank_number = bank_num;
170 }
171
172 struct flash_bank *flash_bank_list(void)
173 {
174 return flash_banks;
175 }
176
177 struct flash_bank *get_flash_bank_by_num_noprobe(int num)
178 {
179 struct flash_bank *p;
180 int i = 0;
181
182 for (p = flash_banks; p; p = p->next)
183 {
184 if (i++ == num)
185 {
186 return p;
187 }
188 }
189 LOG_ERROR("flash bank %d does not exist", num);
190 return NULL;
191 }
192
193 int flash_get_bank_count(void)
194 {
195 struct flash_bank *p;
196 int i = 0;
197 for (p = flash_banks; p; p = p->next)
198 {
199 i++;
200 }
201 return i;
202 }
203
204 struct flash_bank *get_flash_bank_by_name(const char *name)
205 {
206 unsigned requested = get_flash_name_index(name);
207 unsigned found = 0;
208
209 struct flash_bank *bank;
210 for (bank = flash_banks; NULL != bank; bank = bank->next)
211 {
212 if (strcmp(bank->name, name) == 0)
213 return bank;
214 if (!flash_driver_name_matches(bank->driver->name, name))
215 continue;
216 if (++found < requested)
217 continue;
218 return bank;
219 }
220 return NULL;
221 }
222
223 struct flash_bank *get_flash_bank_by_num(int num)
224 {
225 struct flash_bank *p = get_flash_bank_by_num_noprobe(num);
226 int retval;
227
228 if (p == NULL)
229 return NULL;
230
231 retval = p->driver->auto_probe(p);
232
233 if (retval != ERROR_OK)
234 {
235 LOG_ERROR("auto_probe failed %d\n", retval);
236 return NULL;
237 }
238 return p;
239 }
240
241 /* lookup flash bank by address */
242 struct flash_bank *get_flash_bank_by_addr(struct target *target, uint32_t addr)
243 {
244 struct flash_bank *c;
245
246 /* cycle through bank list */
247 for (c = flash_banks; c; c = c->next)
248 {
249 int retval;
250 retval = c->driver->auto_probe(c);
251
252 if (retval != ERROR_OK)
253 {
254 LOG_ERROR("auto_probe failed %d\n", retval);
255 return NULL;
256 }
257 /* check whether address belongs to this flash bank */
258 if ((addr >= c->base) && (addr <= c->base + (c->size - 1)) && target == c->target)
259 return c;
260 }
261 LOG_ERROR("No flash at address 0x%08" PRIx32 "\n", addr);
262 return NULL;
263 }
264
265 int default_flash_mem_blank_check(struct flash_bank *bank)
266 {
267 struct target *target = bank->target;
268 const int buffer_size = 1024;
269 int i;
270 uint32_t nBytes;
271 int retval = ERROR_OK;
272
273 if (bank->target->state != TARGET_HALTED)
274 {
275 LOG_ERROR("Target not halted");
276 return ERROR_TARGET_NOT_HALTED;
277 }
278
279 uint8_t *buffer = malloc(buffer_size);
280
281 for (i = 0; i < bank->num_sectors; i++)
282 {
283 uint32_t j;
284 bank->sectors[i].is_erased = 1;
285
286 for (j = 0; j < bank->sectors[i].size; j += buffer_size)
287 {
288 uint32_t chunk;
289 chunk = buffer_size;
290 if (chunk > (j - bank->sectors[i].size))
291 {
292 chunk = (j - bank->sectors[i].size);
293 }
294
295 retval = target_read_memory(target, bank->base + bank->sectors[i].offset + j, 4, chunk/4, buffer);
296 if (retval != ERROR_OK)
297 {
298 goto done;
299 }
300
301 for (nBytes = 0; nBytes < chunk; nBytes++)
302 {
303 if (buffer[nBytes] != 0xFF)
304 {
305 bank->sectors[i].is_erased = 0;
306 break;
307 }
308 }
309 }
310 }
311
312 done:
313 free(buffer);
314
315 return retval;
316 }
317
318 int default_flash_blank_check(struct flash_bank *bank)
319 {
320 struct target *target = bank->target;
321 int i;
322 int retval;
323 int fast_check = 0;
324 uint32_t blank;
325
326 if (bank->target->state != TARGET_HALTED)
327 {
328 LOG_ERROR("Target not halted");
329 return ERROR_TARGET_NOT_HALTED;
330 }
331
332 for (i = 0; i < bank->num_sectors; i++)
333 {
334 uint32_t address = bank->base + bank->sectors[i].offset;
335 uint32_t size = bank->sectors[i].size;
336
337 if ((retval = target_blank_check_memory(target, address, size, &blank)) != ERROR_OK)
338 {
339 fast_check = 0;
340 break;
341 }
342 if (blank == 0xFF)
343 bank->sectors[i].is_erased = 1;
344 else
345 bank->sectors[i].is_erased = 0;
346 fast_check = 1;
347 }
348
349 if (!fast_check)
350 {
351 LOG_USER("Running slow fallback erase check - add working memory");
352 return default_flash_mem_blank_check(bank);
353 }
354
355 return ERROR_OK;
356 }
357
358 /* Manipulate given flash region, selecting the bank according to target
359 * and address. Maps an address range to a set of sectors, and issues
360 * the callback() on that set ... e.g. to erase or unprotect its members.
361 *
362 * (Note a current bad assumption: that protection operates on the same
363 * size sectors as erase operations use.)
364 *
365 * The "pad_reason" parameter is a kind of boolean: when it's NULL, the
366 * range must fit those sectors exactly. This is clearly safe; it can't
367 * erase data which the caller said to leave alone, for example. If it's
368 * non-NULL, rather than failing, extra data in the first and/or last
369 * sectors will be added to the range, and that reason string is used when
370 * warning about those additions.
371 */
372 static int flash_iterate_address_range(struct target *target,
373 char *pad_reason, uint32_t addr, uint32_t length,
374 int (*callback)(struct flash_bank *bank, int first, int last))
375 {
376 struct flash_bank *c;
377 uint32_t last_addr = addr + length; /* first address AFTER end */
378 int first = -1;
379 int last = -1;
380 int i;
381
382 if ((c = get_flash_bank_by_addr(target, addr)) == NULL)
383 return ERROR_FLASH_DST_OUT_OF_BANK; /* no corresponding bank found */
384
385 if (c->size == 0 || c->num_sectors == 0)
386 {
387 LOG_ERROR("Bank is invalid");
388 return ERROR_FLASH_BANK_INVALID;
389 }
390
391 if (length == 0)
392 {
393 /* special case, erase whole bank when length is zero */
394 if (addr != c->base)
395 {
396 LOG_ERROR("Whole bank access must start at beginning of bank.");
397 return ERROR_FLASH_DST_BREAKS_ALIGNMENT;
398 }
399
400 return callback(c, 0, c->num_sectors - 1);
401 }
402
403 /* check whether it all fits in this bank */
404 if (addr + length - 1 > c->base + c->size - 1)
405 {
406 LOG_ERROR("Flash access does not fit into bank.");
407 return ERROR_FLASH_DST_BREAKS_ALIGNMENT;
408 }
409
410 /** @todo: handle erasures that cross into adjacent banks */
411
412 addr -= c->base;
413 last_addr -= c->base;
414
415 for (i = 0; i < c->num_sectors; i++)
416 {
417 struct flash_sector *f = c->sectors + i;
418 uint32_t end = f->offset + f->size;
419
420 /* start only on a sector boundary */
421 if (first < 0) {
422 /* scanned past the first sector? */
423 if (addr < f->offset)
424 break;
425
426 /* is this the first sector? */
427 if (addr == f->offset)
428 first = i;
429
430 /* Does this need head-padding? If so, pad and warn;
431 * or else force an error.
432 *
433 * Such padding can make trouble, since *WE* can't
434 * ever know if that data was in use. The warning
435 * should help users sort out messes later.
436 */
437 else if (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) f->offset,
443 (unsigned) addr - 1);
444 first = i;
445 } else
446 continue;
447 }
448
449 /* is this (also?) the last sector? */
450 if (last_addr == end) {
451 last = i;
452 break;
453 }
454
455 /* Does this need tail-padding? If so, pad and warn;
456 * or else force an error.
457 */
458 if (last_addr < end && pad_reason) {
459 /* FIXME say how many bytes (e.g. 80 KB) */
460 LOG_WARNING("Adding extra %s range, "
461 "%#8.8x to %#8.8x",
462 pad_reason,
463 (unsigned) last_addr,
464 (unsigned) end - 1);
465 last = i;
466 break;
467 }
468
469 /* MUST finish on a sector boundary */
470 if (last_addr <= f->offset)
471 break;
472 }
473
474 /* invalid start or end address? */
475 if (first == -1 || last == -1) {
476 LOG_ERROR("address range 0x%8.8x .. 0x%8.8x "
477 "is not sector-aligned",
478 (unsigned) (c->base + addr),
479 (unsigned) (c->base + last_addr - 1));
480 return ERROR_FLASH_DST_BREAKS_ALIGNMENT;
481 }
482
483 /* The NOR driver may trim this range down, based on what
484 * sectors are already erased/unprotected. GDB currently
485 * blocks such optimizations.
486 */
487 return callback(c, first, last);
488 }
489
490 int flash_erase_address_range(struct target *target,
491 bool pad, uint32_t addr, uint32_t length)
492 {
493 return flash_iterate_address_range(target, pad ? "erase" : NULL,
494 addr, length, &flash_driver_erase);
495 }
496
497 static int flash_driver_unprotect(struct flash_bank *bank, int first, int last)
498 {
499 return flash_driver_protect(bank, 0, first, last);
500 }
501
502 static int flash_unlock_address_range(struct target *target, uint32_t addr, uint32_t length)
503 {
504 /* By default, pad to sector boundaries ... the real issue here
505 * is that our (only) caller *permanently* removes protection,
506 * and doesn't restore it.
507 */
508 return flash_iterate_address_range(target, "unprotect",
509 addr, length, &flash_driver_unprotect);
510 }
511
512 static int compare_section (const void * a, const void * b)
513 {
514 struct imageection *b1, *b2;
515 b1=*((struct imageection **)a);
516 b2=*((struct imageection **)b);
517
518 if (b1->base_address == b2->base_address)
519 {
520 return 0;
521 } else if (b1->base_address > b2->base_address)
522 {
523 return 1;
524 } else
525 {
526 return -1;
527 }
528 }
529
530
531 int flash_write_unlock(struct target *target, struct image *image,
532 uint32_t *written, int erase, bool unlock)
533 {
534 int retval = ERROR_OK;
535
536 int section;
537 uint32_t section_offset;
538 struct flash_bank *c;
539 int *padding;
540
541 section = 0;
542 section_offset = 0;
543
544 if (written)
545 *written = 0;
546
547 if (erase)
548 {
549 /* assume all sectors need erasing - stops any problems
550 * when flash_write is called multiple times */
551
552 flash_set_dirty();
553 }
554
555 /* allocate padding array */
556 padding = calloc(image->num_sections, sizeof(*padding));
557
558 /* This fn requires all sections to be in ascending order of addresses,
559 * whereas an image can have sections out of order. */
560 struct imageection **sections = malloc(sizeof(struct imageection *) *
561 image->num_sections);
562 int i;
563 for (i = 0; i < image->num_sections; i++)
564 {
565 sections[i] = &image->sections[i];
566 }
567
568 qsort(sections, image->num_sections, sizeof(struct imageection *),
569 compare_section);
570
571 /* loop until we reach end of the image */
572 while (section < image->num_sections)
573 {
574 uint32_t buffer_size;
575 uint8_t *buffer;
576 int section_first;
577 int section_last;
578 uint32_t run_address = sections[section]->base_address + section_offset;
579 uint32_t run_size = sections[section]->size - section_offset;
580 int pad_bytes = 0;
581
582 if (sections[section]->size == 0)
583 {
584 LOG_WARNING("empty section %d", section);
585 section++;
586 section_offset = 0;
587 continue;
588 }
589
590 /* find the corresponding flash bank */
591 if ((c = get_flash_bank_by_addr(target, run_address)) == NULL)
592 {
593 section++; /* and skip it */
594 section_offset = 0;
595 continue;
596 }
597
598 /* collect consecutive sections which fall into the same bank */
599 section_first = section;
600 section_last = section;
601 padding[section] = 0;
602 while ((run_address + run_size - 1 < c->base + c->size - 1)
603 && (section_last + 1 < image->num_sections))
604 {
605 if (sections[section_last + 1]->base_address < (run_address + run_size))
606 {
607 LOG_DEBUG("section %d out of order "
608 "(surprising, but supported)",
609 section_last + 1);
610 /* REVISIT this can break with autoerase ...
611 * clobbering data after it's written.
612 */
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 if ((run_address + run_size + pad_bytes) > (c->base + c->size))
635 break;
636 padding[section_last] = pad_bytes;
637 run_size += sections[++section_last]->size;
638 run_size += pad_bytes;
639
640 if (pad_bytes > 0)
641 LOG_INFO("Padding image section %d with %d bytes", section_last-1, pad_bytes);
642 }
643
644 /* fit the run into bank constraints */
645 if (run_address + run_size - 1 > c->base + c->size - 1)
646 {
647 /* REVISIT isn't this superfluous, given the while()
648 * loop conditions above??
649 */
650 LOG_WARNING("writing %d bytes only - as image section is %d bytes and bank is only %d bytes", \
651 (int)(c->base + c->size - run_address), (int)(run_size), (int)(c->size));
652 run_size = c->base + c->size - run_address;
653 }
654
655 /* If we're applying any sector automagic, then pad this
656 * (maybe-combined) segment to the end of its last sector.
657 */
658 if (unlock || erase) {
659 int sector;
660 uint32_t offset_start = run_address - c->base;
661 uint32_t offset_end = offset_start + run_size;
662 uint32_t end = offset_end, delta;
663
664 for (sector = 0; sector < c->num_sectors; sector++) {
665 end = c->sectors[sector].offset
666 + c->sectors[sector].size;
667 if (offset_end <= end)
668 break;
669 }
670
671 delta = end - offset_end;
672 padding[section_last] += delta;
673 run_size += delta;
674 }
675
676 /* allocate buffer */
677 buffer = malloc(run_size);
678 buffer_size = 0;
679
680 /* read sections to the buffer */
681 while (buffer_size < run_size)
682 {
683 size_t size_read;
684
685 size_read = run_size - buffer_size;
686 if (size_read > sections[section]->size - section_offset)
687 size_read = sections[section]->size - section_offset;
688
689 /* KLUDGE!
690 *
691 * #¤%#"%¤% we have to figure out the section # from the sorted
692 * list of pointers to sections to invoke image_read_section()...
693 */
694 int t_section_num = (sections[section] - image->sections) / sizeof(struct imageection);
695
696 if ((retval = image_read_section(image, t_section_num, section_offset,
697 size_read, buffer + buffer_size, &size_read)) != ERROR_OK || size_read == 0)
698 {
699 free(buffer);
700 goto done;
701 }
702
703 /* see if we need to pad the section */
704 while (padding[section]--)
705 (buffer + buffer_size)[size_read++] = 0xff;
706
707 buffer_size += size_read;
708 section_offset += size_read;
709
710 if (section_offset >= sections[section]->size)
711 {
712 section++;
713 section_offset = 0;
714 }
715 }
716
717 retval = ERROR_OK;
718
719 if (unlock)
720 {
721 retval = flash_unlock_address_range(target, run_address, run_size);
722 }
723 if (retval == ERROR_OK)
724 {
725 if (erase)
726 {
727 /* calculate and erase sectors */
728 retval = flash_erase_address_range(target,
729 true, run_address, run_size);
730 }
731 }
732
733 if (retval == ERROR_OK)
734 {
735 /* write flash sectors */
736 retval = flash_driver_write(c, buffer, run_address - c->base, run_size);
737 }
738
739 free(buffer);
740
741 if (retval != ERROR_OK)
742 {
743 /* abort operation */
744 goto done;
745 }
746
747 if (written != NULL)
748 *written += run_size; /* add run size to total written counter */
749 }
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 }
764
765 /**
766 * Invalidates cached flash state which a target can change as it runs.
767 *
768 * @param target The target being resumed
769 *
770 * OpenOCD caches some flash state for brief periods. For example, a sector
771 * that is protected must be unprotected before OpenOCD tries to write it,
772 * Also, a sector that's not erased must be erased before it's written.
773 *
774 * As a rule, OpenOCD and target firmware can both modify the flash, so when
775 * a target starts running, OpenOCD needs to invalidate its cached state.
776 */
777 void nor_resume(struct target *target)
778 {
779 struct flash_bank *bank;
780
781 for (bank = flash_banks; bank; bank = bank->next) {
782 int i;
783
784 if (bank->target != target)
785 continue;
786
787 for (i = 0; i < bank->num_sectors; i++) {
788 struct flash_sector *sector = bank->sectors + i;
789
790 sector->is_erased = -1;
791 sector->is_protected = -1;
792 }
793 }
794 }

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)