cfe6a77709866742d622d0d4c2401c6637e570c0
[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 static const uint32_t str9x_flash_write_code[] = {
368 /* write: */
369 0xe3c14003, /* bic r4, r1, #3 */
370 0xe3a03040, /* mov r3, #0x40 */
371 0xe1c430b0, /* strh r3, [r4, #0] */
372 0xe0d030b2, /* ldrh r3, [r0], #2 */
373 0xe0c130b2, /* strh r3, [r1], #2 */
374 0xe3a03070, /* mov r3, #0x70 */
375 0xe1c430b0, /* strh r3, [r4, #0] */
376 /* busy: */
377 0xe5d43000, /* ldrb r3, [r4, #0] */
378 0xe3130080, /* tst r3, #0x80 */
379 0x0afffffc, /* beq busy */
380 0xe3a05050, /* mov r5, #0x50 */
381 0xe1c450b0, /* strh r5, [r4, #0] */
382 0xe3a050ff, /* mov r5, #0xFF */
383 0xe1c450b0, /* strh r5, [r4, #0] */
384 0xe3130012, /* tst r3, #0x12 */
385 0x1a000001, /* bne exit */
386 0xe2522001, /* subs r2, r2, #1 */
387 0x1affffed, /* bne write */
388 /* exit: */
389 0xe1200070, /* bkpt #0 */
390 };
391
392 /* flash write code */
393 if (target_alloc_working_area(target, sizeof(str9x_flash_write_code),
394 &str9x_info->write_algorithm) != ERROR_OK)
395 {
396 LOG_WARNING("no working area available, can't do block memory writes");
397 return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
398 };
399
400 target_write_buffer(target, str9x_info->write_algorithm->address,
401 sizeof(str9x_flash_write_code),
402 (uint8_t*)str9x_flash_write_code);
403
404 /* memory buffer */
405 while (target_alloc_working_area_try(target, buffer_size, &source) != ERROR_OK)
406 {
407 buffer_size /= 2;
408 if (buffer_size <= 256)
409 {
410 /* if we already allocated the writing code, but failed to get a
411 * buffer, free the algorithm */
412 if (str9x_info->write_algorithm)
413 target_free_working_area(target, str9x_info->write_algorithm);
414
415 LOG_WARNING("no large enough working area available, can't do block memory writes");
416 return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
417 }
418 }
419
420 armv4_5_info.common_magic = ARM_COMMON_MAGIC;
421 armv4_5_info.core_mode = ARM_MODE_SVC;
422 armv4_5_info.core_state = ARM_STATE_ARM;
423
424 init_reg_param(&reg_params[0], "r0", 32, PARAM_OUT);
425 init_reg_param(&reg_params[1], "r1", 32, PARAM_OUT);
426 init_reg_param(&reg_params[2], "r2", 32, PARAM_OUT);
427 init_reg_param(&reg_params[3], "r3", 32, PARAM_IN);
428
429 while (count > 0)
430 {
431 uint32_t thisrun_count = (count > (buffer_size / 2)) ? (buffer_size / 2) : count;
432
433 target_write_buffer(target, source->address, thisrun_count * 2, buffer);
434
435 buf_set_u32(reg_params[0].value, 0, 32, source->address);
436 buf_set_u32(reg_params[1].value, 0, 32, address);
437 buf_set_u32(reg_params[2].value, 0, 32, thisrun_count);
438
439 if ((retval = target_run_algorithm(target, 0, NULL, 4, reg_params,
440 str9x_info->write_algorithm->address,
441 0, 10000, &armv4_5_info)) != ERROR_OK)
442 {
443 LOG_ERROR("error executing str9x flash write algorithm");
444 retval = ERROR_FLASH_OPERATION_FAILED;
445 break;
446 }
447
448 if (buf_get_u32(reg_params[3].value, 0, 32) != 0x80)
449 {
450 retval = ERROR_FLASH_OPERATION_FAILED;
451 break;
452 }
453
454 buffer += thisrun_count * 2;
455 address += thisrun_count * 2;
456 count -= thisrun_count;
457 }
458
459 target_free_working_area(target, source);
460 target_free_working_area(target, str9x_info->write_algorithm);
461
462 destroy_reg_param(&reg_params[0]);
463 destroy_reg_param(&reg_params[1]);
464 destroy_reg_param(&reg_params[2]);
465 destroy_reg_param(&reg_params[3]);
466
467 return retval;
468 }
469
470 static int str9x_write(struct flash_bank *bank,
471 uint8_t *buffer, uint32_t offset, uint32_t count)
472 {
473 struct target *target = bank->target;
474 uint32_t words_remaining = (count / 2);
475 uint32_t bytes_remaining = (count & 0x00000001);
476 uint32_t address = bank->base + offset;
477 uint32_t bytes_written = 0;
478 uint8_t status;
479 int retval;
480 uint32_t check_address = offset;
481 uint32_t bank_adr;
482 int i;
483
484 if (bank->target->state != TARGET_HALTED)
485 {
486 LOG_ERROR("Target not halted");
487 return ERROR_TARGET_NOT_HALTED;
488 }
489
490 if (offset & 0x1)
491 {
492 LOG_WARNING("offset 0x%" PRIx32 " breaks required 2-byte alignment", offset);
493 return ERROR_FLASH_DST_BREAKS_ALIGNMENT;
494 }
495
496 for (i = 0; i < bank->num_sectors; i++)
497 {
498 uint32_t sec_start = bank->sectors[i].offset;
499 uint32_t sec_end = sec_start + bank->sectors[i].size;
500
501 /* check if destination falls within the current sector */
502 if ((check_address >= sec_start) && (check_address < sec_end))
503 {
504 /* check if destination ends in the current sector */
505 if (offset + count < sec_end)
506 check_address = offset + count;
507 else
508 check_address = sec_end;
509 }
510 }
511
512 if (check_address != offset + count)
513 return ERROR_FLASH_DST_OUT_OF_BANK;
514
515 /* multiple half words (2-byte) to be programmed? */
516 if (words_remaining > 0)
517 {
518 /* try using a block write */
519 if ((retval = str9x_write_block(bank, buffer, offset, words_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 LOG_WARNING("couldn't use block writes, falling back to single memory accesses");
526 }
527 else if (retval == ERROR_FLASH_OPERATION_FAILED)
528 {
529 LOG_ERROR("flash writing failed with error code: 0x%x", retval);
530 return ERROR_FLASH_OPERATION_FAILED;
531 }
532 }
533 else
534 {
535 buffer += words_remaining * 2;
536 address += words_remaining * 2;
537 words_remaining = 0;
538 }
539 }
540
541 while (words_remaining > 0)
542 {
543 bank_adr = address & ~0x03;
544
545 /* write data command */
546 target_write_u16(target, bank_adr, 0x40);
547 target_write_memory(target, address, 2, 1, buffer + bytes_written);
548
549 /* get status command */
550 target_write_u16(target, bank_adr, 0x70);
551
552 int timeout;
553 for (timeout = 0; timeout < 1000; timeout++)
554 {
555 target_read_u8(target, bank_adr, &status);
556 if (status & 0x80)
557 break;
558 alive_sleep(1);
559 }
560 if (timeout == 1000)
561 {
562 LOG_ERROR("write timed out");
563 return ERROR_FAIL;
564 }
565
566 /* clear status reg and read array */
567 target_write_u16(target, bank_adr, 0x50);
568 target_write_u16(target, bank_adr, 0xFF);
569
570 if (status & 0x10)
571 return ERROR_FLASH_OPERATION_FAILED;
572 else if (status & 0x02)
573 return ERROR_FLASH_OPERATION_FAILED;
574
575 bytes_written += 2;
576 words_remaining--;
577 address += 2;
578 }
579
580 if (bytes_remaining)
581 {
582 uint8_t last_halfword[2] = {0xff, 0xff};
583 i = 0;
584
585 while (bytes_remaining > 0)
586 {
587 last_halfword[i++] = *(buffer + bytes_written);
588 bytes_remaining--;
589 bytes_written++;
590 }
591
592 bank_adr = address & ~0x03;
593
594 /* write data command */
595 target_write_u16(target, bank_adr, 0x40);
596 target_write_memory(target, address, 2, 1, last_halfword);
597
598 /* query status command */
599 target_write_u16(target, bank_adr, 0x70);
600
601 int timeout;
602 for (timeout = 0; timeout < 1000; timeout++)
603 {
604 target_read_u8(target, bank_adr, &status);
605 if (status & 0x80)
606 break;
607 alive_sleep(1);
608 }
609 if (timeout == 1000)
610 {
611 LOG_ERROR("write timed out");
612 return ERROR_FAIL;
613 }
614
615 /* clear status reg and read array */
616 target_write_u16(target, bank_adr, 0x50);
617 target_write_u16(target, bank_adr, 0xFF);
618
619 if (status & 0x10)
620 return ERROR_FLASH_OPERATION_FAILED;
621 else if (status & 0x02)
622 return ERROR_FLASH_OPERATION_FAILED;
623 }
624
625 return ERROR_OK;
626 }
627
628 static int str9x_probe(struct flash_bank *bank)
629 {
630 return ERROR_OK;
631 }
632
633 #if 0
634 COMMAND_HANDLER(str9x_handle_part_id_command)
635 {
636 return ERROR_OK;
637 }
638 #endif
639
640 static int get_str9x_info(struct flash_bank *bank, char *buf, int buf_size)
641 {
642 snprintf(buf, buf_size, "str9x flash driver info");
643 return ERROR_OK;
644 }
645
646 COMMAND_HANDLER(str9x_handle_flash_config_command)
647 {
648 struct str9x_flash_bank *str9x_info;
649 struct target *target = NULL;
650
651 if (CMD_ARGC < 5)
652 {
653 return ERROR_COMMAND_SYNTAX_ERROR;
654 }
655
656 struct flash_bank *bank;
657 int retval = CALL_COMMAND_HANDLER(flash_command_get_bank, 0, &bank);
658 if (ERROR_OK != retval)
659 return retval;
660
661 uint32_t bbsr, nbbsr, bbadr, nbbadr;
662 COMMAND_PARSE_NUMBER(u32, CMD_ARGV[1], bbsr);
663 COMMAND_PARSE_NUMBER(u32, CMD_ARGV[2], nbbsr);
664 COMMAND_PARSE_NUMBER(u32, CMD_ARGV[3], bbadr);
665 COMMAND_PARSE_NUMBER(u32, CMD_ARGV[4], nbbadr);
666
667 str9x_info = bank->driver_priv;
668
669 target = bank->target;
670
671 if (bank->target->state != TARGET_HALTED)
672 {
673 LOG_ERROR("Target not halted");
674 return ERROR_TARGET_NOT_HALTED;
675 }
676
677 /* config flash controller */
678 target_write_u32(target, FLASH_BBSR, bbsr);
679 target_write_u32(target, FLASH_NBBSR, nbbsr);
680 target_write_u32(target, FLASH_BBADR, bbadr >> 2);
681 target_write_u32(target, FLASH_NBBADR, nbbadr >> 2);
682
683 /* set bit 18 instruction TCM order as per flash programming manual */
684 arm966e_write_cp15(target, 62, 0x40000);
685
686 /* enable flash bank 1 */
687 target_write_u32(target, FLASH_CR, 0x18);
688 return ERROR_OK;
689 }
690
691 static const struct command_registration str9x_config_command_handlers[] = {
692 {
693 .name = "flash_config",
694 .handler = str9x_handle_flash_config_command,
695 .mode = COMMAND_EXEC,
696 .help = "Configure str9x flash controller, prior to "
697 "programming the flash.",
698 .usage = "bank_id BBSR NBBSR BBADR NBBADR",
699 },
700 COMMAND_REGISTRATION_DONE
701 };
702
703 static const struct command_registration str9x_command_handlers[] = {
704 {
705 .name = "str9x",
706 .mode = COMMAND_ANY,
707 .help = "str9x flash command group",
708 .chain = str9x_config_command_handlers,
709 },
710 COMMAND_REGISTRATION_DONE
711 };
712
713 struct flash_driver str9x_flash = {
714 .name = "str9x",
715 .commands = str9x_command_handlers,
716 .flash_bank_command = str9x_flash_bank_command,
717 .erase = str9x_erase,
718 .protect = str9x_protect,
719 .write = str9x_write,
720 .read = default_flash_read,
721 .probe = str9x_probe,
722 .auto_probe = str9x_probe,
723 .erase_check = default_flash_blank_check,
724 .protect_check = str9x_protect_check,
725 .info = get_str9x_info,
726 };

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)