flash/nor: fix doc/help and range test for flash protect
[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 * Copyright (C) 2010 by Antonio Borneo <borneo.antonio@gmail.com> *
7 * *
8 * This program is free software; you can redistribute it and/or modify *
9 * it under the terms of the GNU General Public License as published by *
10 * the Free Software Foundation; either version 2 of the License, or *
11 * (at your option) any later version. *
12 * *
13 * This program is distributed in the hope that it will be useful, *
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16 * GNU General Public License for more details. *
17 * *
18 * You should have received a copy of the GNU General Public License *
19 * along with this program. If not, see <http://www.gnu.org/licenses/>. *
20 ***************************************************************************/
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25 #include <flash/common.h>
26 #include <flash/nor/core.h>
27 #include <flash/nor/imp.h>
28 #include <target/image.h>
29
30 /**
31 * @file
32 * Upper level of NOR flash framework.
33 * The lower level interfaces are to drivers. These upper level ones
34 * primarily support access from Tcl scripts or from GDB.
35 */
36
37 static struct flash_bank *flash_banks;
38
39 int flash_driver_erase(struct flash_bank *bank, int first, int last)
40 {
41 int retval;
42
43 retval = bank->driver->erase(bank, first, last);
44 if (retval != ERROR_OK)
45 LOG_ERROR("failed erasing sectors %d to %d", first, last);
46
47 return retval;
48 }
49
50 int flash_driver_protect(struct flash_bank *bank, int set, int first, int last)
51 {
52 int retval;
53 int num_blocks;
54
55 if (bank->num_prot_blocks)
56 num_blocks = bank->num_prot_blocks;
57 else
58 num_blocks = bank->num_sectors;
59
60
61 /* callers may not supply illegal parameters ... */
62 if (first < 0 || first > last || last >= num_blocks) {
63 LOG_ERROR("illegal protection block range");
64 return ERROR_FAIL;
65 }
66
67 /* force "set" to 0/1 */
68 set = !!set;
69
70 /* DANGER!
71 *
72 * We must not use any cached information about protection state!!!!
73 *
74 * There are a million things that could change the protect state:
75 *
76 * the target could have reset, power cycled, been hot plugged,
77 * the application could have run, etc.
78 *
79 * Drivers only receive valid protection block range.
80 */
81 retval = bank->driver->protect(bank, set, first, last);
82 if (retval != ERROR_OK)
83 LOG_ERROR("failed setting protection for blocks %d to %d", first, last);
84
85 return retval;
86 }
87
88 int flash_driver_write(struct flash_bank *bank,
89 uint8_t *buffer, uint32_t offset, uint32_t count)
90 {
91 int retval;
92
93 retval = bank->driver->write(bank, buffer, offset, count);
94 if (retval != ERROR_OK) {
95 LOG_ERROR(
96 "error writing to flash at address 0x%08" PRIx32 " at offset 0x%8.8" PRIx32,
97 bank->base,
98 offset);
99 }
100
101 return retval;
102 }
103
104 int flash_driver_read(struct flash_bank *bank,
105 uint8_t *buffer, uint32_t offset, uint32_t count)
106 {
107 int retval;
108
109 LOG_DEBUG("call flash_driver_read()");
110
111 retval = bank->driver->read(bank, buffer, offset, count);
112 if (retval != ERROR_OK) {
113 LOG_ERROR(
114 "error reading to flash at address 0x%08" PRIx32 " at offset 0x%8.8" PRIx32,
115 bank->base,
116 offset);
117 }
118
119 return retval;
120 }
121
122 int default_flash_read(struct flash_bank *bank,
123 uint8_t *buffer, uint32_t offset, uint32_t count)
124 {
125 return target_read_buffer(bank->target, offset + bank->base, count, buffer);
126 }
127
128 void flash_bank_add(struct flash_bank *bank)
129 {
130 /* put flash bank in linked list */
131 unsigned bank_num = 0;
132 if (flash_banks) {
133 /* find last flash bank */
134 struct flash_bank *p = flash_banks;
135 while (NULL != p->next) {
136 bank_num += 1;
137 p = p->next;
138 }
139 p->next = bank;
140 bank_num += 1;
141 } else
142 flash_banks = bank;
143
144 bank->bank_number = bank_num;
145 }
146
147 struct flash_bank *flash_bank_list(void)
148 {
149 return flash_banks;
150 }
151
152 struct flash_bank *get_flash_bank_by_num_noprobe(int num)
153 {
154 struct flash_bank *p;
155 int i = 0;
156
157 for (p = flash_banks; p; p = p->next) {
158 if (i++ == num)
159 return p;
160 }
161 LOG_ERROR("flash bank %d does not exist", num);
162 return NULL;
163 }
164
165 int flash_get_bank_count(void)
166 {
167 struct flash_bank *p;
168 int i = 0;
169 for (p = flash_banks; p; p = p->next)
170 i++;
171 return i;
172 }
173
174 struct flash_bank *get_flash_bank_by_name_noprobe(const char *name)
175 {
176 unsigned requested = get_flash_name_index(name);
177 unsigned found = 0;
178
179 struct flash_bank *bank;
180 for (bank = flash_banks; NULL != bank; bank = bank->next) {
181 if (strcmp(bank->name, name) == 0)
182 return bank;
183 if (!flash_driver_name_matches(bank->driver->name, name))
184 continue;
185 if (++found < requested)
186 continue;
187 return bank;
188 }
189 return NULL;
190 }
191
192 int get_flash_bank_by_name(const char *name, struct flash_bank **bank_result)
193 {
194 struct flash_bank *bank;
195 int retval;
196
197 bank = get_flash_bank_by_name_noprobe(name);
198 if (bank != NULL) {
199 retval = bank->driver->auto_probe(bank);
200
201 if (retval != ERROR_OK) {
202 LOG_ERROR("auto_probe failed");
203 return retval;
204 }
205 }
206
207 *bank_result = bank;
208 return ERROR_OK;
209 }
210
211 int get_flash_bank_by_num(int num, struct flash_bank **bank)
212 {
213 struct flash_bank *p = get_flash_bank_by_num_noprobe(num);
214 int retval;
215
216 if (p == NULL)
217 return ERROR_FAIL;
218
219 retval = p->driver->auto_probe(p);
220
221 if (retval != ERROR_OK) {
222 LOG_ERROR("auto_probe failed");
223 return retval;
224 }
225 *bank = p;
226 return ERROR_OK;
227 }
228
229 /* lookup flash bank by address, bank not found is success, but
230 * result_bank is set to NULL. */
231 int get_flash_bank_by_addr(struct target *target,
232 uint32_t addr,
233 bool check,
234 struct flash_bank **result_bank)
235 {
236 struct flash_bank *c;
237
238 /* cycle through bank list */
239 for (c = flash_banks; c; c = c->next) {
240 if (c->target != target)
241 continue;
242
243 int retval;
244 retval = c->driver->auto_probe(c);
245
246 if (retval != ERROR_OK) {
247 LOG_ERROR("auto_probe failed");
248 return retval;
249 }
250 /* check whether address belongs to this flash bank */
251 if ((addr >= c->base) && (addr <= c->base + (c->size - 1))) {
252 *result_bank = c;
253 return ERROR_OK;
254 }
255 }
256 *result_bank = NULL;
257 if (check) {
258 LOG_ERROR("No flash at address 0x%08" PRIx32, addr);
259 return ERROR_FAIL;
260 }
261 return ERROR_OK;
262 }
263
264 static int default_flash_mem_blank_check(struct flash_bank *bank)
265 {
266 struct target *target = bank->target;
267 const int buffer_size = 1024;
268 int i;
269 uint32_t nBytes;
270 int retval = ERROR_OK;
271
272 if (bank->target->state != TARGET_HALTED) {
273 LOG_ERROR("Target not halted");
274 return ERROR_TARGET_NOT_HALTED;
275 }
276
277 uint8_t *buffer = malloc(buffer_size);
278
279 for (i = 0; i < bank->num_sectors; i++) {
280 uint32_t j;
281 bank->sectors[i].is_erased = 1;
282
283 for (j = 0; j < bank->sectors[i].size; j += buffer_size) {
284 uint32_t chunk;
285 chunk = buffer_size;
286 if (chunk > (j - bank->sectors[i].size))
287 chunk = (j - bank->sectors[i].size);
288
289 retval = target_read_memory(target,
290 bank->base + bank->sectors[i].offset + j,
291 4,
292 chunk/4,
293 buffer);
294 if (retval != ERROR_OK)
295 goto done;
296
297 for (nBytes = 0; nBytes < chunk; nBytes++) {
298 if (buffer[nBytes] != bank->erased_value) {
299 bank->sectors[i].is_erased = 0;
300 break;
301 }
302 }
303 }
304 }
305
306 done:
307 free(buffer);
308
309 return retval;
310 }
311
312 int default_flash_blank_check(struct flash_bank *bank)
313 {
314 struct target *target = bank->target;
315 int i;
316 int retval;
317 int fast_check = 0;
318 uint32_t blank;
319
320 if (bank->target->state != TARGET_HALTED) {
321 LOG_ERROR("Target not halted");
322 return ERROR_TARGET_NOT_HALTED;
323 }
324
325 for (i = 0; i < bank->num_sectors; i++) {
326 uint32_t address = bank->base + bank->sectors[i].offset;
327 uint32_t size = bank->sectors[i].size;
328
329 retval = target_blank_check_memory(target, address, size, &blank, bank->erased_value);
330 if (retval != ERROR_OK) {
331 fast_check = 0;
332 break;
333 }
334 if (blank == bank->erased_value)
335 bank->sectors[i].is_erased = 1;
336 else
337 bank->sectors[i].is_erased = 0;
338 fast_check = 1;
339 }
340
341 if (!fast_check) {
342 LOG_USER("Running slow fallback erase check - add working memory");
343 return default_flash_mem_blank_check(bank);
344 }
345
346 return ERROR_OK;
347 }
348
349 /* Manipulate given flash region, selecting the bank according to target
350 * and address. Maps an address range to a set of sectors, and issues
351 * the callback() on that set ... e.g. to erase or unprotect its members.
352 *
353 * Parameter iterate_protect_blocks switches iteration of protect block
354 * instead of erase sectors. If there is no protect blocks array, sectors
355 * are used in iteration, so compatibility for old flash drivers is retained.
356 *
357 * The "pad_reason" parameter is a kind of boolean: when it's NULL, the
358 * range must fit those sectors exactly. This is clearly safe; it can't
359 * erase data which the caller said to leave alone, for example. If it's
360 * non-NULL, rather than failing, extra data in the first and/or last
361 * sectors will be added to the range, and that reason string is used when
362 * warning about those additions.
363 */
364 static int flash_iterate_address_range_inner(struct target *target,
365 char *pad_reason, uint32_t addr, uint32_t length,
366 bool iterate_protect_blocks,
367 int (*callback)(struct flash_bank *bank, int first, int last))
368 {
369 struct flash_bank *c;
370 struct flash_sector *block_array;
371 uint32_t last_addr = addr + length; /* first address AFTER end */
372 int first = -1;
373 int last = -1;
374 int i;
375 int num_blocks;
376
377 int retval = get_flash_bank_by_addr(target, addr, true, &c);
378 if (retval != ERROR_OK)
379 return retval;
380
381 if (c->size == 0 || c->num_sectors == 0) {
382 LOG_ERROR("Bank is invalid");
383 return ERROR_FLASH_BANK_INVALID;
384 }
385
386 if (length == 0) {
387 /* special case, erase whole bank when length is zero */
388 if (addr != c->base) {
389 LOG_ERROR("Whole bank access must start at beginning of bank.");
390 return ERROR_FLASH_DST_BREAKS_ALIGNMENT;
391 }
392
393 return callback(c, 0, c->num_sectors - 1);
394 }
395
396 /* check whether it all fits in this bank */
397 if (addr + length - 1 > c->base + c->size - 1) {
398 LOG_ERROR("Flash access does not fit into bank.");
399 return ERROR_FLASH_DST_BREAKS_ALIGNMENT;
400 }
401
402 addr -= c->base;
403 last_addr -= c->base;
404
405 if (iterate_protect_blocks && c->prot_blocks && c->num_prot_blocks) {
406 block_array = c->prot_blocks;
407 num_blocks = c->num_prot_blocks;
408 } else {
409 block_array = c->sectors;
410 num_blocks = c->num_sectors;
411 iterate_protect_blocks = false;
412 }
413
414
415 for (i = 0; i < num_blocks; i++) {
416 struct flash_sector *f = &block_array[i];
417 uint32_t end = f->offset + f->size;
418
419 /* start only on a sector boundary */
420 if (first < 0) {
421 /* scanned past the first sector? */
422 if (addr < f->offset)
423 break;
424
425 /* is this the first sector? */
426 if (addr == f->offset)
427 first = i;
428
429 /* Does this need head-padding? If so, pad and warn;
430 * or else force an error.
431 *
432 * Such padding can make trouble, since *WE* can't
433 * ever know if that data was in use. The warning
434 * should help users sort out messes later.
435 */
436 else if (addr < end && pad_reason) {
437 /* FIXME say how many bytes (e.g. 80 KB) */
438 LOG_WARNING("Adding extra %s range, "
439 "%#8.8x to %#8.8x",
440 pad_reason,
441 (unsigned) f->offset,
442 (unsigned) addr - 1);
443 first = i;
444 } else
445 continue;
446 }
447
448 /* is this (also?) the last sector? */
449 if (last_addr == end) {
450 last = i;
451 break;
452 }
453
454 /* Does this need tail-padding? If so, pad and warn;
455 * or else force an error.
456 */
457 if (last_addr < end && pad_reason) {
458 /* FIXME say how many bytes (e.g. 80 KB) */
459 LOG_WARNING("Adding extra %s range, "
460 "%#8.8x to %#8.8x",
461 pad_reason,
462 (unsigned) last_addr,
463 (unsigned) end - 1);
464 last = i;
465 break;
466 }
467
468 /* MUST finish on a sector boundary */
469 if (last_addr <= f->offset)
470 break;
471 }
472
473 /* invalid start or end address? */
474 if (first == -1 || last == -1) {
475 LOG_ERROR("address range 0x%8.8x .. 0x%8.8x "
476 "is not sector-aligned",
477 (unsigned) (c->base + addr),
478 (unsigned) (c->base + last_addr - 1));
479 return ERROR_FLASH_DST_BREAKS_ALIGNMENT;
480 }
481
482 /* The NOR driver may trim this range down, based on what
483 * sectors are already erased/unprotected. GDB currently
484 * blocks such optimizations.
485 */
486 return callback(c, first, last);
487 }
488
489 /* The inner fn only handles a single bank, we could be spanning
490 * multiple chips.
491 */
492 static int flash_iterate_address_range(struct target *target,
493 char *pad_reason, uint32_t addr, uint32_t length,
494 bool iterate_protect_blocks,
495 int (*callback)(struct flash_bank *bank, int first, int last))
496 {
497 struct flash_bank *c;
498 int retval = ERROR_OK;
499
500 /* Danger! zero-length iterations means entire bank! */
501 do {
502 retval = get_flash_bank_by_addr(target, addr, true, &c);
503 if (retval != ERROR_OK)
504 return retval;
505
506 uint32_t cur_length = length;
507 /* check whether it all fits in this bank */
508 if (addr + length - 1 > c->base + c->size - 1) {
509 LOG_DEBUG("iterating over more than one flash bank.");
510 cur_length = c->base + c->size - addr;
511 }
512 retval = flash_iterate_address_range_inner(target,
513 pad_reason, addr, cur_length,
514 iterate_protect_blocks,
515 callback);
516 if (retval != ERROR_OK)
517 break;
518
519 length -= cur_length;
520 addr += cur_length;
521 } while (length > 0);
522
523 return retval;
524 }
525
526 int flash_erase_address_range(struct target *target,
527 bool pad, uint32_t addr, uint32_t length)
528 {
529 return flash_iterate_address_range(target, pad ? "erase" : NULL,
530 addr, length, false, &flash_driver_erase);
531 }
532
533 static int flash_driver_unprotect(struct flash_bank *bank, int first, int last)
534 {
535 return flash_driver_protect(bank, 0, first, last);
536 }
537
538 int flash_unlock_address_range(struct target *target, uint32_t addr, uint32_t length)
539 {
540 /* By default, pad to sector boundaries ... the real issue here
541 * is that our (only) caller *permanently* removes protection,
542 * and doesn't restore it.
543 */
544 return flash_iterate_address_range(target, "unprotect",
545 addr, length, true, &flash_driver_unprotect);
546 }
547
548 static int compare_section(const void *a, const void *b)
549 {
550 struct imagesection *b1, *b2;
551 b1 = *((struct imagesection **)a);
552 b2 = *((struct imagesection **)b);
553
554 if (b1->base_address == b2->base_address)
555 return 0;
556 else if (b1->base_address > b2->base_address)
557 return 1;
558 else
559 return -1;
560 }
561
562 int flash_write_unlock(struct target *target, struct image *image,
563 uint32_t *written, int erase, bool unlock)
564 {
565 int retval = ERROR_OK;
566
567 int section;
568 uint32_t section_offset;
569 struct flash_bank *c;
570 int *padding;
571
572 section = 0;
573 section_offset = 0;
574
575 if (written)
576 *written = 0;
577
578 if (erase) {
579 /* assume all sectors need erasing - stops any problems
580 * when flash_write is called multiple times */
581
582 flash_set_dirty();
583 }
584
585 /* allocate padding array */
586 padding = calloc(image->num_sections, sizeof(*padding));
587
588 /* This fn requires all sections to be in ascending order of addresses,
589 * whereas an image can have sections out of order. */
590 struct imagesection **sections = malloc(sizeof(struct imagesection *) *
591 image->num_sections);
592 int i;
593 for (i = 0; i < image->num_sections; i++)
594 sections[i] = &image->sections[i];
595
596 qsort(sections, image->num_sections, sizeof(struct imagesection *),
597 compare_section);
598
599 /* loop until we reach end of the image */
600 while (section < image->num_sections) {
601 uint32_t buffer_size;
602 uint8_t *buffer;
603 int section_last;
604 uint32_t run_address = sections[section]->base_address + section_offset;
605 uint32_t run_size = sections[section]->size - section_offset;
606 int pad_bytes = 0;
607
608 if (sections[section]->size == 0) {
609 LOG_WARNING("empty section %d", section);
610 section++;
611 section_offset = 0;
612 continue;
613 }
614
615 /* find the corresponding flash bank */
616 retval = get_flash_bank_by_addr(target, run_address, false, &c);
617 if (retval != ERROR_OK)
618 goto done;
619 if (c == NULL) {
620 LOG_WARNING("no flash bank found for address %" PRIx32, run_address);
621 section++; /* and skip it */
622 section_offset = 0;
623 continue;
624 }
625
626 /* collect consecutive sections which fall into the same bank */
627 section_last = section;
628 padding[section] = 0;
629 while ((run_address + run_size - 1 < c->base + c->size - 1) &&
630 (section_last + 1 < image->num_sections)) {
631 /* sections are sorted */
632 assert(sections[section_last + 1]->base_address >= c->base);
633 if (sections[section_last + 1]->base_address >= (c->base + c->size)) {
634 /* Done with this bank */
635 break;
636 }
637
638 /* FIXME This needlessly touches sectors BETWEEN the
639 * sections it's writing. Without auto erase, it just
640 * writes ones. That WILL INVALIDATE data in cases
641 * like Stellaris Tempest chips, corrupting internal
642 * ECC codes; and at least FreeScale suggests issues
643 * with that approach (in HC11 documentation).
644 *
645 * With auto erase enabled, data in those sectors will
646 * be needlessly destroyed; and some of the limited
647 * number of flash erase cycles will be wasted...
648 *
649 * In both cases, the extra writes slow things down.
650 */
651
652 /* if we have multiple sections within our image,
653 * flash programming could fail due to alignment issues
654 * attempt to rebuild a consecutive buffer for the flash loader */
655 pad_bytes = (sections[section_last + 1]->base_address) - (run_address + run_size);
656 padding[section_last] = pad_bytes;
657 run_size += sections[++section_last]->size;
658 run_size += pad_bytes;
659
660 if (pad_bytes > 0)
661 LOG_INFO("Padding image section %d with %d bytes",
662 section_last-1,
663 pad_bytes);
664 }
665
666 if (run_address + run_size - 1 > c->base + c->size - 1) {
667 /* If we have more than one flash chip back to back, then we limit
668 * the current write operation to the current chip.
669 */
670 LOG_DEBUG("Truncate flash run size to the current flash chip.");
671
672 run_size = c->base + c->size - run_address;
673 assert(run_size > 0);
674 }
675
676 /* If we're applying any sector automagic, then pad this
677 * (maybe-combined) segment to the end of its last sector.
678 */
679 if (unlock || erase) {
680 int sector;
681 uint32_t offset_start = run_address - c->base;
682 uint32_t offset_end = offset_start + run_size;
683 uint32_t end = offset_end, delta;
684
685 for (sector = 0; sector < c->num_sectors; sector++) {
686 end = c->sectors[sector].offset
687 + c->sectors[sector].size;
688 if (offset_end <= end)
689 break;
690 }
691
692 delta = end - offset_end;
693 padding[section_last] += delta;
694 run_size += delta;
695 }
696
697 /* allocate buffer */
698 buffer = malloc(run_size);
699 if (buffer == NULL) {
700 LOG_ERROR("Out of memory for flash bank buffer");
701 retval = ERROR_FAIL;
702 goto done;
703 }
704 buffer_size = 0;
705
706 /* read sections to the buffer */
707 while (buffer_size < run_size) {
708 size_t size_read;
709
710 size_read = run_size - buffer_size;
711 if (size_read > sections[section]->size - section_offset)
712 size_read = sections[section]->size - section_offset;
713
714 /* KLUDGE!
715 *
716 * #¤%#"%¤% we have to figure out the section # from the sorted
717 * list of pointers to sections to invoke image_read_section()...
718 */
719 intptr_t diff = (intptr_t)sections[section] - (intptr_t)image->sections;
720 int t_section_num = diff / sizeof(struct imagesection);
721
722 LOG_DEBUG("image_read_section: section = %d, t_section_num = %d, "
723 "section_offset = %d, buffer_size = %d, size_read = %d",
724 (int)section, (int)t_section_num, (int)section_offset,
725 (int)buffer_size, (int)size_read);
726 retval = image_read_section(image, t_section_num, section_offset,
727 size_read, buffer + buffer_size, &size_read);
728 if (retval != ERROR_OK || size_read == 0) {
729 free(buffer);
730 goto done;
731 }
732
733 /* see if we need to pad the section */
734 while (padding[section]--)
735 (buffer + buffer_size)[size_read++] = c->default_padded_value;
736
737 buffer_size += size_read;
738 section_offset += size_read;
739
740 if (section_offset >= sections[section]->size) {
741 section++;
742 section_offset = 0;
743 }
744 }
745
746 retval = ERROR_OK;
747
748 if (unlock)
749 retval = flash_unlock_address_range(target, run_address, run_size);
750 if (retval == ERROR_OK) {
751 if (erase) {
752 /* calculate and erase sectors */
753 retval = flash_erase_address_range(target,
754 true, run_address, run_size);
755 }
756 }
757
758 if (retval == ERROR_OK) {
759 /* write flash sectors */
760 retval = flash_driver_write(c, buffer, run_address - c->base, run_size);
761 }
762
763 free(buffer);
764
765 if (retval != ERROR_OK) {
766 /* abort operation */
767 goto done;
768 }
769
770 if (written != NULL)
771 *written += run_size; /* add run size to total written counter */
772 }
773
774 done:
775 free(sections);
776 free(padding);
777
778 return retval;
779 }
780
781 int flash_write(struct target *target, struct image *image,
782 uint32_t *written, int erase)
783 {
784 return flash_write_unlock(target, image, written, erase, false);
785 }
786
787 struct flash_sector *alloc_block_array(uint32_t offset, uint32_t size, int num_blocks)
788 {
789 int i;
790
791 struct flash_sector *array = calloc(num_blocks, sizeof(struct flash_sector));
792 if (array == NULL)
793 return NULL;
794
795 for (i = 0; i < num_blocks; i++) {
796 array[i].offset = offset;
797 array[i].size = size;
798 array[i].is_erased = -1;
799 array[i].is_protected = -1;
800 offset += size;
801 }
802
803 return array;
804 }

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)