Skip to content

Commit 774ec26

Browse files
committed
Verify flash contents after programming
Add an option, on by default, to read out flash contents after programming and compare it to the programmed data. Without it, bugs in flash loader algos and other problems will silently leave flash corrupted.
1 parent 5166025 commit 774ec26

File tree

5 files changed

+47
-18
lines changed

5 files changed

+47
-18
lines changed

pyocd/core/options.py

+2
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,8 @@ class OptionInfo(NamedTuple):
146146
"Name of test firmware binary."),
147147
OptionInfo('user_script', str, None,
148148
"Path of the user script file."),
149+
OptionInfo('verify', bool, True,
150+
"Controls whether a verify pass should be performed after flash programming has finished. Default is True"),
149151
OptionInfo('warning.cortex_m_default', bool, True,
150152
"Whether to show the warning about use of the cortex_m target type. Default is True."),
151153

pyocd/flash/builder.py

+27-13
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,11 @@ def mark_all_pages_not_same(self):
132132
for page in self.page_list:
133133
page.same = False
134134

135+
def mark_all_pages_unknown(self):
136+
"""@brief Sets the same flag to False for all pages in this sector."""
137+
for page in self.page_list:
138+
page.same = None
139+
135140
def __repr__(self):
136141
return "<_FlashSector@%x addr=%x size=%x wgt=%g pages=%s, subsectors=%d>" % (
137142
id(self), self.addr, self.size, self.erase_weight, self.page_list, self.n_subsectors)
@@ -417,7 +422,7 @@ def add_page_with_existing_data():
417422
page = add_page_with_existing_data()
418423
sector_page_addr += page.size
419424

420-
def program(self, chip_erase=None, progress_cb=None, smart_flash=True, fast_verify=False, keep_unwritten=True, no_reset=False):
425+
def program(self, chip_erase=None, progress_cb=None, smart_flash=True, fast_verify=False, keep_unwritten=True, no_reset=False, verify=True):
421426
"""@brief Determine fastest method of flashing and then run flash programming.
422427
423428
Data must have already been added with add_data().
@@ -444,6 +449,8 @@ def program(self, chip_erase=None, progress_cb=None, smart_flash=True, fast_veri
444449
be read from memory and restored while programming.
445450
@param no_reset Boolean indicating whether if the device should not be reset after the
446451
programming process has finished.
452+
@param verify Boolean indicating whether a verify pass should be performed after the
453+
programming process has finished.
447454
"""
448455

449456
# Send notification that we're about to program flash.
@@ -535,12 +542,6 @@ def program(self, chip_erase=None, progress_cb=None, smart_flash=True, fast_veri
535542
else:
536543
flash_operation = self._sector_erase_program(progress_cb)
537544

538-
# Cleanup flash algo and reset target after programming.
539-
self.flash.cleanup()
540-
541-
if no_reset is not True:
542-
self.flash.target.reset_and_halt()
543-
544545
program_finish = time()
545546
self.perf.program_time = program_finish - program_start
546547
self.perf.program_type = flash_operation
@@ -571,6 +572,23 @@ def program(self, chip_erase=None, progress_cb=None, smart_flash=True, fast_veri
571572
self.perf.skipped_byte_count = skipped_byte_count
572573
self.perf.skipped_page_count = skipped_page_count
573574

575+
if verify:
576+
LOG.info("Verifying")
577+
# Reset same flag for all pages to enable rescanning
578+
for sector in self.sector_list:
579+
sector.mark_all_pages_unknown()
580+
self._scan_pages_for_same(progress_cb)
581+
failed_pages = [page for page in self.page_list if page.same is False]
582+
if failed_pages:
583+
LOG.debug("Verify failed for pages {}".format(failed_pages))
584+
raise FlashProgramFailure('flash verify failure', address=failed_pages[0].addr)
585+
586+
# Cleanup flash algo and reset target after programming.
587+
self.flash.cleanup()
588+
589+
if no_reset is not True:
590+
self.flash.target.reset_and_halt()
591+
574592
if self.log_performance:
575593
if chip_erase:
576594
LOG.info("Erased chip, programmed %d bytes (%s), skipped %d bytes (%s) at %.02f kB/s",
@@ -899,12 +917,6 @@ def _scan_pages_for_same(self, progress_cb=_stub_progress):
899917
if self.sector_erase_weight > 0:
900918
progress_cb(float(progress) / float(self.sector_erase_weight))
901919

902-
# If we have to program any pages of a sector, then mark all pages of that sector
903-
# as needing to be programmed, since the sector will be erased.
904-
for sector in self.sector_list:
905-
if sector.are_any_pages_not_same():
906-
sector.mark_all_pages_not_same()
907-
908920
return progress
909921

910922
def _next_nonsame_page(self, i):
@@ -937,6 +949,8 @@ def _sector_erase_program_double_buffer(self, progress_cb=_stub_progress):
937949
self.flash.init(self.flash.Operation.ERASE)
938950
for sector in self.sector_list:
939951
if sector.are_any_pages_not_same():
952+
# If the sector is erased, all its pages must be programmed
953+
sector.mark_all_pages_not_same()
940954
# Erase the sector
941955
for addr in sector.addrs:
942956
self.flash.erase_sector(addr)

pyocd/flash/file_programmer.py

+7-2
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ def __init__(self,
6363
smart_flash: Optional[bool] = None,
6464
trust_crc: Optional[bool] = None,
6565
keep_unwritten: Optional[bool] = None,
66-
no_reset: Optional[bool] = None
66+
no_reset: Optional[bool] = None,
67+
verify: Optional[bool] = None
6768
):
6869
"""@brief Constructor.
6970
@@ -86,13 +87,16 @@ def __init__(self,
8687
be read from memory and restored while programming.
8788
@param no_reset Boolean indicating whether if the device should not be reset after the
8889
programming process has finished.
90+
@param verify Boolean indicating whether a verify pass should be performed after the
91+
programming process has finished.
8992
"""
9093
self._session = session
9194
self._chip_erase = chip_erase
9295
self._smart_flash = smart_flash
9396
self._trust_crc = trust_crc
9497
self._keep_unwritten = keep_unwritten
9598
self._no_reset = no_reset
99+
self._verify = verify
96100
self._progress = progress
97101
self._loader = None
98102

@@ -152,7 +156,8 @@ def program(self, file_or_path: Union[str, IO[bytes]], file_format: Optional[str
152156
smart_flash=self._smart_flash,
153157
trust_crc=self._trust_crc,
154158
keep_unwritten=self._keep_unwritten,
155-
no_reset=self._no_reset)
159+
no_reset=self._no_reset,
160+
verify=self._verify)
156161

157162
# file_obj = None
158163
# Open the file if a path was provided.

pyocd/flash/loader.py

+9-2
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ class MemoryLoader:
143143
_trust_crc: Optional[bool]
144144
_keep_unwritten: Optional[bool]
145145
_no_reset: Optional[bool]
146+
_verify: Optional[bool]
146147

147148
def __init__(self,
148149
session: "Session",
@@ -151,7 +152,8 @@ def __init__(self,
151152
smart_flash: Optional[bool] = None,
152153
trust_crc: Optional[bool] = None,
153154
keep_unwritten: Optional[bool] = None,
154-
no_reset: Optional[bool] = None
155+
no_reset: Optional[bool] = None,
156+
verify: Optional[bool] = None
155157
):
156158
"""@brief Constructor.
157159
@@ -174,6 +176,8 @@ def __init__(self,
174176
be read from memory and restored while programming.
175177
@param no_reset Boolean indicating whether if the device should not be reset after the
176178
programming process has finished.
179+
@param verify Boolean indicating whether a verify pass should be performed after the
180+
programming process has finished.
177181
"""
178182
self._session = session
179183
assert session.board
@@ -198,6 +202,8 @@ def __init__(self,
198202
else self._session.options.get('keep_unwritten')
199203
self._no_reset = no_reset if (no_reset is not None) \
200204
else self._session.options.get('no_reset')
205+
self._verify = verify if (verify is not None) \
206+
else self._session.options.get('verify')
201207

202208
self._reset_state()
203209

@@ -296,7 +302,8 @@ def commit(self):
296302
smart_flash=self._smart_flash,
297303
fast_verify=self._trust_crc,
298304
keep_unwritten=self._keep_unwritten,
299-
no_reset=self._no_reset)
305+
no_reset=self._no_reset,
306+
verify=self._verify)
300307
perfList.append(perf)
301308
didChipErase = True
302309

pyocd/subcommands/load_cmd.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,8 @@ def invoke(self) -> int:
101101
programmer = FileProgrammer(session,
102102
chip_erase=self._args.erase,
103103
trust_crc=self._args.trust_crc,
104-
no_reset=self._args.no_reset)
104+
no_reset=self._args.no_reset,
105+
verify=self._args.verify)
105106
for filename in self._args.file:
106107
# Get an initial path with the argument as-is.
107108
file_path = Path(filename).expanduser()

0 commit comments

Comments
 (0)