1e71753e67edc5110100dc6af989ba438886b79a
[openocd.git] / src / flash / nor / str9x.c
1 /***************************************************************************
2 * Copyright (C) 2005 by Dominic Rath *
3 * Dominic.Rath@gmx.de *
4 * *
5 * Copyright (C) 2008 by Spencer Oliver *
6 * spen@spen-soft.co.uk *
7 *
8 * Copyright (C) 2008 by Oyvind Harboe *
9 * oyvind.harboe@zylin.com *
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 * This program is distributed in the hope that it will be useful, *
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
19 * GNU General Public License for more details. *
20 * *
21 * You should have received a copy of the GNU General Public License *
22 * along with this program; if not, write to the *
23 * Free Software Foundation, Inc., *
24 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
25 ***************************************************************************/
26 #ifdef HAVE_CONFIG_H
27 #include "config.h"
28 #endif
29
30 #include "imp.h"
31 #include "str9x.h"
32 #include <target/arm966e.h>
33 #include <target/algorithm.h>
34
35
36 static uint32_t bank1start = 0x00080000;
37
38 static int str9x_build_block_list(struct flash_bank *bank)
39 {
40 struct str9x_flash_bank *str9x_info = bank->driver_priv;
41
42 int i;
43 int num_sectors;
44 int b0_sectors = 0, b1_sectors = 0;
45 uint32_t offset = 0;
46
47 /* set if we have large flash str9 */
48 str9x_info->variant = 0;
49 str9x_info->bank1 = 0;
50
51 switch (bank->size)
52 {
53 case (256 * 1024):
54 b0_sectors = 4;
55 break;
56 case (512 * 1024):
57 b0_sectors = 8;
58 break;
59 case (1024 * 1024):
60 bank1start = 0x00100000;
61 str9x_info->variant = 1;
62 b0_sectors = 16;
63 break;
64 case (2048 * 1024):
65 bank1start = 0x00200000;
66 str9x_info->variant = 1;
67 b0_sectors = 32;
68 break;
69 case (128 * 1024):
70 str9x_info->variant = 1;
71 str9x_info->bank1 = 1;
72 b1_sectors = 8;
73 bank1start = bank->base;
74 break;
75 case (32 * 1024):
76 str9x_info->bank1 = 1;
77 b1_sectors = 4;
78 bank1start = bank->base;
79 break;
80 default:
81 LOG_ERROR("BUG: unknown bank->size encountered");
82 exit(-1);
83 }
84
85 num_sectors = b0_sectors + b1_sectors;
86
87 bank->num_sectors = num_sectors;
88 bank->sectors = malloc(sizeof(struct flash_sector) * num_sectors);
89 str9x_info->sector_bits = malloc(sizeof(uint32_t) * num_sectors);
90
91 num_sectors = 0;
92
93 for (i = 0; i < b0_sectors; i++)
94 {
95 bank->sectors[num_sectors].offset = offset;
96 bank->sectors[num_sectors].size = 0x10000;
97 offset += bank->sectors[i].size;
98 bank->sectors[num_sectors].is_erased = -1;
99 bank->sectors[num_sectors].is_protected = 1;
100 str9x_info->sector_bits[num_sectors++] = (1 << i);
101 }
102
103 for (i = 0; i < b1_sectors; i++)
104 {
105 bank->sectors[num_sectors].offset = offset;
106 bank->sectors[num_sectors].size = str9x_info->variant == 0 ? 0x2000 : 0x4000;
107 offset += bank->sectors[i].size;
108 bank->sectors[num_sectors].is_erased = -1;
109 bank->sectors[num_sectors].is_protected = 1;
110 if (str9x_info->variant)
111 str9x_info->sector_bits[num_sectors++] = (1 << i);
112 else
113 str9x_info->sector_bits[num_sectors++] = (1 << (i + 8));
114 }
115
116 return ERROR_OK;
117 }
118
119 /* flash bank str9x <base> <size> 0 0 <target#>
120 */
121 FLASH_BANK_COMMAND_HANDLER(str9x_flash_bank_command)
122 {
123 struct str9x_flash_bank *str9x_info;
124
125 if (CMD_ARGC < 6)
126 {
127 LOG_WARNING("incomplete flash_bank str9x configuration");
128 return ERROR_FLASH_BANK_INVALID;
129 }
130
131 str9x_info = malloc(sizeof(struct str9x_flash_bank));
132 bank->driver_priv = str9x_info;
133
134 str9x_build_block_list(bank);
135
136 str9x_info->write_algorithm = NULL;
137
138 return ERROR_OK;
139 }
140
141 static int str9x_protect_check(struct flash_bank *bank)
142 {
143 int retval;
144 struct str9x_flash_bank *str9x_info = bank->driver_priv;
145 struct target *target = bank->target;
146
147 int i;
148 uint32_t adr;
149 uint32_t status = 0;
150 uint16_t hstatus = 0;
151
152 if (bank->target->state != TARGET_HALTED)
153 {
154 LOG_ERROR("Target not halted");
155 return ERROR_TARGET_NOT_HALTED;
156 }
157
158 /* read level one protection */
159
160 if (str9x_info->variant)
161 {
162 if (str9x_info->bank1)
163 {
164 adr = bank1start + 0x18;
165 if ((retval = target_write_u16(target, adr, 0x90)) != ERROR_OK)
166 {
167 return retval;
168 }
169 if ((retval = target_read_u16(target, adr, &hstatus)) != ERROR_OK)
170 {
171 return retval;
172 }
173 status = hstatus;
174 }
175 else
176 {
177 adr = bank1start + 0x14;
178 if ((retval = target_write_u16(target, adr, 0x90)) != ERROR_OK)
179 {
180 return retval;
181 }
182 if ((retval = target_read_u32(target, adr, &status)) != ERROR_OK)
183 {
184 return retval;
185 }
186 }
187 }
188 else
189 {
190 adr = bank1start + 0x10;
191 if ((retval = target_write_u16(target, adr, 0x90)) != ERROR_OK)
192 {
193 return retval;
194 }
195 if ((retval = target_read_u16(target, adr, &hstatus)) != ERROR_OK)
196 {
197 return retval;
198 }
199 status = hstatus;
200 }
201
202 /* read array command */
203 if ((retval = target_write_u16(target, adr, 0xFF)) != ERROR_OK)
204 {
205 return retval;
206 }
207
208 for (i = 0; i < bank->num_sectors; i++)
209 {
210 if (status & str9x_info->sector_bits[i])
211 bank->sectors[i].is_protected = 1;
212 else
213 bank->sectors[i].is_protected = 0;
214 }
215
216 return ERROR_OK;
217 }
218
219 static int str9x_erase(struct flash_bank *bank, int first, int last)
220 {
221 struct target *target = bank->target;
222 int i;
223 uint32_t adr;
224 uint8_t status;
225 uint8_t erase_cmd;
226 int total_timeout;
227
228 if (bank->target->state != TARGET_HALTED)
229 {
230 LOG_ERROR("Target not halted");
231 return ERROR_TARGET_NOT_HALTED;
232 }
233
234 /* Check if we can erase whole bank */
235 if ((first == 0) && (last == (bank->num_sectors - 1)))
236 {
237 /* Optimize to run erase bank command instead of sector */
238 erase_cmd = 0x80;
239 /* Add timeout duration since erase bank takes more time */
240 total_timeout = 1000 * bank->num_sectors;
241 }
242 else
243 {
244 /* Erase sector command */
245 erase_cmd = 0x20;
246 total_timeout = 1000;
247 }
248
249 for (i = first; i <= last; i++)
250 {
251 int retval;
252 adr = bank->base + bank->sectors[i].offset;
253
254 /* erase sectors or block */
255 if ((retval = target_write_u16(target, adr, erase_cmd)) != ERROR_OK)
256 {
257 return retval;
258 }
259 if ((retval = target_write_u16(target, adr, 0xD0)) != ERROR_OK)
260 {
261 return retval;
262 }
263
264 /* get status */
265 if ((retval = target_write_u16(target, adr, 0x70)) != ERROR_OK)
266 {
267 return retval;
268 }
269
270 int timeout;
271 for (timeout = 0; timeout < total_timeout; timeout++)
272 {
273 if ((retval = target_read_u8(target, adr, &status)) != ERROR_OK)
274 {
275 return retval;
276 }
277 if (status & 0x80)
278 break;
279 alive_sleep(1);
280 }
281 if (timeout == total_timeout)
282 {
283 LOG_ERROR("erase timed out");
284 return ERROR_FAIL;
285 }
286
287 /* clear status, also clear read array */
288 if ((retval = target_write_u16(target, adr, 0x50)) != ERROR_OK)
289 {
290 return retval;
291 }
292
293 /* read array command */
294 if ((retval = target_write_u16(target, adr, 0xFF)) != ERROR_OK)
295 {
296 return retval;
297 }
298
299 if (status & 0x22)
300 {
301 LOG_ERROR("error erasing flash bank, status: 0x%x", status);
302 return ERROR_FLASH_OPERATION_FAILED;
303 }
304
305 /* If we ran erase bank command, we are finished */
306 if (erase_cmd == 0x80)
307 break;
308 }
309
310 for (i = first; i <= last; i++)
311 bank->sectors[i].is_erased = 1;
312
313 return ERROR_OK;
314 }
315
316 static int str9x_protect(struct flash_bank *bank,
317 int set, int first, int last)
318 {
319 struct target *target = bank->target;
320 int i;
321 uint32_t adr;
322 uint8_t status;
323
324 if (bank->target->state != TARGET_HALTED)
325 {
326 LOG_ERROR("Target not halted");
327 return ERROR_TARGET_NOT_HALTED;
328 }
329
330 for (i = first; i <= last; i++)
331 {
332 /* Level One Protection */
333
334 adr = bank->base + bank->sectors[i].offset;
335
336 target_write_u16(target, adr, 0x60);
337 if (set)
338 target_write_u16(target, adr, 0x01);
339 else
340 target_write_u16(target, adr, 0xD0);
341
342 /* query status */
343 target_read_u8(target, adr, &status);
344
345 /* clear status, also clear read array */
346 target_write_u16(target, adr, 0x50);
347
348 /* read array command */
349 target_write_u16(target, adr, 0xFF);
350 }
351
352 return ERROR_OK;
353 }
354
355 static int str9x_write_block(struct flash_bank *bank,
356 uint8_t *buffer, uint32_t offset, uint32_t count)
357 {
358 struct str9x_flash_bank *str9x_info = bank->driver_priv;
359 struct target *target = bank->target;
360 uint32_t buffer_size = 32768;
361 struct working_area *source;
362 uint32_t address = bank->base + offset;
363 struct reg_param reg_params[4];
364 struct arm_algorithm armv4_5_info;
365 int retval = ERROR_OK;
366
367 /* see contib/loaders/flash/str9x.s for src */
368
369 static const uint32_t str9x_flash_write_code[] = {
370 /* write: */
371 0xe3c14003, /* bic r4, r1, #3 */
372 0xe3a03040, /* mov r3, #0x40 */
373 0xe1c430b0, /* strh r3, [r4, #0] */
374 0xe0d030b2, /* ldrh r3, [r0], #2 */
375 0xe0c130b2, /* strh r3, [r1], #2 */
376 0xe3a03070, /* mov r3, #0x70 */
377 0xe1c430b0, /* strh r3, [r4, #0] */
378 /* busy: */
379 0xe5d43000, /* ldrb r3, [r4, #0] */
380 0xe3130080, /* tst r3, #0x80 */
381 0x0afffffc, /* beq busy */
382 0xe3a05050, /* mov r5, #0x50 */
383 0xe1c450b0, /* strh r5, [r4, #0] */
384 0xe3a050ff, /* mov r5, #0xFF */
385 0xe1c450b0, /* strh r5, [r4, #0] */
386 0xe3130012, /* tst r3, #0x12 */
387 0x1a000001, /* bne exit */
388 0xe2522001, /* subs r2, r2, #1 */
389 0x1affffed, /* bne write */
390 /* exit: */
391 0xe1200070, /* bkpt #0 */
392 };
393
394 /* flash write code */
395 if (target_alloc_working_area(target, sizeof(str9x_flash_write_code),
396 &str9x_info->write_algorithm) != ERROR_OK)
397 {
398 LOG_WARNING("no working area available, can't do block memory writes");
399 return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
400 };
401
402 target_write_buffer(target, str9x_info->write_algorithm->address,
403 sizeof(str9x_flash_write_code),
404 (uint8_t*)str9x_flash_write_code);
405
406 /* memory buffer */
407 while (target_alloc_working_area_try(target, buffer_size, &source) != ERROR_OK)
408 {
409 buffer_size /= 2;
410 if (buffer_size <= 256)
411 {
412 /* if we already allocated the writing code, but failed to get a
413 * buffer, free the algorithm */
414 if (str9x_info->write_algorithm)
415 target_free_working_area(target, str9x_info->write_algorithm);
416
417 LOG_WARNING("no large enough working area available, can't do block memory writes");
418 return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
419 }
420 }
421
422 armv4_5_info.common_magic = ARM_COMMON_MAGIC;
423 armv4_5_info.core_mode = ARM_MODE_SVC;
424 armv4_5_info.core_state = ARM_STATE_ARM;
425
426 init_reg_param(&reg_params[0], "r0", 32, PARAM_OUT);
427 init_reg_param(&reg_params[1], "r1", 32, PARAM_OUT);
428 init_reg_param(&reg_params[2], "r2", 32, PARAM_OUT);
429 init_reg_param(&reg_params[3], "r3", 32, PARAM_IN);
430
431 while (count > 0)
432 {
433 uint32_t thisrun_count = (count > (buffer_size / 2)) ? (buffer_size / 2) : count;
434
435 target_write_buffer(target, source->address, thisrun_count * 2, buffer);
436
437 buf_set_u32(reg_params[0].value, 0, 32, source->address);
438 buf_set_u32(reg_params[1].value, 0, 32, address);
439 buf_set_u32(reg_params[2].value, 0, 32, thisrun_count);
440
441 if ((retval = target_run_algorithm(target, 0, NULL, 4, reg_params,
442 str9x_info->write_algorithm->address,
443 0, 10000, &armv4_5_info)) != ERROR_OK)
444 {
445 LOG_ERROR("error executing str9x flash write algorithm");
446 retval = ERROR_FLASH_OPERATION_FAILED;
447 break;
448 }
449
450 if (buf_get_u32(reg_params[3].value, 0, 32) != 0x80)
451 {
452 retval = ERROR_FLASH_OPERATION_FAILED;
453 break;
454 }
455
456 buffer += thisrun_count * 2;
457 address += thisrun_count * 2;
458 count -= thisrun_count;
459 }
460
461 target_free_working_area(target, source);
462 target_free_working_area(target, str9x_info->write_algorithm);
463
464 destroy_reg_param(&reg_params[0]);
465 destroy_reg_param(&reg_params[1]);
466 destroy_reg_param(&reg_params[2]);
467 destroy_reg_param(&reg_params[3]);
468
469 return retval;
470 }
471
472 static int str9x_write(struct flash_bank *bank,
473 uint8_t *buffer, uint32_t offset, uint32_t count)
474 {
475 struct target *target = bank->target;
476 uint32_t words_remaining = (count / 2);
477 uint32_t bytes_remaining = (count & 0x00000001);
478 uint32_t address = bank->base + offset;
479 uint32_t bytes_written = 0;
480 uint8_t status;
481 int retval;
482 uint32_t check_address = offset;
483 uint32_t bank_adr;
484 int i;
485
486 if (bank->target->state != TARGET_HALTED)
487 {
488 LOG_ERROR("Target not halted");
489 return ERROR_TARGET_NOT_HALTED;
490 }
491
492 if (offset & 0x1)
493 {
494 LOG_WARNING("offset 0x%" PRIx32 " breaks required 2-byte alignment", offset);
495 return ERROR_FLASH_DST_BREAKS_ALIGNMENT;
496 }
497
498 for (i = 0; i < bank->num_sectors; i++)
499 {
500 uint32_t sec_start = bank->sectors[i].offset;
501 uint32_t sec_end = sec_start + bank->sectors[i].size;
502
503 /* check if destination falls within the current sector */
504 if ((check_address >= sec_start) && (check_address < sec_end))
505 {
506 /* check if destination ends in the current sector */
507 if (offset + count < sec_end)
508 check_address = offset + count;
509 else
510 check_address = sec_end;
511 }
512 }
513
514 if (check_address != offset + count)
515 return ERROR_FLASH_DST_OUT_OF_BANK;
516
517 /* multiple half words (2-byte) to be programmed? */
518 if (words_remaining > 0)
519 {
520 /* try using a block write */
521 if ((retval = str9x_write_block(bank, buffer, offset, words_remaining)) != ERROR_OK)
522 {
523 if (retval == ERROR_TARGET_RESOURCE_NOT_AVAILABLE)
524 {
525 /* if block write failed (no sufficient working area),
526 * we use normal (slow) single dword accesses */
527 LOG_WARNING("couldn't use block writes, falling back to single memory accesses");
528 }
529 else if (retval == ERROR_FLASH_OPERATION_FAILED)
530 {
531 LOG_ERROR("flash writing failed with error code: 0x%x", retval);
532 return ERROR_FLASH_OPERATION_FAILED;
533 }
534 }
535 else
536 {
537 buffer += words_remaining * 2;
538 address += words_remaining * 2;
539 words_remaining = 0;
540 }
541 }
542
543 while (words_remaining > 0)
544 {
545 bank_adr = address & ~0x03;
546
547 /* write data command */
548 target_write_u16(target, bank_adr, 0x40);
549 target_write_memory(target, address, 2, 1, buffer + bytes_written);
550
551 /* get status command */
552 target_write_u16(target, bank_adr, 0x70);
553
554 int timeout;
555 for (timeout = 0; timeout < 1000; timeout++)
556 {
557 target_read_u8(target, bank_adr, &status);
558 if (status & 0x80)
559 break;
560 alive_sleep(1);
561 }
562 if (timeout == 1000)
563 {
564 LOG_ERROR("write timed out");
565 return ERROR_FAIL;
566 }
567
568 /* clear status reg and read array */
569 target_write_u16(target, bank_adr, 0x50);
570 target_write_u16(target, bank_adr, 0xFF);
571
572 if (status & 0x10)
573 return ERROR_FLASH_OPERATION_FAILED;
574 else if (status & 0x02)
575 return ERROR_FLASH_OPERATION_FAILED;
576
577 bytes_written += 2;
578 words_remaining--;
579 address += 2;
580 }
581
582 if (bytes_remaining)
583 {
584 uint8_t last_halfword[2] = {0xff, 0xff};
585 i = 0;
586
587 while (bytes_remaining > 0)
588 {
589 last_halfword[i++] = *(buffer + bytes_written);
590 bytes_remaining--;
591 bytes_written++;
592 }
593
594 bank_adr = address & ~0x03;
595
596 /* write data command */
597 target_write_u16(target, bank_adr, 0x40);
598 target_write_memory(target, address, 2, 1, last_halfword);
599
600 /* query status command */
601 target_write_u16(target, bank_adr, 0x70);
602
603 int timeout;
604 for (timeout = 0; timeout < 1000; timeout++)
605 {
606 target_read_u8(target, bank_adr, &status);
607 if (status & 0x80)
608 break;
609 alive_sleep(1);
610 }
611 if (timeout == 1000)
612 {
613 LOG_ERROR("write timed out");
614 return ERROR_FAIL;
615 }
616
617 /* clear status reg and read array */
618 target_write_u16(target, bank_adr, 0x50);
619 target_write_u16(target, bank_adr, 0xFF);
620
621 if (status & 0x10)
622 return ERROR_FLASH_OPERATION_FAILED;
623 else if (status & 0x02)
624 return ERROR_FLASH_OPERATION_FAILED;
625 }
626
627 return ERROR_OK;
628 }
629
630 static int str9x_probe(struct flash_bank *bank)
631 {
632 return ERROR_OK;
633 }
634
635 #if 0
636 COMMAND_HANDLER(str9x_handle_part_id_command)
637 {
638 return ERROR_OK;
639 }
640 #endif
641
642 static int get_str9x_info(struct flash_bank *bank, char *buf, int buf_size)
643 {
644 snprintf(buf, buf_size, "str9x flash driver info");
645 return ERROR_OK;
646 }
647
648 COMMAND_HANDLER(str9x_handle_flash_config_command)
649 {
650 struct str9x_flash_bank *str9x_info;
651 struct target *target = NULL;
652
653 if (CMD_ARGC < 5)
654 {
655 return ERROR_COMMAND_SYNTAX_ERROR;
656 }
657
658 struct flash_bank *bank;
659 int retval = CALL_COMMAND_HANDLER(flash_command_get_bank, 0, &bank);
660 if (ERROR_OK != retval)
661 return retval;
662
663 uint32_t bbsr, nbbsr, bbadr, nbbadr;
664 COMMAND_PARSE_NUMBER(u32, CMD_ARGV[1], bbsr);
665 COMMAND_PARSE_NUMBER(u32, CMD_ARGV[2], nbbsr);
666 COMMAND_PARSE_NUMBER(u32, CMD_ARGV[3], bbadr);
667 COMMAND_PARSE_NUMBER(u32, CMD_ARGV[4], nbbadr);
668
669 str9x_info = bank->driver_priv;
670
671 target = bank->target;
672
673 if (bank->target->state != TARGET_HALTED)
674 {
675 LOG_ERROR("Target not halted");
676 return ERROR_TARGET_NOT_HALTED;
677 }
678
679 /* config flash controller */
680 target_write_u32(target, FLASH_BBSR, bbsr);
681 target_write_u32(target, FLASH_NBBSR, nbbsr);
682 target_write_u32(target, FLASH_BBADR, bbadr >> 2);
683 target_write_u32(target, FLASH_NBBADR, nbbadr >> 2);
684
685 /* set bit 18 instruction TCM order as per flash programming manual */
686 arm966e_write_cp15(target, 62, 0x40000);
687
688 /* enable flash bank 1 */
689 target_write_u32(target, FLASH_CR, 0x18);
690 return ERROR_OK;
691 }
692
693 static const struct command_registration str9x_config_command_handlers[] = {
694 {
695 .name = "flash_config",
696 .handler = str9x_handle_flash_config_command,
697 .mode = COMMAND_EXEC,
698 .help = "Configure str9x flash controller, prior to "
699 "programming the flash.",
700 .usage = "bank_id BBSR NBBSR BBADR NBBADR",
701 },
702 COMMAND_REGISTRATION_DONE
703 };
704
705 static const struct command_registration str9x_command_handlers[] = {
706 {
707 .name = "str9x",
708 .mode = COMMAND_ANY,
709 .help = "str9x flash command group",
710 .chain = str9x_config_command_handlers,
711 },
712 COMMAND_REGISTRATION_DONE
713 };
714
715 struct flash_driver str9x_flash = {
716 .name = "str9x",
717 .commands = str9x_command_handlers,
718 .flash_bank_command = str9x_flash_bank_command,
719 .erase = str9x_erase,
720 .protect = str9x_protect,
721 .write = str9x_write,
722 .read = default_flash_read,
723 .probe = str9x_probe,
724 .auto_probe = str9x_probe,
725 .erase_check = default_flash_blank_check,
726 .protect_check = str9x_protect_check,
727 .info = get_str9x_info,
728 };

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)