Skip to content

fast_program=True causes silent data loss on targets with sector_size > page_size #1983

Description

@awto-au

pyOCD fast_program=True sector/page data-loss issue

Title

fast_program=True causes silent data loss on targets with sector_size > page_size (erased sectors only partially re-programmed)

Environment

pyocd 0.44.1, also present at current git HEAD; Linux x86-64; STLINK-V3; STM32F427 with target override stm32f429xi.

Steps

  1. Flash image A.
  2. Modify one 4 KB page within a 16 KB sector.
  3. Program with -O smart_flash=true -O fast_program=true -O chip_erase=sector.

Expected

Dirty sectors are erased and fully re-programmed; unchanged sectors are skipped.

Actual

Dirty sectors are erased, but only the changed 4 KB page is programmed. The other 12 KB of each dirty 16 KB sector reads back as 0xFF.

Observed log line:

Erased 32768 bytes (2 sectors), programmed 8192 bytes (2 pages), skipped 57344 bytes (14 pages)

The data loss was independently confirmed by read-back with st-flash.

Cause

With assume_estimate_correct=True, _analyze_pages_with_crc32() resolves matching pages to same=True. Then _finalize_smart_flash() early-returns because no pages are left with same is None, and _scan_pages_for_same(), the only place that calls sector.mark_all_pages_not_same() for dirty sectors, never runs.

The sector-erase programmer then erases sectors with any not-same page but programs only the not-same pages. This affects any region where sector_size > page_size and a dirty sector contains a CRC-matching page. This is not the documented CRC-collision caveat; the digests were correct.

Suggested Fix

Run the mark-all-pages-in-dirty-sectors loop unconditionally in _finalize_smart_flash() after analysis.

def _finalize_smart_flash(self, progress_cb, fast_verify: bool) -> None:
    if any(page.same is None for page in self.page_list):
        self._compute_sector_erase_pages_and_weight(fast_verify)
        if any(page.same is None for page in self.page_list):
            self._scan_pages_for_same(progress_cb)

    for sector in self.sector_list:
        if sector.are_any_pages_not_same():
            sector.mark_all_pages_not_same()

The duplicate marking inside _scan_pages_for_same() is idempotent and can be left or removed.

Notes

  • Verified on real STM32F427 silicon with STLINK-V3 serial 004D00373033510135393935.
  • Default pyOCD mode, fast_program=False, passes the same incremental flash proof.
  • Trigger conditions: fast_program=True; sector_size > page_size; partial-sector dirty data where at least one page in an erased sector matches the existing target contents.
  • Re-run an upstream issue search immediately before filing; the original search found no existing report.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions