1 /***************************************************************************
2 * Copyright (C) 2009 by Duane Ellis *
3 * openocd@duaneellis.com *
5 * Copyright (C) 2010 by Olaf Lüke (at91sam3s* support) *
6 * olaf@uni-paderborn.de *
8 * Copyright (C) 2011 by Olivier Schonken, Jim Norris *
9 * (at91sam3x* & at91sam4 support)* *
11 * Copyright (C) 2015 Morgan Quigley *
12 * (atsamv, atsams, and atsame support) *
14 * This program is free software; you can redistribute it and/or modify *
15 * it under the terms of the GNU General Public License as published by *
16 * the Free Software Foundation; either version 2 of the License, or *
17 * (at your option) any later version. *
19 * This program is distributed in the hope that it will be useful, *
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
22 * GNU General Public License for more details. *
24 * You should have received a copy of the GNU General Public License *
25 * along with this program. If not, see <http://www.gnu.org/licenses/>. *
26 ***************************************************************************/
28 /* Some of the the lower level code was based on code supplied by
29 * ATMEL under this copyright. */
31 /* BEGIN ATMEL COPYRIGHT */
32 /* ----------------------------------------------------------------------------
33 * ATMEL Microcontroller Software Support
34 * ----------------------------------------------------------------------------
35 * Copyright (c) 2009, Atmel Corporation
37 * All rights reserved.
39 * Redistribution and use in source and binary forms, with or without
40 * modification, are permitted provided that the following conditions are met:
42 * - Redistributions of source code must retain the above copyright notice,
43 * this list of conditions and the disclaimer below.
45 * Atmel's name may not be used to endorse or promote products derived from
46 * this software without specific prior written permission.
48 * DISCLAIMER: THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR
49 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
50 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
51 * DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, INDIRECT,
52 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
53 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
54 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
55 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
56 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
57 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
58 * ----------------------------------------------------------------------------
60 /* END ATMEL COPYRIGHT */
67 #include <helper/time_support.h>
69 #define REG_NAME_WIDTH (12)
71 #define SAMV_EFC_FCMD_GETD (0x0) /* (EFC) Get Flash Descriptor */
72 #define SAMV_EFC_FCMD_WP (0x1) /* (EFC) Write Page */
73 #define SAMV_EFC_FCMD_WPL (0x2) /* (EFC) Write Page and Lock */
74 #define SAMV_EFC_FCMD_EWP (0x3) /* (EFC) Erase Page and Write Page */
75 #define SAMV_EFC_FCMD_EWPL (0x4) /* (EFC) Erase Page, Write Page then Lock*/
76 #define SAMV_EFC_FCMD_EA (0x5) /* (EFC) Erase All */
77 #define SAMV_EFC_FCMD_EPA (0x7) /* (EFC) Erase pages */
78 #define SAMV_EFC_FCMD_SLB (0x8) /* (EFC) Set Lock Bit */
79 #define SAMV_EFC_FCMD_CLB (0x9) /* (EFC) Clear Lock Bit */
80 #define SAMV_EFC_FCMD_GLB (0xA) /* (EFC) Get Lock Bit */
81 #define SAMV_EFC_FCMD_SFB (0xB) /* (EFC) Set Fuse Bit */
82 #define SAMV_EFC_FCMD_CFB (0xC) /* (EFC) Clear Fuse Bit */
83 #define SAMV_EFC_FCMD_GFB (0xD) /* (EFC) Get Fuse Bit */
85 #define OFFSET_EFC_FMR 0
86 #define OFFSET_EFC_FCR 4
87 #define OFFSET_EFC_FSR 8
88 #define OFFSET_EFC_FRR 12
90 #define SAMV_CHIPID_CIDR (0x400E0940)
91 #define SAMV_NUM_GPNVM_BITS 9
92 #define SAMV_CONTROLLER_ADDR (0x400e0c00)
93 #define SAMV_SECTOR_SIZE 16384
94 #define SAMV_PAGE_SIZE 512
95 #define SAMV_FLASH_BASE 0x00400000
97 extern struct flash_driver atsamv_flash
;
99 struct samv_flash_bank
{
102 unsigned gpnvm
[SAMV_NUM_GPNVM_BITS
];
105 /* The actual sector size of the SAMV7 flash memory is 128K bytes.
106 * 16 sectors for a 2048KB device. The lock regions are 16KB per lock
107 * region, with a 2048KB device having 128 lock regions.
108 * For the best results, num_sectors is thus set to the number of lock
109 * regions, and the sector_size set to the lock region size. Page
110 * erases are used to erase 16KB sections when programming */
112 static int samv_efc_get_status(struct target
*target
, uint32_t *v
)
114 int r
= target_read_u32(target
, SAMV_CONTROLLER_ADDR
+ OFFSET_EFC_FSR
, v
);
118 static int samv_efc_get_result(struct target
*target
, uint32_t *v
)
121 int r
= target_read_u32(target
, SAMV_CONTROLLER_ADDR
+ OFFSET_EFC_FRR
, &rv
);
127 static int samv_efc_start_command(struct target
*target
,
128 unsigned command
, unsigned argument
)
131 samv_efc_get_status(target
, &v
);
133 LOG_ERROR("flash controller is not ready");
137 v
= (0x5A << 24) | (argument
<< 8) | command
;
138 LOG_DEBUG("starting flash command: 0x%08x", (unsigned int)(v
));
139 int r
= target_write_u32(target
, SAMV_CONTROLLER_ADDR
+ OFFSET_EFC_FCR
, v
);
141 LOG_DEBUG("write failed");
145 static int samv_efc_perform_command(struct target
*target
,
146 unsigned command
, unsigned argument
, uint32_t *status
)
150 int64_t ms_now
, ms_end
;
155 r
= samv_efc_start_command(target
, command
, argument
);
159 ms_end
= 10000 + timeval_ms();
162 r
= samv_efc_get_status(target
, &v
);
165 ms_now
= timeval_ms();
166 if (ms_now
> ms_end
) {
168 LOG_ERROR("Command timeout");
171 } while ((v
& 1) == 0);
173 /* if requested, copy the flash controller error bits back to the caller */
179 static int samv_erase_pages(struct target
*target
,
180 int first_page
, int num_pages
, uint32_t *status
)
202 * According to the datasheet FARG[15:2] defines the page from which
203 * the erase will start.This page must be modulo 4, 8, 16 or 32
204 * according to the number of pages to erase. FARG[1:0] defines the
205 * number of pages to be erased. Previously (firstpage << 2) was used
206 * to conform to this, seems it should not be shifted...
208 return samv_efc_perform_command(target
, SAMV_EFC_FCMD_EPA
,
209 first_page
| erase_pages
, status
);
212 static int samv_get_gpnvm(struct target
*target
, unsigned gpnvm
, unsigned *out
)
217 if (gpnvm
>= SAMV_NUM_GPNVM_BITS
) {
218 LOG_ERROR("invalid gpnvm %d, max: %d", gpnvm
, SAMV_NUM_GPNVM_BITS
);
222 r
= samv_efc_perform_command(target
, SAMV_EFC_FCMD_GFB
, 0, NULL
);
224 LOG_ERROR("samv_get_gpnvm failed");
228 r
= samv_efc_get_result(target
, &v
);
231 *out
= (v
>> gpnvm
) & 1;
236 static int samv_clear_gpnvm(struct target
*target
, unsigned gpnvm
)
241 if (gpnvm
>= SAMV_NUM_GPNVM_BITS
) {
242 LOG_ERROR("invalid gpnvm %d, max: %d", gpnvm
, SAMV_NUM_GPNVM_BITS
);
245 r
= samv_get_gpnvm(target
, gpnvm
, &v
);
247 LOG_DEBUG("get gpnvm failed: %d", r
);
250 r
= samv_efc_perform_command(target
, SAMV_EFC_FCMD_CFB
, gpnvm
, NULL
);
251 LOG_DEBUG("clear gpnvm result: %d", r
);
255 static int samv_set_gpnvm(struct target
*target
, unsigned gpnvm
)
259 if (gpnvm
>= SAMV_NUM_GPNVM_BITS
) {
260 LOG_ERROR("invalid gpnvm %d, max: %d", gpnvm
, SAMV_NUM_GPNVM_BITS
);
264 r
= samv_get_gpnvm(target
, gpnvm
, &v
);
268 r
= ERROR_OK
; /* the gpnvm bit is already set */
270 /* we need to set it */
271 r
= samv_efc_perform_command(target
, SAMV_EFC_FCMD_SFB
, gpnvm
, NULL
);
276 static int samv_flash_unlock(struct target
*target
,
277 unsigned start_sector
, unsigned end_sector
)
282 uint32_t pages_per_sector
;
284 /* todo: look into this... i think this should be done on lock regions */
285 pages_per_sector
= SAMV_SECTOR_SIZE
/ SAMV_PAGE_SIZE
;
286 while (start_sector
<= end_sector
) {
287 pg
= start_sector
* pages_per_sector
;
288 r
= samv_efc_perform_command(target
, SAMV_EFC_FCMD_CLB
, pg
, &status
);
296 static int samv_flash_lock(struct target
*target
,
297 unsigned start_sector
, unsigned end_sector
)
301 uint32_t pages_per_sector
;
304 /* todo: look into this... i think this should be done on lock regions */
305 pages_per_sector
= SAMV_SECTOR_SIZE
/ SAMV_PAGE_SIZE
;
306 while (start_sector
<= end_sector
) {
307 pg
= start_sector
* pages_per_sector
;
308 r
= samv_efc_perform_command(target
, SAMV_EFC_FCMD_SLB
, pg
, &status
);
316 static int samv_protect_check(struct flash_bank
*bank
)
321 r
= samv_efc_perform_command(bank
->target
, SAMV_EFC_FCMD_GLB
, 0, NULL
);
323 samv_efc_get_result(bank
->target
, &v
[0]);
324 samv_efc_get_result(bank
->target
, &v
[1]);
325 samv_efc_get_result(bank
->target
, &v
[2]);
326 r
= samv_efc_get_result(bank
->target
, &v
[3]);
331 for (int x
= 0; x
< bank
->num_sectors
; x
++)
332 bank
->sectors
[x
].is_protected
= (!!(v
[x
>> 5] & (1 << (x
% 32))));
336 FLASH_BANK_COMMAND_HANDLER(samv_flash_bank_command
)
338 LOG_INFO("flash bank command");
339 struct samv_flash_bank
*samv_info
;
340 samv_info
= calloc(1, sizeof(struct samv_flash_bank
));
341 bank
->driver_priv
= samv_info
;
345 static int samv_get_device_id(struct flash_bank
*bank
, uint32_t *device_id
)
347 return target_read_u32(bank
->target
, SAMV_CHIPID_CIDR
, device_id
);
350 static int samv_probe(struct flash_bank
*bank
)
353 int r
= samv_get_device_id(bank
, &device_id
);
356 LOG_INFO("device id = 0x%08" PRIx32
"", device_id
);
358 uint8_t eproc
= (device_id
>> 5) & 0x7;
360 LOG_ERROR("unexpected eproc code: %d was expecting 0 (Cortex-M7)", eproc
);
364 uint8_t nvm_size_code
= (device_id
>> 8) & 0xf;
365 switch (nvm_size_code
) {
367 bank
->size
= 512 * 1024;
370 bank
->size
= 1024 * 1024;
373 bank
->size
= 2048 * 1024;
376 LOG_ERROR("unrecognized flash size code: %d", nvm_size_code
);
381 struct samv_flash_bank
*samv_info
= bank
->driver_priv
;
382 samv_info
->size_bytes
= bank
->size
;
383 samv_info
->probed
= 1;
385 bank
->base
= SAMV_FLASH_BASE
;
386 bank
->num_sectors
= bank
->size
/ SAMV_SECTOR_SIZE
;
387 bank
->sectors
= calloc(bank
->num_sectors
, sizeof(struct flash_sector
));
388 for (int s
= 0; s
< (int)bank
->num_sectors
; s
++) {
389 bank
->sectors
[s
].size
= SAMV_SECTOR_SIZE
;
390 bank
->sectors
[s
].offset
= s
* SAMV_SECTOR_SIZE
;
391 bank
->sectors
[s
].is_erased
= -1;
392 bank
->sectors
[s
].is_protected
= -1;
395 r
= samv_protect_check(bank
);
402 static int samv_auto_probe(struct flash_bank
*bank
)
404 struct samv_flash_bank
*samv_info
= bank
->driver_priv
;
405 if (samv_info
->probed
)
407 return samv_probe(bank
);
410 static int samv_erase(struct flash_bank
*bank
, int first
, int last
)
412 const int page_count
= 32; /* 32 pages equals 16 KB lock region */
414 if (bank
->target
->state
!= TARGET_HALTED
) {
415 LOG_ERROR("Target not halted");
416 return ERROR_TARGET_NOT_HALTED
;
419 int r
= samv_auto_probe(bank
);
423 /* easy case: we've been requested to erase the entire flash */
424 if ((first
== 0) && ((last
+ 1) == (int)(bank
->num_sectors
)))
425 return samv_efc_perform_command(bank
->target
, SAMV_EFC_FCMD_EA
, 0, NULL
);
427 LOG_INFO("erasing lock regions %d-%d...", first
, last
);
429 for (int i
= first
; i
<= last
; i
++) {
431 r
= samv_erase_pages(bank
->target
, (i
* page_count
), page_count
, &status
);
432 LOG_INFO("erasing lock region %d", i
);
434 LOG_ERROR("error performing erase page @ lock region number %d",
436 if (status
& (1 << 2)) {
437 LOG_ERROR("lock region %d is locked", (unsigned int)(i
));
440 if (status
& (1 << 1)) {
441 LOG_ERROR("flash command error @lock region %d", (unsigned int)(i
));
448 static int samv_protect(struct flash_bank
*bank
, int set
, int first
, int last
)
450 if (bank
->target
->state
!= TARGET_HALTED
) {
451 LOG_ERROR("Target not halted");
452 return ERROR_TARGET_NOT_HALTED
;
457 r
= samv_flash_lock(bank
->target
, (unsigned)(first
), (unsigned)(last
));
459 r
= samv_flash_unlock(bank
->target
, (unsigned)(first
), (unsigned)(last
));
464 static int samv_page_read(struct target
*target
,
465 unsigned page_num
, uint8_t *buf
)
467 uint32_t addr
= SAMV_FLASH_BASE
+ page_num
* SAMV_PAGE_SIZE
;
468 int r
= target_read_memory(target
, addr
, 4, SAMV_PAGE_SIZE
/ 4, buf
);
470 LOG_ERROR("flash program failed to read page @ 0x%08x",
471 (unsigned int)(addr
));
475 static int samv_page_write(struct target
*target
,
476 unsigned pagenum
, const uint8_t *buf
)
479 const uint32_t addr
= SAMV_FLASH_BASE
+ pagenum
* SAMV_PAGE_SIZE
;
482 LOG_DEBUG("write page %u at address 0x%08x", pagenum
, (unsigned int)addr
);
483 r
= target_write_memory(target
, addr
, 4, SAMV_PAGE_SIZE
/ 4, buf
);
485 LOG_ERROR("failed to buffer page at 0x%08x", (unsigned int)addr
);
489 r
= samv_efc_perform_command(target
, SAMV_EFC_FCMD_WP
, pagenum
, &status
);
491 LOG_ERROR("error performing write page at 0x%08x", (unsigned int)addr
);
492 if (status
& (1 << 2)) {
493 LOG_ERROR("page at 0x%08x is locked", (unsigned int)addr
);
496 if (status
& (1 << 1)) {
497 LOG_ERROR("flash command error at 0x%08x", (unsigned int)addr
);
503 static int samv_write(struct flash_bank
*bank
, const uint8_t *buffer
,
504 uint32_t offset
, uint32_t count
)
506 if (bank
->target
->state
!= TARGET_HALTED
) {
507 LOG_ERROR("target not halted");
508 return ERROR_TARGET_NOT_HALTED
;
514 if ((offset
+ count
) > bank
->size
) {
515 LOG_ERROR("flash write error - past end of bank");
516 LOG_ERROR(" offset: 0x%08x, count 0x%08x, bank end: 0x%08x",
517 (unsigned int)(offset
),
518 (unsigned int)(count
),
519 (unsigned int)(bank
->size
));
523 uint8_t pagebuffer
[SAMV_PAGE_SIZE
] = {0};
524 uint32_t page_cur
= offset
/ SAMV_PAGE_SIZE
;
525 uint32_t page_end
= (offset
+ count
- 1) / SAMV_PAGE_SIZE
;
527 LOG_DEBUG("offset: 0x%08x, count: 0x%08x",
528 (unsigned int)(offset
), (unsigned int)(count
));
529 LOG_DEBUG("page start: %d, page end: %d", (int)(page_cur
), (int)(page_end
));
531 /* Special case: all one page */
533 /* (1) non-aligned start */
535 /* (3) non-aligned end. */
538 uint32_t page_offset
;
540 /* handle special case - all one page. */
541 if (page_cur
== page_end
) {
542 LOG_DEBUG("special case, all in one page");
543 r
= samv_page_read(bank
->target
, page_cur
, pagebuffer
);
547 page_offset
= offset
& (SAMV_PAGE_SIZE
-1);
548 memcpy(pagebuffer
+ page_offset
, buffer
, count
);
550 r
= samv_page_write(bank
->target
, page_cur
, pagebuffer
);
556 /* step 1) handle the non-aligned starting address */
557 page_offset
= offset
& (SAMV_PAGE_SIZE
- 1);
559 LOG_DEBUG("non-aligned start");
560 /* read the partial page */
561 r
= samv_page_read(bank
->target
, page_cur
, pagebuffer
);
565 /* over-write with new data */
566 uint32_t n
= SAMV_PAGE_SIZE
- page_offset
;
567 memcpy(pagebuffer
+ page_offset
, buffer
, n
);
569 r
= samv_page_write(bank
->target
, page_cur
, pagebuffer
);
579 /* By checking that offset is correct here, we also fix a clang warning */
580 assert(offset
% SAMV_PAGE_SIZE
== 0);
582 /* step 2) handle the full pages */
583 LOG_DEBUG("full page loop: cur=%d, end=%d, count = 0x%08x",
584 (int)page_cur
, (int)page_end
, (unsigned int)(count
));
586 while ((page_cur
< page_end
) && (count
>= SAMV_PAGE_SIZE
)) {
587 r
= samv_page_write(bank
->target
, page_cur
, buffer
);
590 count
-= SAMV_PAGE_SIZE
;
591 buffer
+= SAMV_PAGE_SIZE
;
595 /* step 3) write final page, if it's partial (otherwise it's already done) */
597 LOG_DEBUG("final partial page, count = 0x%08x", (unsigned int)(count
));
598 /* we have a partial page */
599 r
= samv_page_read(bank
->target
, page_cur
, pagebuffer
);
602 memcpy(pagebuffer
, buffer
, count
); /* data goes at start of page */
603 r
= samv_page_write(bank
->target
, page_cur
, pagebuffer
);
610 static int samv_get_info(struct flash_bank
*bank
, char *buf
, int buf_size
)
612 struct samv_flash_bank
*samv_info
= bank
->driver_priv
;
613 if (!samv_info
->probed
) {
614 int r
= samv_probe(bank
);
618 snprintf(buf
, buf_size
, "Cortex-M7 detected with %d kB flash",
623 COMMAND_HANDLER(samv_handle_gpnvm_command
)
625 struct flash_bank
*bank
= get_flash_bank_by_num_noprobe(0);
628 struct samv_flash_bank
*samv_info
= bank
->driver_priv
;
629 struct target
*target
= bank
->target
;
631 if (target
->state
!= TARGET_HALTED
) {
632 LOG_ERROR("target not halted");
633 return ERROR_TARGET_NOT_HALTED
;
637 if (!samv_info
->probed
) {
638 r
= samv_auto_probe(bank
);
653 if (!strcmp(CMD_ARGV
[0], "show") && !strcmp(CMD_ARGV
[1], "all"))
657 COMMAND_PARSE_NUMBER(u32
, CMD_ARGV
[1], v32
);
662 return ERROR_COMMAND_SYNTAX_ERROR
;
667 if (!strcmp("show", CMD_ARGV
[0])) {
671 for (int x
= 0; x
< SAMV_NUM_GPNVM_BITS
; x
++) {
672 r
= samv_get_gpnvm(target
, x
, &v
);
675 command_print(CMD_CTX
, "samv-gpnvm%u: %u", x
, v
);
679 if ((who
>= 0) && (((unsigned)who
) < SAMV_NUM_GPNVM_BITS
)) {
680 r
= samv_get_gpnvm(target
, who
, &v
);
681 command_print(CMD_CTX
, "samv-gpnvm%u: %u", who
, v
);
684 command_print(CMD_CTX
, "invalid gpnvm: %u", who
);
685 return ERROR_COMMAND_SYNTAX_ERROR
;
690 command_print(CMD_CTX
, "missing gpnvm number");
691 return ERROR_COMMAND_SYNTAX_ERROR
;
694 if (!strcmp("set", CMD_ARGV
[0]))
695 r
= samv_set_gpnvm(target
, who
);
696 else if (!strcmp("clr", CMD_ARGV
[0]) || !strcmp("clear", CMD_ARGV
[0]))
697 r
= samv_clear_gpnvm(target
, who
);
699 command_print(CMD_CTX
, "unknown command: %s", CMD_ARGV
[0]);
700 r
= ERROR_COMMAND_SYNTAX_ERROR
;
705 static const struct command_registration atsamv_exec_command_handlers
[] = {
708 .handler
= samv_handle_gpnvm_command
,
709 .mode
= COMMAND_EXEC
,
710 .usage
= "[('clr'|'set'|'show') bitnum]",
711 .help
= "Without arguments, shows all bits in the gpnvm "
712 "register. Otherwise, clears, sets, or shows one "
713 "General Purpose Non-Volatile Memory (gpnvm) bit.",
715 COMMAND_REGISTRATION_DONE
718 static const struct command_registration atsamv_command_handlers
[] = {
722 .help
= "atsamv flash command group",
724 .chain
= atsamv_exec_command_handlers
,
726 COMMAND_REGISTRATION_DONE
729 struct flash_driver atsamv_flash
= {
731 .commands
= atsamv_command_handlers
,
732 .flash_bank_command
= samv_flash_bank_command
,
734 .protect
= samv_protect
,
736 .read
= default_flash_read
,
738 .auto_probe
= samv_auto_probe
,
739 .erase_check
= default_flash_blank_check
,
740 .protect_check
= samv_protect_check
,
741 .info
= samv_get_info
,
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)