Skip to content

Do not write a refused MFM track into the space of another one (#921) - #967

Open
enver-haase wants to merge 1 commit into
GideonZ:masterfrom
enver-haase:fix/wd177x-write-track-refused
Open

enver-haase wants to merge 1 commit into
GideonZ:masterfrom
enver-haase:fix/wd177x-write-track-refused

Conversation

@enver-haase

@enver-haase enver-haase commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #921.

What was wrong

MfmDisk::UpdateTrack() assigns the file position only when it accepts a track, and returns -1 for a track or side out of range and -2 when the decoded sectors need more room than the image reserved. The WRITE TRACK completion ignored that: it wrote the sectors at offset, which on a refusal still held whatever the previous command had left there, and then had the track recorded as formatted. So a format the image could not hold went into another track's space, and the image claimed a format that was never stored.

The fix (software/drive/wd177x.cc only)

  • Completion: when UpdateTrack() refuses the track, nothing is written and the track is not recorded; the log says which track and by how much it missed. The drive cannot be told at this point, since it read the status when busy dropped (Firmware-side write failures never reach the drive CPU: the WD177x block clears BUSY 64 us after the last byte #937).
  • On arrival: where the image reserves no space for the track at all, WRITE TRACK is refused before the transfer, with lost data and busy released. That is the only moment the drive can still be told: the 1571 and the 1581 leave their byte loop when busy drops and report the command as failed, and neither reads the status after a WRITE TRACK that ran to the end. Lost data is the status the data sheet defines for this command; record not found is undefined for it.
  • Lost data is cleared per command beside record not found, as the data sheet has both reset when updated, so it cannot be read as the outcome of the next command.

Test

New E2E suite g71-mfm-format-refused (tests/e2e/drive/g71_mfm_format_refused_test.py with mfm_format_command.asm, registered in run-tests). A G71 with GCR tracks 1 to 35 and three extra cylinders on side 0 is mounted read/write on a 1571, and each cylinder is formatted from the C64 with the 1571's partial burst format command, nine 512-byte sectors (4608 bytes). Only the command travels over the bus, so a C64 is enough. The suite reads the image file back over FTP after each format.

cylinder track in the image check
36 full formats, and the image holds the nine sectors control
35 4000 bytes refused after the transfer; its track stays as it was wrong on master
37 100 bytes, less than the 162-byte MFM header refused on arrival; the drive answers, the device stays reachable, the track stays as it was wrong on master

Afterwards the suite discards the image's changes (unlink, remove), deletes it, and puts the drive back to the model and power state it had.

Red, Ultimate II+L in a C64, official 3.15a (dddd29b2), FPGA 125:

[01] the drive takes the image ... OK (4.125s)
[02] [control] cylinder 36 formats, and the image holds it ... OK (7.016s)
     cylinder 36: 00, OK,00,00
[03] [wrong on master] cylinder 35, too small for the format, stays as it was ... FAIL (its header now says 0x8fa0, 9 sectors, for a format the image could not hold, 7.019s)
     cylinder 35: 24,READ ERROR,00,00
[04] [wrong on master] cylinder 37, with no room at all, is refused and the drive answers ... FAIL (its track in the image changed: the length word is 0x8064, the first bytes aa aa aa aa aa aa aa aa, 7.069s)
     cylinder 37: 24,READ ERROR,00,00
g71_mfm_format_refused_test: FAIL (its header now says 0x8fa0, 9 sectors, for a format the image could not hold, 25.3s)

0x8fa0 is the MFM marker on a 4000-byte track, with nine sectors in its table and none of their data; 0x8064 is the marker on the 100-byte track, which now holds the refused format's fill byte.

Green, same Ultimate II+L and C64: v3.15a plus only this PR's wd177x.cc change (git_commit_hash cef333fb), FPGA 125, suite at c019a9d:

[01] the drive takes the image ... OK (6.137s)
[02] [control] cylinder 36 formats, and the image holds it ... OK (11.3s SLOW)
     cylinder 36: 00, OK,00,00
[03] [wrong on master] cylinder 35, too small for the format, stays as it was ... OK (10.6s SLOW)
     cylinder 35: 24,READ ERROR,00,00
[04] [wrong on master] cylinder 37, with no room at all, is refused and the drive answers ... OK (10.4s SLOW)
     cylinder 37: 24,READ ERROR,00,00
g71_mfm_format_refused_test: OK (4 checks, 39.6s)

Details: #967 (comment)

This branch itself builds with make u2pl_swonly. Gate checks without a device: registry_test, lint_test (ruff) and stale_gates_test pass.

What the suite cannot see

Where master writes the refused sectors at the stale position into another track, that lands in the drive's copy of the image (gcr_ram_file) and reaches the file only when the image is saved. A C64 cannot read MFM sectors back, as that takes burst transfers, which need a C128. A drive-side reader (M-W/M-E into the 1571, M-R over the slow bus) could close that gap; it is not part of this PR.

Not tested: the 1581 with a D81. There the space per track is fixed, and a format with more sectors than a track holds would take the same completion path.

MfmDisk::UpdateTrack() assigns the file position only when it accepts the
track, and returns -1 for a track or side out of range and -2 when the decoded
sectors need more room than the image reserved. WRITE TRACK ignored that return
value and wrote newTrack.actualDataSize bytes at 'offset' regardless, and on a
refusal 'offset' still held whatever the command before had left there. So a
format the image could not hold overwrote a different track's data. The format
is lost either way; the data that was there need not be.

The completion now writes nothing when the track was refused, and says which
track it was and by how much it missed.

Where the refusal is knowable before the transfer, WRITE TRACK is refused when
it arrives: a track the image reserved no space for can never be stored,
whatever the drive sends. That is also the only moment at which the drive can
be told, since both the 1571 and the 1581 leave their byte loop when busy drops
and report the command as failed, while neither reads the status after a WRITE
TRACK that ran to the end. Lost data is the status the data sheet defines for
this command; record not found, which this file uses on the read paths, is
undefined for it.

Lost data is now cleared per command beside record not found, as the data sheet
has both reset when updated, so the bit cannot be read as the outcome of the
command after the one that set it.

New E2E suite g71-mfm-format-refused: a G71 whose cylinder 35 is 4000 bytes,
cylinder 36 a full track and cylinder 37 100 bytes, each formatted from the
C64 with the 1571's partial burst format, nine 512-byte sectors. Cylinder 36
is the control and has to show up in the image file. Cylinders 35 and 37
have to stay as they were, and the drive has to answer after the refusal on
arrival. On 3.15a cylinder 35's header says nine MFM sectors whose data was
never stored, and cylinder 37 is marked MFM and holds the refused fill
byte. The write at the stale position into another track goes to the
drive's copy of the image, which a C64 cannot read back, so the suite does
not see that part.

Refs GideonZ#921.
@enver-haase

Copy link
Copy Markdown
Contributor Author

Green, same Ultimate II+L and C64: v3.15a plus only this PR's wd177x.cc change (git_commit_hash cef333fb), FPGA 125, suite at c019a9d:

[01] the drive takes the image ... OK (6.137s)
[02] [control] cylinder 36 formats, and the image holds it ... OK (11.3s SLOW)
     cylinder 36: 00, OK,00,00
[03] [wrong on master] cylinder 35, too small for the format, stays as it was ... OK (10.6s SLOW)
     cylinder 35: 24,READ ERROR,00,00
[04] [wrong on master] cylinder 37, with no room at all, is refused and the drive answers ... OK (10.4s SLOW)
     cylinder 37: 24,READ ERROR,00,00
g71_mfm_format_refused_test: OK (4 checks, 39.6s)

On cylinder 37, the 1571 takes lost data on arrival as 24,READ ERROR, the same answer it gave on master after the failed verify. It keeps serving the bus, and the device stays reachable. So for the 1571, lost data on WRITE TRACK is safe to report. On the 1581, lost data after a write sector sent the DOS into recov and hung the bus (#938). That is a different command and a different path; this PR does not touch it.

@chrisgleissner chrisgleissner added bug Existing behavior is incorrect, broken, or regressed; includes supported documentation defects. storage Disk/tape emulation, images, mounting, copying, filenames, filesystems, and storage media. labels Oct 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Existing behavior is incorrect, broken, or regressed; includes supported documentation defects. storage Disk/tape emulation, images, mounting, copying, filenames, filesystems, and storage media.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

WRITE TRACK ignores UpdateTrack()'s rejection and writes at a stale offset

2 participants