amiga-bootcamp/10_devices/trackdisk.md
Ilia Sharin da9e7d3b63 Phase 1: enrich 07_dos and 10_devices (highest FPGA priority)
07_dos:
- file_io.md: 108→240+ lines — buffered I/O (FRead/FWrite/SetVBuf),
  access mode comparison, FileHandle struct with offsets, standard
  handles, Printf %ld warning, FileInfoBlock, practical patterns
  (copy file, get size, load to RAM), error code table
- filesystem.md: 91→270+ lines — full disk geometry (ADF/HDF),
  all 8 DOS\x filesystem IDs, root block byte-level layout, file
  header layout with reverse-order pointer quirk, OFS vs FFS data
  blocks with efficiency numbers, bitmap blocks, extension blocks,
  checksum algorithm, Python ADF reader
- locks_examine.md: 113→270+ lines — lock semantics diagram, FileLock
  struct with handler discovery, ExAll bulk scan, practical patterns
  (atomic write, path resolution, volume info), 4 antipatterns
  (leaked locks, exclusive too long, unchecked IoErr, DupLock),
  pattern matching

10_devices:
- audio.md: 73→240+ lines — hardware architecture diagram, channel
  registers with offsets, period/frequency table, priority allocation,
  double-buffering, audio interrupts, AM/PM modulation, direct HW
- timer.md: 80→230+ lines — CIA timer hardware, all 5 units with
  decision flowchart, non-blocking delays, signal-based waiting,
  time arithmetic, ReadEClock, periodic game loop pattern, pitfalls
- trackdisk.md: 82→210+ lines — MFM encoding, track format, disk
  geometry, read/write/motor, change notification, track caching,
  direct hardware access, FPGA timing implications
- keyboard.md: 58→220+ lines — CIA-A serial handshake protocol with
  sequence diagram, bit rotation quirk, complete key code map,
  key matrix bitmap, reset sequence, FPGA notes
2026-04-23 20:23:50 -04:00

5.4 KiB
Raw Blame History

← Home · Devices

trackdisk.device — Floppy Disk DMA Controller

Overview

trackdisk.device interfaces with the Amiga's floppy disk controller — a custom DMA engine that reads and writes raw MFM-encoded data from double-density 3.5" disks. It provides block-level access (512 bytes/sector, 11 sectors/track, 80 tracks × 2 sides = 1,760 sectors = 880 KB per disk).


Hardware Architecture

graph LR
    subgraph "Custom Chips"
        DSKBYTR["DSKBYTR<br/>$DFF01A<br/>Disk Data Byte"]
        DSKLEN["DSKLEN<br/>$DFF024<br/>DMA Length"]
        DSKPT["DSKPT<br/>$DFF020<br/>DMA Pointer"]
    end

    subgraph "CIA-B"
        CIAPRB["PRA/PRB<br/>$BFD100<br/>Motor, Side, Step"]
    end

    DISK["3.5 DD Disk"] -->|"MFM bitstream"| DSKBYTR
    CIAPRB -->|"Motor/Step/Side"| DISK
    DSKPT -->|"DMA to/from"| CHIPRAM["Chip RAM Buffer<br/>(~13 KB/track)"]

Disk Geometry

Parameter Value
Tracks 80 (079)
Sides 2 (0=upper, 1=lower)
Sectors per track 11 (DD), 22 (HD)
Bytes per sector 512
Total capacity 880 KB (DD), 1,760 KB (HD)
Rotation speed 300 RPM (1 revolution = 200 ms)
Transfer rate ~250 kbit/s (DD raw MFM)
Track-to-track seek ~3 ms

MFM Encoding

The disk stores data in Modified Frequency Modulation format. Each byte becomes 16 bits on disk (clock + data interleaved):

Data bit:  1   0   1   1   0   0   0   1
MFM:      01  10  01  01  10  10  10  01
           ↑   ↑   ↑   ↑   ↑   ↑   ↑   ↑
           c/d pairs (clock bit inserted before each data bit)

A raw track is ~12,668 bytes of MFM data (including gaps, sync words, and sector headers).

Track Format (AmigaDOS)

Track = 11 sectors, each containing:

  Sync:     $4489 $4489         (2 words — MFM-encoded $A1 $A1)
  Header:   format, track, sector, sectors_to_gap (MFM-encoded)
  Header checksum: XOR of header longs
  Data:     512 bytes of payload (MFM-encoded = 1024 bytes on disk)
  Data checksum: XOR of data longs

Gaps between sectors: variable-length padding

Using trackdisk.device

Opening

struct MsgPort *diskPort = CreateMsgPort();
struct IOExtTD *diskReq = (struct IOExtTD *)
    CreateIORequest(diskPort, sizeof(struct IOExtTD));

/* Unit numbers: DF0:=0, DF1:=1, DF2:=2, DF3:=3 */
BYTE err = OpenDevice("trackdisk.device", 0,
                       (struct IORequest *)diskReq, 0);

Reading Sectors

UBYTE *buf = AllocMem(512, MEMF_CHIP);  /* MUST be Chip RAM */

diskReq->iotd_Req.io_Command = CMD_READ;
diskReq->iotd_Req.io_Data    = buf;
diskReq->iotd_Req.io_Length  = 512;       /* bytes to read */
diskReq->iotd_Req.io_Offset  = 0;        /* byte offset on disk */
/* offset = (track * 2 + side) * 11 * 512 + sector * 512 */
DoIO((struct IORequest *)diskReq);

Writing + Updating (Motor Control)

/* Write a sector: */
diskReq->iotd_Req.io_Command = CMD_WRITE;
diskReq->iotd_Req.io_Data    = buf;
diskReq->iotd_Req.io_Length  = 512;
diskReq->iotd_Req.io_Offset  = 512 * 10;  /* sector 10 */
DoIO((struct IORequest *)diskReq);

/* Flush write buffer to disk: */
diskReq->iotd_Req.io_Command = CMD_UPDATE;
DoIO((struct IORequest *)diskReq);

/* Turn off motor when done: */
diskReq->iotd_Req.io_Command = TD_MOTOR;
diskReq->iotd_Req.io_Length  = 0;  /* 0=off, 1=on */
DoIO((struct IORequest *)diskReq);

Disk Change Notification

/* Wait for disk insertion/removal: */
diskReq->iotd_Req.io_Command = TD_CHANGENUM;
DoIO((struct IORequest *)diskReq);
ULONG changeCount = diskReq->iotd_Req.io_Actual;

/* Async notification: */
diskReq->iotd_Req.io_Command = TD_ADDCHANGEINT;
diskReq->iotd_Req.io_Data    = (APTR)&myInterrupt;
SendIO((struct IORequest *)diskReq);
/* myInterrupt is signalled on disk change */

Track Caching

trackdisk.device reads an entire track (11 sectors) into an internal buffer on each access. Subsequent reads of other sectors on the same track are served from cache:

Read sector 0 → DMA reads track 0 (11 sectors) → cache hit for sectors 110
Read sector 11 → new track → DMA reads track 1
Read sector 5 → cache hit (still in track 0 buffer)

FPGA implication: the MiSTer core must emulate this whole-track DMA behaviour for correct timing. Games that measure seek+read latency will behave incorrectly if only single sectors are transferred.


Direct Hardware Access (Games/Demos)

Games often bypass trackdisk.device for speed and copy protection:

; Direct floppy read — raw track DMA:
    LEA     $DFF000, A5              ; custom base
    MOVE.L  #TrackBuffer, $20(A5)    ; DSKPT — DMA pointer (Chip RAM)
    MOVE.W  #$8210, $96(A5)          ; DMACON — enable disk DMA

    ; Select drive, side, seek to track:
    MOVE.B  #$F7, $BFD100            ; CIA-B PRB — select DF0, motor on
    ; ... step head to desired track ...

    ; Start reading one track:
    MOVE.W  #$8000|6300, $24(A5)     ; DSKLEN — enable, ~6300 words
    MOVE.W  #$8000|6300, $24(A5)     ; write twice to start DMA

    ; Wait for DMA complete (DSKBLK interrupt):
    BTST    #1, $DFF01F              ; INTREQR — DSKBLK bit
    BEQ.S   .-4

References

  • NDK39: devices/trackdisk.h, resources/disk.h
  • HRM: Amiga Hardware Reference Manual — Disk Controller chapter
  • ADCD 2.1: trackdisk.device autodocs
  • See also: filesystem.md — FFS/OFS block format on top of trackdisk