Skip to content

fix: write refused four times naming JEDEC ids that do not exist - #19

Merged
jackulau merged 3 commits into
mainfrom
write-waits-out-garbage-ids
Jul 17, 2026
Merged

jackulau merged 3 commits into
mainfrom
write-waits-out-garbage-ids

Conversation

@jackulau

Copy link
Copy Markdown
Owner

What happened

A marginal probe answered 1fffff, then 00bfff, then nothing. write refused four times running, twice with chip not detected and twice with unknown chip capacity for JEDEC id <garbage>, refusing to write blind.

That is a wrong diagnosis, not just a vague one. It points at the chip and sends the user to look up a part that does not exist, when the fault is mechanical.

Why it was mine

#18 taught read to wait for an id it can SIZE. I deliberately gave write a single look, reasoning that a genuinely unlisted chip deserves an immediate honest refusal instead of burning the patience budget. The reasoning was sound; the weighting was wrong. Garbage from a bad contact window is far more common than an unlisted chip, and the hardware demonstrated it within hours.

The fix

write uses identify_chip_patient like read. The two cases are separated by the discriminator already documented for the bus, just never applied to the id itself: a real part repeats, a bad contact varies.

  • Several DIFFERENT unsizable ids -> a marginal probe. Says so, names the garbage, points at reseating.
  • ONE id seen consistently -> a real answer. Keeps the specific unknown-capacity refusal.
  • Nothing at all -> chip not detected.

The fake was too kind

It modelled garbage as a single fixed wrong id, which is indistinguishable from an unlisted chip, so it could not express the difference this fix turns on. It now cycles ids so successive polls differ. Same lesson as resetting four_byte on recovery: a fake kinder than physics hides the bugs physics causes.

Verification

  • 513 tests, clippy -D warnings, fmt: all green by exit code.
  • Mutation: reverting to the single look reproduces the field error string exactly, in 0.01s. Removing the varying-id diagnostic turns its test red too.
  • Nothing was written to the user's chip at any point; the failure was at startup identify.

jackulau added 3 commits July 17, 2026 01:03
A marginal probe answered 1fffff, then 00bfff, then nothing, and write refused
each time with "unknown chip capacity for JEDEC id 1fffff -- refusing to write
blind". That is a wrong diagnosis, not merely a vague one: it points at the chip
and sends the user to look up a part that does not exist, when the fault is
mechanical and the fix is to reseat the probe.

I introduced this hours earlier. read waits for an id it can size; I gave write a
single look on the theory that a genuinely unlisted chip deserves an immediate
honest refusal rather than burning the whole patience budget. The reasoning was
sound and the weighting was wrong: garbage shifted in through a bad contact window
is far more common than an unlisted chip, and real hardware demonstrated it within
hours.

write now uses identify_chip_patient like read. The two cases are separated by the
discriminator already used for the bus itself, just never applied to the id: a real
part repeats, a bad contact varies. Several DIFFERENT unsizable ids is a marginal
probe and now says so, naming the garbage it saw. ONE id seen consistently is a
real answer and keeps the specific unknown-capacity refusal.

The fake modelled garbage as a single fixed wrong id, which is indistinguishable
from an unlisted chip, so it could not express the difference the fix turns on. It
now cycles ids so successive polls differ. Same lesson as four_byte on recovery: a
fake kinder than physics hides the bugs physics causes.

Verified by mutation: the old single-look reproduces the field error string exactly
("unknown chip capacity for JEDEC id 1fffff"), in 0.01s.
…robe never gets past it

write's mandatory pre-write backup used a timestamped path, so every attempt began
the 32 MB read at offset zero and orphaned the previous run's resume sidecar. On a
probe that cannot hold the whole read in one process lifetime, that makes the write
unreachable no matter how many times it is run. A cliff, not a slope: the same shape
as chunk-size versus contact window.

It also made the read path's "Ctrl-C is safe and re-running resumes from here",
printed from inside a write's backup phase, a false promise. In the field a backup
read reached 23% and was interrupted; the next run would have started over, and the
message said otherwise.

The path is now keyed on the CHIP (jedec id + size) rather than on the clock, so the
existing resume machinery applies. Keying on identity is what makes reuse safe: a
sidecar is only ever honoured for a dump of the same chip.

Resuming across a partially-completed write is sound. The baseline decides only
whether a block is SKIPPED, and a block is skipped only when baseline == image. Any
block read before an earlier write ran holds old content, which differs from the
image, so it is re-erased and re-programmed rather than skipped. A stale baseline can
cost time; it cannot skip a block that still needs writing.

Verified by mutation: restoring the timestamped path turns the new test red on the
two-runs-one-path assertion.
Asked whether a dropout mid-write is survivable, I went to add a wait_for_contact
to the block retry loop: it burns all 3 attempts in 60ms against a bus that is not
there, which looked like the read-vs-write asymmetry all over again.

Mutation said otherwise. Deleting the new wait again left every test green, so it
bought nothing, so it is reverted. The property is already provided somewhere less
obvious: the block's verify readback goes through read_range -> read_chunk_patient,
which waits for contact, so a failed attempt ALREADY blocks until the probe returns
and the retry lands.

Keeping the test, which pins the property whoever provides it, and a comment at the
retry loop so the next person does not re-add the redundant wait. Also records why
waiting there is safe at all: losing contact is losing power, so nothing is
mid-erase; the retry re-erases before re-programming, so an attempt is idempotent;
and the dedicated 4-byte opcodes cannot be misframed by a chip that came back in its
3-byte power-on default, which is the trap that made waiting READS land at addr>>8.
@jackulau
jackulau merged commit a2ba85f into main Jul 17, 2026
1 check passed
@jackulau
jackulau deleted the write-waits-out-garbage-ids branch July 17, 2026 08:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant