- integrated patch from Magnus Lundin that fixes at91sam7 flash timing bugs and possi...
[openocd.git] / src / flash / str7x.c
1 /***************************************************************************
2 * Copyright (C) 2005 by Dominic Rath *
3 * Dominic.Rath@gmx.de *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
19 ***************************************************************************/
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23
24 #include "replacements.h"
25
26 #include "str7x.h"
27 #include "flash.h"
28 #include "target.h"
29 #include "log.h"
30 #include "armv4_5.h"
31 #include "algorithm.h"
32 #include "binarybuffer.h"
33
34 #include <stdlib.h>
35 #include <string.h>
36 #include <unistd.h>
37
38 str7x_mem_layout_t mem_layout[] = {
39 {0x00000000, 0x02000, 0x01},
40 {0x00002000, 0x02000, 0x01},
41 {0x00004000, 0x02000, 0x01},
42 {0x00006000, 0x02000, 0x01},
43 {0x00008000, 0x08000, 0x01},
44 {0x00010000, 0x10000, 0x01},
45 {0x00020000, 0x10000, 0x01},
46 {0x00030000, 0x10000, 0x01},
47 {0x000C0000, 0x02000, 0x100},
48 {0x000C2000, 0x02000, 0x100},
49 {0,0},
50 };
51
52 int str7x_register_commands(struct command_context_s *cmd_ctx);
53 int str7x_flash_bank_command(struct command_context_s *cmd_ctx, char *cmd, char **args, int argc, struct flash_bank_s *bank);
54 int str7x_erase(struct flash_bank_s *bank, int first, int last);
55 int str7x_protect(struct flash_bank_s *bank, int set, int first, int last);
56 int str7x_write(struct flash_bank_s *bank, u8 *buffer, u32 offset, u32 count);
57 int str7x_probe(struct flash_bank_s *bank);
58 int str7x_handle_part_id_command(struct command_context_s *cmd_ctx, char *cmd, char **args, int argc);
59 int str7x_protect_check(struct flash_bank_s *bank);
60 int str7x_erase_check(struct flash_bank_s *bank);
61 int str7x_info(struct flash_bank_s *bank, char *buf, int buf_size);
62
63 flash_driver_t str7x_flash =
64 {
65 .name = "str7x",
66 .register_commands = str7x_register_commands,
67 .flash_bank_command = str7x_flash_bank_command,
68 .erase = str7x_erase,
69 .protect = str7x_protect,
70 .write = str7x_write,
71 .probe = str7x_probe,
72 .erase_check = str7x_erase_check,
73 .protect_check = str7x_protect_check,
74 .info = str7x_info
75 };
76
77 int str7x_register_commands(struct command_context_s *cmd_ctx)
78 {
79
80 return ERROR_OK;
81 }
82
83 int str7x_get_flash_adr(struct flash_bank_s *bank, u32 reg)
84 {
85 str7x_flash_bank_t *str7x_info = bank->driver_priv;
86 return (str7x_info->flash_base|reg);
87 }
88
89 int str7x_build_block_list(struct flash_bank_s *bank)
90 {
91 str7x_flash_bank_t *str7x_info = bank->driver_priv;
92
93 int i;
94 int num_sectors;
95
96 switch (bank->size)
97 {
98 case 16 * 1024:
99 num_sectors = 2;
100 break;
101 case 64 * 1024:
102 num_sectors = 5;
103 break;
104 case 128 * 1024:
105 num_sectors = 6;
106 break;
107 case 256 * 1024:
108 num_sectors = 8;
109 break;
110 default:
111 ERROR("BUG: unknown bank->size encountered");
112 exit(-1);
113 }
114
115 if( str7x_info->bank1 == 1 )
116 {
117 num_sectors += 2;
118 }
119
120 bank->num_sectors = num_sectors;
121 bank->sectors = malloc(sizeof(flash_sector_t) * num_sectors);
122
123 for (i = 0; i < num_sectors; i++)
124 {
125 bank->sectors[i].offset = mem_layout[i].sector_start;
126 bank->sectors[i].size = mem_layout[i].sector_size;
127 bank->sectors[i].is_erased = -1;
128 bank->sectors[i].is_protected = 1;
129 }
130
131 return ERROR_OK;
132 }
133
134 /* flash bank str7x <base> <size> 0 0 <str71_variant> <target#>
135 */
136 int str7x_flash_bank_command(struct command_context_s *cmd_ctx, char *cmd, char **args, int argc, struct flash_bank_s *bank)
137 {
138 str7x_flash_bank_t *str7x_info;
139
140 if (argc < 7)
141 {
142 WARNING("incomplete flash_bank str7x configuration");
143 return ERROR_FLASH_BANK_INVALID;
144 }
145
146 str7x_info = malloc(sizeof(str7x_flash_bank_t));
147 bank->driver_priv = str7x_info;
148
149 if (strcmp(args[5], "STR71x") == 0)
150 {
151 str7x_info->bank1 = 1;
152 str7x_info->flash_base = 0x40000000;
153 }
154 else if (strcmp(args[5], "STR73x") == 0)
155 {
156 str7x_info->bank1 = 0;
157 str7x_info->flash_base = 0x80000000;
158 }
159 else
160 {
161 ERROR("unknown STR7x variant");
162 free(str7x_info);
163 return ERROR_FLASH_BANK_INVALID;
164 }
165
166 str7x_info->target = get_target_by_num(strtoul(args[6], NULL, 0));
167 if (!str7x_info->target)
168 {
169 ERROR("no target '%s' configured", args[6]);
170 exit(-1);
171 }
172
173 str7x_build_block_list(bank);
174
175 str7x_info->write_algorithm = NULL;
176
177 return ERROR_OK;
178 }
179
180 u32 str7x_status(struct flash_bank_s *bank)
181 {
182 str7x_flash_bank_t *str7x_info = bank->driver_priv;
183 target_t *target = str7x_info->target;
184 u32 retval;
185
186 target_read_u32(target, str7x_get_flash_adr(bank, FLASH_CR0), &retval);
187
188 return retval;
189 }
190
191 u32 str7x_result(struct flash_bank_s *bank)
192 {
193 str7x_flash_bank_t *str7x_info = bank->driver_priv;
194 target_t *target = str7x_info->target;
195 u32 retval;
196
197 target_read_u32(target, str7x_get_flash_adr(bank, FLASH_ER), &retval);
198
199 return retval;
200 }
201
202 int str7x_blank_check(struct flash_bank_s *bank, int first, int last)
203 {
204 str7x_flash_bank_t *str7x_info = bank->driver_priv;
205 target_t *target = str7x_info->target;
206 u8 *buffer;
207 int i;
208 int nBytes;
209
210 if ((first < 0) || (last > bank->num_sectors))
211 return ERROR_FLASH_SECTOR_INVALID;
212
213 if (str7x_info->target->state != TARGET_HALTED)
214 {
215 return ERROR_TARGET_NOT_HALTED;
216 }
217
218 buffer = malloc(256);
219
220 for (i = first; i <= last; i++)
221 {
222 bank->sectors[i].is_erased = 1;
223
224 target->type->read_memory(target, bank->base + bank->sectors[i].offset, 4, 256/4, buffer);
225
226 for (nBytes = 0; nBytes < 256; nBytes++)
227 {
228 if (buffer[nBytes] != 0xFF)
229 {
230 bank->sectors[i].is_erased = 0;
231 break;
232 }
233 }
234 }
235
236 free(buffer);
237
238 return ERROR_OK;
239 }
240
241 int str7x_protect_check(struct flash_bank_s *bank)
242 {
243 str7x_flash_bank_t *str7x_info = bank->driver_priv;
244 target_t *target = str7x_info->target;
245
246 int i;
247 u32 retval;
248
249 if (str7x_info->target->state != TARGET_HALTED)
250 {
251 return ERROR_TARGET_NOT_HALTED;
252 }
253
254 target_read_u32(target, str7x_get_flash_adr(bank, FLASH_NVWPAR), &retval);
255
256 for (i = 0; i < bank->num_sectors; i++)
257 {
258 if (retval & (mem_layout[i].reg_offset << i))
259 bank->sectors[i].is_protected = 0;
260 else
261 bank->sectors[i].is_protected = 1;
262 }
263
264 return ERROR_OK;
265 }
266
267 int str7x_erase(struct flash_bank_s *bank, int first, int last)
268 {
269 str7x_flash_bank_t *str7x_info = bank->driver_priv;
270 target_t *target = str7x_info->target;
271
272 int i;
273 u32 cmd;
274 u32 retval;
275 u32 erase_blocks;
276
277 if (str7x_info->target->state != TARGET_HALTED)
278 {
279 return ERROR_TARGET_NOT_HALTED;
280 }
281
282 erase_blocks = 0;
283
284 for (i = first; i <= last; i++)
285 erase_blocks |= (mem_layout[i].reg_offset << i);
286
287 /* clear FLASH_ER register */
288 target_write_u32(target, str7x_get_flash_adr(bank, FLASH_ER), 0x0);
289
290 cmd = FLASH_SER;
291 target_write_u32(target, str7x_get_flash_adr(bank, FLASH_CR0), cmd);
292
293 cmd = erase_blocks;
294 target_write_u32(target, str7x_get_flash_adr(bank, FLASH_CR1), cmd);
295
296 cmd = FLASH_SER|FLASH_WMS;
297 target_write_u32(target, str7x_get_flash_adr(bank, FLASH_CR0), cmd);
298
299 while (((retval = str7x_status(bank)) & (FLASH_BSYA1|FLASH_BSYA2))){
300 usleep(1000);
301 }
302
303 retval = str7x_result(bank);
304
305 if (retval & FLASH_ERER)
306 return ERROR_FLASH_SECTOR_NOT_ERASED;
307 else if (retval & FLASH_WPF)
308 return ERROR_FLASH_OPERATION_FAILED;
309
310 for (i = first; i <= last; i++)
311 bank->sectors[i].is_erased = 1;
312
313 return ERROR_OK;
314 }
315
316 int str7x_protect(struct flash_bank_s *bank, int set, int first, int last)
317 {
318 str7x_flash_bank_t *str7x_info = bank->driver_priv;
319 target_t *target = str7x_info->target;
320 int i;
321 u32 cmd;
322 u32 retval;
323 u32 protect_blocks;
324
325 if (str7x_info->target->state != TARGET_HALTED)
326 {
327 return ERROR_TARGET_NOT_HALTED;
328 }
329
330 protect_blocks = 0xFFFFFFFF;
331
332 if (set)
333 {
334 for (i = first; i <= last; i++)
335 protect_blocks &= ~(mem_layout[i].reg_offset << i);
336 }
337
338 /* clear FLASH_ER register */
339 target_write_u32(target, str7x_get_flash_adr(bank, FLASH_ER), 0x0);
340
341 cmd = FLASH_SPR;
342 target_write_u32(target, str7x_get_flash_adr(bank, FLASH_CR0), cmd);
343
344 cmd = str7x_get_flash_adr(bank, FLASH_NVWPAR);
345 target_write_u32(target, str7x_get_flash_adr(bank, FLASH_AR), cmd);
346
347 cmd = protect_blocks;
348 target_write_u32(target, str7x_get_flash_adr(bank, FLASH_DR0), cmd);
349
350 cmd = FLASH_SPR|FLASH_WMS;
351 target_write_u32(target, str7x_get_flash_adr(bank, FLASH_CR0), cmd);
352
353 while (((retval = str7x_status(bank)) & (FLASH_BSYA1|FLASH_BSYA2))){
354 usleep(1000);
355 }
356
357 retval = str7x_result(bank);
358
359 DEBUG("retval: 0x%8.8x", retval);
360
361 if (retval & FLASH_ERER)
362 return ERROR_FLASH_SECTOR_NOT_ERASED;
363 else if (retval & FLASH_WPF)
364 return ERROR_FLASH_OPERATION_FAILED;
365
366 return ERROR_OK;
367 }
368
369 int str7x_write_block(struct flash_bank_s *bank, u8 *buffer, u32 offset, u32 count)
370 {
371 str7x_flash_bank_t *str7x_info = bank->driver_priv;
372 target_t *target = str7x_info->target;
373 u32 buffer_size = 8192;
374 working_area_t *source;
375 u32 address = bank->base + offset;
376 reg_param_t reg_params[5];
377 armv4_5_algorithm_t armv4_5_info;
378 int retval;
379
380 u32 str7x_flash_write_code[] = {
381 /* write: */
382 0xe3a04201, /* mov r4, #0x10000000 */
383 0xe5824000, /* str r4, [r2, #0x0] */
384 0xe5821010, /* str r1, [r2, #0x10] */
385 0xe4904004, /* ldr r4, [r0], #4 */
386 0xe5824008, /* str r4, [r2, #0x8] */
387 0xe4904004, /* ldr r4, [r0], #4 */
388 0xe582400c, /* str r4, [r2, #0xc] */
389 0xe3a04209, /* mov r4, #0x90000000 */
390 0xe5824000, /* str r4, [r2, #0x0] */
391 /* busy: */
392 0xe5924000, /* ldr r4, [r2, #0x0] */
393 0xe3140016, /* tst r4, #0x16 */
394 0x1afffffc, /* bne busy */
395 0xe5924014, /* ldr r4, [r2, #0x14] */
396 0xe31400ff, /* tst r4, #0xff */
397 0x03140c01, /* tsteq r4, #0x100 */
398 0x1a000002, /* bne exit */
399 0xe2811008, /* add r1, r1, #0x8 */
400 0xe2533001, /* subs r3, r3, #1 */
401 0x1affffec, /* bne write */
402 /* exit: */
403 0xeafffffe, /* b exit */
404 };
405
406 u8 str7x_flash_write_code_buf[80];
407 int i;
408
409 /* flash write code */
410 if (!str7x_info->write_algorithm)
411 {
412 if (target_alloc_working_area(target, 4 * 20, &str7x_info->write_algorithm) != ERROR_OK)
413 {
414 WARNING("no working area available, can't do block memory writes");
415 return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
416 };
417
418 /* convert flash writing code into a buffer in target endianness */
419 for (i = 0; i < 20; i++)
420 target_buffer_set_u32(target, str7x_flash_write_code_buf + i*4, str7x_flash_write_code[i]);
421
422 target_write_buffer(target, str7x_info->write_algorithm->address, 20 * 4, str7x_flash_write_code_buf);
423 }
424
425 /* memory buffer */
426 while (target_alloc_working_area(target, buffer_size, &source) != ERROR_OK)
427 {
428 buffer_size /= 2;
429 if (buffer_size <= 256)
430 {
431 /* if we already allocated the writing code, but failed to get a buffer, free the algorithm */
432 if (str7x_info->write_algorithm)
433 target_free_working_area(target, str7x_info->write_algorithm);
434
435 WARNING("no large enough working area available, can't do block memory writes");
436 return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
437 }
438 };
439
440 armv4_5_info.common_magic = ARMV4_5_COMMON_MAGIC;
441 armv4_5_info.core_mode = ARMV4_5_MODE_SVC;
442 armv4_5_info.core_state = ARMV4_5_STATE_ARM;
443
444 init_reg_param(&reg_params[0], "r0", 32, PARAM_OUT);
445 init_reg_param(&reg_params[1], "r1", 32, PARAM_OUT);
446 init_reg_param(&reg_params[2], "r2", 32, PARAM_OUT);
447 init_reg_param(&reg_params[3], "r3", 32, PARAM_OUT);
448 init_reg_param(&reg_params[4], "r4", 32, PARAM_IN);
449
450 while (count > 0)
451 {
452 u32 thisrun_count = (count > (buffer_size / 8)) ? (buffer_size / 8) : count;
453
454 target_write_buffer(target, source->address, thisrun_count * 8, buffer);
455
456 buf_set_u32(reg_params[0].value, 0, 32, source->address);
457 buf_set_u32(reg_params[1].value, 0, 32, address);
458 buf_set_u32(reg_params[2].value, 0, 32, str7x_get_flash_adr(bank, FLASH_CR0));
459 buf_set_u32(reg_params[3].value, 0, 32, thisrun_count);
460
461 if ((retval = target->type->run_algorithm(target, 0, NULL, 5, reg_params, str7x_info->write_algorithm->address, str7x_info->write_algorithm->address + (19 * 4), 10000, &armv4_5_info)) != ERROR_OK)
462 {
463 ERROR("error executing str7x flash write algorithm");
464 return ERROR_FLASH_OPERATION_FAILED;
465 }
466
467 if (buf_get_u32(reg_params[4].value, 0, 32) != 0x00)
468 {
469 return ERROR_FLASH_OPERATION_FAILED;
470 }
471
472 buffer += thisrun_count * 8;
473 address += thisrun_count * 8;
474 count -= thisrun_count;
475 }
476
477 destroy_reg_param(&reg_params[0]);
478 destroy_reg_param(&reg_params[1]);
479 destroy_reg_param(&reg_params[2]);
480 destroy_reg_param(&reg_params[3]);
481 destroy_reg_param(&reg_params[4]);
482
483 return ERROR_OK;
484 }
485
486 int str7x_write(struct flash_bank_s *bank, u8 *buffer, u32 offset, u32 count)
487 {
488 str7x_flash_bank_t *str7x_info = bank->driver_priv;
489 target_t *target = str7x_info->target;
490 u32 dwords_remaining = (count / 8);
491 u32 bytes_remaining = (count & 0x00000007);
492 u32 address = bank->base + offset;
493 u32 bytes_written = 0;
494 u32 cmd;
495 u32 retval;
496
497
498 if (str7x_info->target->state != TARGET_HALTED)
499 {
500 return ERROR_TARGET_NOT_HALTED;
501 }
502
503 if (offset & 0x7)
504 {
505 WARNING("offset 0x%x breaks required 8-byte alignment", offset);
506 return ERROR_FLASH_DST_BREAKS_ALIGNMENT;
507 }
508
509 if (offset + count > bank->size)
510 return ERROR_FLASH_DST_OUT_OF_BANK;
511
512 /* clear FLASH_ER register */
513 target_write_u32(target, str7x_get_flash_adr(bank, FLASH_ER), 0x0);
514
515 /* multiple dwords (8-byte) to be programmed? */
516 if (dwords_remaining > 0)
517 {
518 /* try using a block write */
519 if ((retval = str7x_write_block(bank, buffer, offset, dwords_remaining)) != ERROR_OK)
520 {
521 if (retval == ERROR_TARGET_RESOURCE_NOT_AVAILABLE)
522 {
523 /* if block write failed (no sufficient working area),
524 * we use normal (slow) single dword accesses */
525 WARNING("couldn't use block writes, falling back to single memory accesses");
526 }
527 else if (retval == ERROR_FLASH_OPERATION_FAILED)
528 {
529 /* if an error occured, we examine the reason, and quit */
530 retval = str7x_result(bank);
531
532 ERROR("flash writing failed with error code: 0x%x", retval);
533 return ERROR_FLASH_OPERATION_FAILED;
534 }
535 }
536 else
537 {
538 buffer += dwords_remaining * 8;
539 address += dwords_remaining * 8;
540 dwords_remaining = 0;
541 }
542 }
543
544 while (dwords_remaining > 0)
545 {
546 // command
547 cmd = FLASH_DWPG;
548 target_write_u32(target, str7x_get_flash_adr(bank, FLASH_CR0), cmd);
549
550 // address
551 target_write_u32(target, str7x_get_flash_adr(bank, FLASH_AR), address);
552
553 // data word 1
554 target->type->write_memory(target, str7x_get_flash_adr(bank, FLASH_DR0), 4, 1, buffer + bytes_written);
555 bytes_written += 4;
556
557 // data word 2
558 target->type->write_memory(target, str7x_get_flash_adr(bank, FLASH_DR1), 4, 1, buffer + bytes_written);
559 bytes_written += 4;
560
561 /* start programming cycle */
562 cmd = FLASH_DWPG | FLASH_WMS;
563 target_write_u32(target, str7x_get_flash_adr(bank, FLASH_CR0), cmd);
564
565 while (((retval = str7x_status(bank)) & (FLASH_BSYA1 | FLASH_BSYA2)))
566 {
567 usleep(1000);
568 }
569
570 retval = str7x_result(bank);
571
572 if (retval & FLASH_PGER)
573 return ERROR_FLASH_OPERATION_FAILED;
574 else if (retval & FLASH_WPF)
575 return ERROR_FLASH_OPERATION_FAILED;
576
577 dwords_remaining--;
578 address += 8;
579 }
580
581 if (bytes_remaining)
582 {
583 u8 last_dword[8] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
584 int i = 0;
585
586 while(bytes_remaining > 0)
587 {
588 last_dword[i++] = *(buffer + bytes_written);
589 bytes_remaining--;
590 bytes_written++;
591 }
592
593 // command
594 cmd = FLASH_DWPG;
595 target_write_u32(target, str7x_get_flash_adr(bank, FLASH_CR0), cmd);
596
597 // address
598 target_write_u32(target, str7x_get_flash_adr(bank, FLASH_AR), address);
599
600 // data word 1
601 target->type->write_memory(target, str7x_get_flash_adr(bank, FLASH_DR0), 4, 1, last_dword);
602 bytes_written += 4;
603
604 // data word 2
605 target->type->write_memory(target, str7x_get_flash_adr(bank, FLASH_DR1), 4, 1, last_dword + 4);
606 bytes_written += 4;
607
608 /* start programming cycle */
609 cmd = FLASH_DWPG | FLASH_WMS;
610 target_write_u32(target, str7x_get_flash_adr(bank, FLASH_CR0), cmd);
611
612 while (((retval = str7x_status(bank)) & (FLASH_BSYA1 | FLASH_BSYA2)))
613 {
614 usleep(1000);
615 }
616
617 retval = str7x_result(bank);
618
619 if (retval & FLASH_PGER)
620 return ERROR_FLASH_OPERATION_FAILED;
621 else if (retval & FLASH_WPF)
622 return ERROR_FLASH_OPERATION_FAILED;
623 }
624
625 return ERROR_OK;
626 }
627
628 int str7x_probe(struct flash_bank_s *bank)
629 {
630 return ERROR_OK;
631 }
632
633 int str7x_handle_part_id_command(struct command_context_s *cmd_ctx, char *cmd, char **args, int argc)
634 {
635 return ERROR_OK;
636 }
637
638 int str7x_erase_check(struct flash_bank_s *bank)
639 {
640 return str7x_blank_check(bank, 0, bank->num_sectors - 1);
641 }
642
643 int str7x_info(struct flash_bank_s *bank, char *buf, int buf_size)
644 {
645 snprintf(buf, buf_size, "str7x flash driver info" );
646 return ERROR_OK;
647 }

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)