mirror of
https://github.com/alfishe/amiga-bootcamp.git
synced 2026-06-13 00:26:28 +00:00
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
This commit is contained in:
parent
aeaea88d75
commit
da9e7d3b63
7 changed files with 1599 additions and 301 deletions
|
|
@ -1,74 +1,178 @@
|
|||
[← Home](../README.md) · [Devices](README.md)
|
||||
|
||||
# trackdisk.device — Floppy Disk I/O
|
||||
# trackdisk.device — Floppy Disk DMA Controller
|
||||
|
||||
## Overview
|
||||
|
||||
`trackdisk.device` provides raw sector I/O for Amiga floppy drives. Each drive is a unit (0–3). The device operates on 512-byte sectors, 11 sectors per track (880 KB DD disks) or 22 per track (1760 KB HD).
|
||||
`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).
|
||||
|
||||
---
|
||||
|
||||
## Opening
|
||||
## Hardware Architecture
|
||||
|
||||
```c
|
||||
struct IOExtTD *tdreq = (struct IOExtTD *)
|
||||
CreateIORequest(port, sizeof(struct IOExtTD));
|
||||
OpenDevice("trackdisk.device", 0, (struct IORequest *)tdreq, 0);
|
||||
```mermaid
|
||||
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 (0–79) |
|
||||
| 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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Commands
|
||||
## Using trackdisk.device
|
||||
|
||||
| Code | Constant | Description |
|
||||
|---|---|---|
|
||||
| 2 | `CMD_READ` | Read sectors |
|
||||
| 3 | `CMD_WRITE` | Write sectors |
|
||||
| 4 | `CMD_UPDATE` | Flush write buffer to disk |
|
||||
| 9 | `TD_MOTOR` | Turn motor on/off |
|
||||
| 10 | `TD_FORMAT` | Low-level format track |
|
||||
| 11 | `TD_SEEK` | Move head to track |
|
||||
| 12 | `TD_REMOVE` | Notify on disk change |
|
||||
| 13 | `TD_CHANGENUM` | Get disk change count |
|
||||
| 14 | `TD_CHANGESTATE` | Check if disk present |
|
||||
| 15 | `TD_PROTSTATUS` | Check write-protect |
|
||||
| 16 | `TD_RAWREAD` | Read raw MFM data |
|
||||
| 17 | `TD_RAWWRITE` | Write raw MFM data |
|
||||
| 18 | `TD_GETDRIVETYPE` | Get drive type |
|
||||
| 19 | `TD_GETNUMTRACKS` | Get total tracks |
|
||||
| 20 | `TD_ADDCHANGEINT` | Add disk change interrupt |
|
||||
| 21 | `TD_REMCHANGEINT` | Remove disk change interrupt |
|
||||
|
||||
---
|
||||
|
||||
## Reading a Sector
|
||||
### Opening
|
||||
|
||||
```c
|
||||
UBYTE buf[512];
|
||||
tdreq->iotd_Req.io_Command = CMD_READ;
|
||||
tdreq->iotd_Req.io_Data = buf;
|
||||
tdreq->iotd_Req.io_Length = 512;
|
||||
tdreq->iotd_Req.io_Offset = 0; /* byte offset = sector * 512 */
|
||||
DoIO((struct IORequest *)tdreq);
|
||||
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
|
||||
|
||||
```c
|
||||
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)
|
||||
|
||||
```c
|
||||
/* 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
|
||||
|
||||
```c
|
||||
/* 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 1–10
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## Disk Geometry
|
||||
## Direct Hardware Access (Games/Demos)
|
||||
|
||||
| Parameter | DD (880 KB) | HD (1760 KB) |
|
||||
|---|---|---|
|
||||
| Heads | 2 | 2 |
|
||||
| Cylinders | 80 | 80 |
|
||||
| Sectors/track | 11 | 22 |
|
||||
| Bytes/sector | 512 | 512 |
|
||||
| Total sectors | 1760 | 3520 |
|
||||
Games often bypass trackdisk.device for speed and copy protection:
|
||||
|
||||
Byte offset = `(cylinder * 2 + head) * sectors_per_track * 512 + sector * 512`
|
||||
```asm
|
||||
; 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`
|
||||
- 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](../07_dos/filesystem.md) — FFS/OFS block format on top of trackdisk
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue