Commodore 1571 Laufwerk mit xum1541 (Teensy2) beim Auslesen einer 5,25-Zoll-Diskette unter Linux

In meinem Keller stehen Kisten voller alter 5,25-Zoll-Disketten. Commodore-Software aus den späten 80ern, Spiele, Tools, selbstgeschriebener Kram. Das Problem mit Floppy-Disks: die Magnetisierung lässt mit der Zeit nach. Alle paar Jahre sollte man die Daten einmal komplett lesen und zurückschreiben, sonst wird das Medium irgendwann unlesbar. Mein letztes Auffrischen ist eine Weile her, also war es mal wieder Zeit. Zugegeben, ich arbeite nur noch extrem selten mit meinem Commodore; aber wenn ich es mache, ist es immer eine kleine Zeitreise voller Nostalgie für mich. Die Geräusche, der Geruch, die Wartezeit. Hin und wieder tut es mir einfach gut, noch mal die alten Spiele zu spielen, mit den Tools zu arbeiten oder auch meine ersten eigenen Programme noch mal zu starten, in den Code zu schauen *kopfschütteln*…. Ich habe sogar noch alte Hausaufgaben auf Disketten 😀

Stapel aus Commodore 1571 Diskettenlaufwerken mit 5,25-Zoll-Disketten und dem 1570/1571 Handbuch

Was als routinemäßige Sicherungsaktion geplant war, wurde dann allerdings eine mehrtägige Debugging-Session. Inklusive eines Firmware-Bugs, der seit sechs Jahren im Code schlummerte.

Die Ausgangslage

Mein Setup: Zwei Commodore 1571 Laufwerke, angeschlossen über einen xum1541 USB-Adapter an einem Linux-Rechner. Der xum1541 sitzt auf einem TEENSY2-Board (ATmega32U4) und spricht über USB mit OpenCBM, einer Open-Source-Treibersammlung für Commodore-Laufwerke. Die Software-Seite hatte ich in einer vorherigen Session bereits verifiziert. Beide Laufwerke laufen bei fast perfekten 300,5 RPM, lesen und schreiben fehlerfrei auf beiden Seiten und liefern sogar bit-identische Images im Crosscheck.

Der Plan war simpel: Alle Disketten systematisch als 1:1-Image sichern, die physischen Medien durch Zurückschreiben auffrischen und nebenbei prüfen, ob die Inhalte bereits in Online-Archiven wie CSDB, Gamebase64 oder dem Internet Archive vorhanden sind.

Der xum1541 im gelben Gehäuse. Links das USB-Kabel zum Linux-Rechner, rechts das IEC-Kabel zur 1571:

xum1541 USB-Adapter im gelben 3D-gedruckten Gehäuse mit USB- und IEC-Kabel angeschlossen

Und ein Blick auf die Platine mit dem Teensy2-Board (ATmega32U4), in dem der Firmware-Bug steckte:

Geöffneter xum1541-Adapter mit Teensy2-Board (ATmega32U4) auf der Platine, USB-Anschluss und IEC-DIN-Buchse

Disk 001: Pirates! und das Kopierschutz-Problem

Die erste Disk war eine Pirates!-Kopie (Microprose, 1987), „imported by The Critters Inc 1221“. Mit d64copy ausgelesen, 683 Blocks, keine Fehler. Dann zurückgeschrieben und zur Verifikation nochmal gelesen. Checksummen stimmten nicht überein.

Die Analyse ergab: Das Original-D64 enthält drei Sektoren mit Error Code 5 (Data Block Checksum Error) auf Track 23 und 24. Das ist klassischer C64-Kopierschutz. Microprose nutzte absichtliche Lesefehler, um Raubkopien zu erkennen. Das Spiel prüft beim Start ob diese Fehler vorhanden sind, fehlen sie, verweigert es den Dienst.

d64copy arbeitet auf Sektor-Ebene und kann solche GCR-Level-Fehler nicht reproduzieren. Die Error-Info wird zwar im D64 gespeichert (Emulatoren wie VICE werten sie aus), aber auf der physischen Disk gehen die absichtlichen Fehler beim Zurückschreiben verloren. Für eine echte 1:1-Kopie inklusive Kopierschutz braucht man nibtools. Das arbeitet direkt auf GCR/Nibble-Ebene und liest die Rohdaten vom Laufwerk, inklusive aller Timing-Patterns und absichtlichen Fehler.

nibtools bauen und der erste Crash

nibtools liegt als Quellcode im OpenCBM-Quellbaum, muss aber separat gebaut werden. Ich habe den markusC64-v637 Branch von GitHub genommen. Build mit dem cc65 Cross-Compiler für den 6502-Floppy-Code und gcc für die Host-Tools ging glatt. Erster Versuch:

nibread -D8 image.nib

Ergebnis: LIBUSB_ERROR_PIPE. Der USB-Transfer bricht sofort ab, nachdem der Drive-Code hochgeladen ist. nibtools erkennt die 1571 korrekt und versucht SRQ-Burst-Transfers zu nutzen, ein schnelles serielles Protokoll das die CIA-UART der 1571 verwendet. Aber der Transfer scheitert schon beim Communication-Test.

Drei Schichten tief: der Firmware-Bug

Schicht 1: Bekannter SRQ-Bug in Firmware v7. Die xum1541-Firmware v7 hatte einen dokumentierten Bug: IEC_SRQ war als 0x80 definiert (Bit 7), aber die Lookup-Tabelle iec2hw_table hatte nur 16 Einträge (4 Bits). SRQ-Operationen griffen ins Leere. In v8 gefixt: IEC_SRQ auf 0x10 geändert, Tabelle auf 32 Einträge erweitert. Commit 3ef4fc0d von Spiro Trikaliotis, 2021.

Problem: Es gab kein fertiges v8-Hex für das TEENSY2-Board. Nur für ZOOMFLOPPY und PROMICRO. Die Boards haben unterschiedliche Mikrocontroller-Pin-Belegungen, also kann man die Firmware nicht einfach zwischen Boards tauschen. Lösung: AVR-Toolchain installiert (gcc-avr, avr-libc), v8 für TEENSY2 selbst gebaut und über den HalfKay-Bootloader geflasht.

teensy_loader_cli --mcu=atmega32u4 -w -v firmware.hex

Risikofrei, da der Bootloader in einem geschützten Flash-Bereich sitzt. Knopf am Teensy drücken, flashen, fertig.

Schicht 2: v8 geflasht, gleiches Problem. Auch mit v8 kommt LIBUSB_ERROR_PIPE. Die OpenCBM-Library musste ebenfalls neu gebaut werden, sie prüft die Firmware-Version und war noch auf v7 kompiliert. Neu gebaut, installiert. Immer noch Crash.

Schicht 3: Der eigentliche Bug. Also tiefer in den Quellcode. In board-teensy2.h, Funktion iec_srq_write():

// BUGGY — seit Commit 57bb6726 (April 2020)
PORTD = (((data >> 4) & IO_DATA) ^ IO_DATA) | IO_SRQ;

Das wurde 1:1 von der ZoomFloppy-Implementierung kopiert. Aber die Pin-Belegung ist bei den Boards unterschiedlich:

  • ZoomFloppy hat IO_DATA auf Port D, Bit 4 (PD4)
  • TEENSY2 hat IO_DATA auf Port F, Bit 0 (PF0)

Der Code schrieb auf den komplett falschen Port mit dem falschen Bit-Shift. Das erklärt warum der Communication-Test sofort fehlschlug. Die SRQ-Daten kamen nie auf den richtigen Pins an.

Der Fix, analog zur korrekten PROMICRO-Implementierung:

// FIXED
uint8_t port_base_data = (DDRF & IO_ATN) | IO_SRQ;
// ...
DDRF = (((data >> 7) & IO_DATA) ^ IO_DATA) | port_base_data;

Die Änderungen im Detail: PORTD wird zu DDRF (richtiger Port für die IEC-Leitungen beim TEENSY2), der Shift von data >> 4 auf data >> 7 (Bit 7 des Datenbytes auf Bit 0 des Ports, wo IO_DATA liegt), und der ATN-Zustand wird über port_base_data erhalten, was vorher nicht berücksichtigt war. Timing von 0,935 µs auf 0,80 µs angepasst, passend zur PROMICRO-Implementierung.

Firmware neu gebaut (8260 Bytes, 32 mehr als vorher), geflasht. nibread läuft sofort fehlerfrei durch alle 41 Tracks. Der Bug steckte seit April 2020 im Code. Jeder mit einem TEENSY2-basierten xum1541 hatte dieses Problem bei SRQ-Transfers.

Den Fix habe ich als Pull Request eingereicht: PR #143 bei OpenCBM.

Die bittere Lektion mit Disk 001

Die erste Pirates!-Disk, der Import von The Critters Inc, war zu diesem Zeitpunkt bereits zerstört. Ich hatte sie vor dem Fix mit d64copy zurückgeschrieben. Das hat die GCR-Level-Kopierschutzmuster überschrieben. Das anschließend mit nibread erstellte NIB-Image zeigt nur noch die „reparierten“ Sektoren, nicht den originalen Kopierschutz. Disk und Images entsorgt.

Die Lektion ist simpel und ärgerlich zugleich: Nie zurückschreiben, bevor das NIB-Image existiert.

Der Workflow ab Disk 002

Ab der zweiten Disk lief der Prozess dann sauber:

  1. nibread -D8 — GCR-Rohdaten lesen, inklusive Kopierschutz
  2. d64copy — Sektor-Level-Image für Emulatoren
  3. nibwrite — 1:1 zurückschreiben aus dem NIB (erhält Kopierschutz)
  4. d64copy nochmal lesen + MD5-Vergleich zur Verifikation

Ergebnisse

DiskInhaltStatus
001Pirates! (The Critters Inc)Verloren — Kopierschutz vor dem Fix zerstört
002Ace of Aces, Light Force, Hacker + TrainersGesichert + aufgefrischt, Killer Track auf Track 1 erhalten
003Commando, The Last V8, Robin of the WoodVerloren — Medium physisch zu schwer beschädigt
004Warplay, Worldcup 86, Hyper OlympicsGesichert, Original-Disk defekt (T18/S15)
005Pirates! (The Red Lions Import, 2-Disk)Gesichert + aufgefrischt + verifiziert

Technische Eckdaten

Für die Leute die es genau wissen wollen:

  • Hardware: 2× Commodore 1571, xum1541 (TEENSY2, ATmega32U4), Linux-Host
  • Software: OpenCBM (from source), nibtools (markusC64-v637), VICE 3.7.1
  • RPM: Konstant 300,5 RPM (Nominal: 300). Die 1571 ist elektronisch geregelt, kein Trimmpoti
  • Kopierschutz-Typen: Error Code 5 (Checksum Errors) auf Pirates!, Killer Track (Track komplett mit Sync-Bytes) auf der Crocodile Soft Compilation
  • NIB-Format: ~336 KB pro Disk (41 Tracks × 8192 Bytes GCR-Rohdaten)
  • D64-Format: ~175 KB pro Disk (683 Sektoren × 256 Bytes + optional 683 Bytes Error-Info)

Fazit

Was als „schnell mal Disketten auffrischen“ geplant war, endete in einer Reise durch sechs Jahre alten Firmware-Code. Der Bug in iec_srq_write() war ein klassischer Copy-Paste-Fehler, der nie aufgefallen ist, weil kaum jemand nibtools mit einem TEENSY2-Board benutzt. SRQ-Burst-Transfers braucht man nur für nibtools, und die meisten Leute nutzen ohnehin einen ZoomFloppy-Adapter, wo der Code korrekt ist.

Der Verlust von Disk 001 ärgert mich immer noch. Aber die Lektion sitzt: Erst NIB, dann zurückschreiben. Und wenn ein Tool beim ersten Mal nicht funktioniert, lohnt es sich manchmal die Firmware aufzuschrauben, statt das Tool wegzulegen.

Der Blick ins Regal, wo das alles steht. Ja, es ist voll da unten:

Retro-Computing-Regal mit Commodore-Monitor, 1571 Laufwerk, C128 Tastatur, Diskettenstapeln und dem gelben xum1541-Adapter
Zweite Perspektive des Retro-Regals mit Commodore 1541-II, Disketten- und Kassettensammlung

Siehe auch: Commodore – PC Projekt (mein erster Versuch mit cbm4linux und einem XM1541-Kabel, anno 2009) und VC-64 Turbo Tape (1986).

Fragen? Einfach melden.