Disassembly analysis of MFORMAT.COM from the CP/M 2.27 system disk for the
SVI-328 with SV-608M expansion. This utility formats both hard disks (via the
SASI bus) and floppy disks (via the FDC), including bad track management and
directory initialisation for the hard disk.
Source: MFORMAT.asm
MFORMAT is the low-level disk formatter for the SV-608M system. It provides two major functions:
DSIZE: directory marker that records the usable disk size.At 7,232 lines of disassembly (roughly 5 KB of Z80 code plus 1 KB of string data, plus embedded BIOS routines at 4259h+), MFORMAT is by far the largest utility on the system disk.
On startup, MFORMAT prints:
608M Disks Formatting Program
Press "H" to format a Hard disk, "F" to format a floppy disk
or ENTER to quit):
After each format operation completes, control returns to the main menu.
When H is selected:
Hard Disk Formatter
Press "A" for track 0 - 257
"B" for track 258 - 513
"C" for track 514 - 769
"D" for track 770 - 1223
"X" for track 0 - 1223
or ENTER to quit):
Each partition offers a confirmation prompt:
Press ENTER to confirm or "Q" for quick option or Control-C to cancel):
When F is selected:
Floppy Disk Formatter
Press "1" for Single Side or
"2" for Double Side or
ENTER to quit):
After formatting, asks:
Format another disk? (Y or N):
The hard disk uses linear track addressing with 32 sectors per track and 256 bytes per sector (8 KB per track). The four partitions map to these track ranges:
| Partition | Key | Track Range | Tracks | Size |
|---|---|---|---|---|
| A (= B:) | A | 0 – 257 | 258 | ~2 MB |
| B (= C:) | B | 258 – 513 | 256 | ~2 MB |
| C (= D:) | C | 514 – 769 | 256 | ~2 MB |
| D (= E:) | D | 770 – 1223 | 454 | ~3.5 MB |
| All | X | 0 – 1223 | 1224 | ~9.5 MB |
Drive letters in parentheses are for the floppy BIOS (A:=floppy, B:–E:=HDD). When booted from HDD (T0T1.SYS), partitions A–D map to drives A:–D: instead, with E: as the floppy.
Note: partition D is larger than the others because it covers the remainder of the disk.
Each partition is formatted by its own subroutine (SUB056–SUB059), but they all follow the same pattern:
The “quick” option (Q) skips the verify step by clearing DATA21 (the verify-enable flag checked in SUB071).
Selecting X (SUB060) formats all 1,224 tracks (0–04C7h) in sequence, with two additional operations after formatting:
The bad track management subsystem (SUB061) is only activated after a full disk format (X). It maintains a bad track table at 1D22h — an array of 1,224 bytes (one per track), where:
00h = good track (empty)FFh = alternate track (allocated as replacement)Initialisation (SUB080): zeros all 1,224 bytes, sets the available track counter (SELF_MOD13) to 04C8h (1224).
Recording (SUB081): writes the error code at offset track_number
in the table, decrements the available track counter.
After formatting, SUB061 displays the defect count and offers:
Total number of defective tracks are : NNN
Press "D" for displaying the defective track list
"M" for calculating the auto-mapping sequence
ENTER for quit):
DEFECTIVE TRK ALTERNATE TRK BAD TRKAfter mapping completes, the disk size is updated on disk via SUB069.
SUB069 writes a DSIZE: directory marker to track 15, sector 0
(linear sector 21EAh in the buffer at that address). This marker is a
12-byte record:
| Offset | Content |
|---|---|
| 0–5 | ASCII DSIZE: |
| 6–7 | Current disk size (track count, LE) |
| 8–11 | #### — placeholder signature |
The marker is written using a SASI Write command (SUB008) and verified
on readback (SUB011) by checking for the DSIZE: + #### signature.
This allows the system to determine the usable disk capacity at boot
time, accounting for any tracks lost to bad track remapping.
SUB005 performs the SASI Write for formatting. It calculates the linear
sector address as track × 32 using an ADD IX,DE loop (32 iterations),
then issues the SASI transaction:
Command — send 6-byte SASI Group 0 Write (opcode 0Ah) via SUB047:
| Byte | Value | Field |
|---|---|---|
| 0 | 08h | Write with verify opcode |
| 1 | LUN/SA | LUN 0 + sector address bits 20–16 |
| 2 | SA | Sector address bits 15–8 |
| 3 | SA | Sector address bits 7–0 |
| 4 | 04h | Block count = 4 sectors (1,024 bytes) |
| 5 | 47h | Control: retries + ECC, step = 15 µs |
The command is issued 8 times per track (8 × 4 = 32 sectors per track).
SUB009 performs the SASI Read for verification. Same sector address calculation, with a 6-byte SASI Group 0 Read:
| Byte | Value | Field |
|---|---|---|
| 0 | 05h | Read with verify opcode |
| 1 | LUN/SA | LUN 0 + sector address bits 20–16 |
| 2 | SA | Sector address bits 15–8 |
| 3 | SA | Sector address bits 7–0 |
| 4 | 07h | Block count = 7 sectors |
| 5 | 07h | Control: retries, step = 15 µs |
Data is read into a buffer at 21EAh and compared byte-by-byte against E5h (SUB072). If any mismatch is found, the track is marked defective.
SUB084 detects the floppy drive type using the same technique as MSYSGEN:
SELDSK(0) via a self-modifying JP instruction (SUB104)A:. Otherwise, E:Floppy formatting uses two distinct track layouts:
Track 0 — Single Density (128 bytes/sector):
Tracks 1–39 — Double Density (256 bytes/sector):
Tracks 40–79, Side 2 (double-sided only):
The skew table at 0B54h defines the physical sector ordering to optimise sequential access:
01 0A 02 0B 03 0C 04 0D 05 0E 06 0F 07 10 08 11 09 12
This is a 2:1 interleave pattern — logical sectors are spread across the track so the disk has time to process one sector before the next logical sector arrives under the head.
The source code contains a note: “Skew table follows and may be patched for better response in your application” — indicating users could customise the interleave.
For each track, SUB085 performs:
The FDC is accessed through three ports:
| Port | Direction | Usage in MFORMAT |
|---|---|---|
| 30h | W/R | FDC Command / Status register |
| 34h | W | Disk Select (0Dh = drive select pattern) |
| 34h | R | INTRQ/DRQ status (bit 7 = INTRQ, bit 6 = DRQ) |
| 38h | W | Density: 00h=DD, 01h=SD, 02h=DD side 2 |
FDC commands used:
0100h LBL1 — Entry: JP LBL2
0103h–011Ah SUB003 — SASI bus reset for formatting
011Bh–0139h SUB004/124 — SASI bus init, test disk ready
013Ah–01F1h (data) — Unreachable code/data region
01F2h–022Eh SUB005 — SASI Write (format track, 32 sectors)
022Fh–026Bh SUB006 — SASI Read (used by alternate mapping)
026Ch–02ABh SUB007 — SASI Write (4 sectors, for directory)
02ACh–02EBh SUB008 — SASI Write (4 sectors, for DSIZE)
02ECh–0328h SUB009 — SASI Read (32 sectors, verify track)
0329h–0394h SUB010 — SASI Data Copy (read + write, remap)
0395h–041Ah SUB011 — SASI Read + validate DSIZE: marker
044Bh–048Ch SUB012/013 — SASI address validation
048Dh–04B2h SUB016/017 — SASI status check / result decode
04B3h–0582h SUB018–024 — SASI command block builders
0583h–065Ch SUB025–031 — SASI command parameter setup
065Dh–0892h SUB030–041 — SASI read/write/format with verify
0893h–0A11h SUB042–054 — SASI bus phases (select, cmd, data, status, msg)
0A12h–0A4Ah DATA03–14 — SASI command buffer and state variables
0AFCh DATA16 — Stack save area
0AFEh LBL2 — JP LBL3 (startup redirect)
0B01h–0B53h (strings) — Skew table description text
0B54h–0B65h (data) — Sector skew table (18 bytes)
0B66h–0BB3h LBL3 — Main menu: dispatch H/F/ENTER
0BB4h–0C20h SUB055 — HDD format dispatcher (partition select)
0C21h–0C85h SUB056 — Format partition A (tracks 0–257)
0C86h–0CEAh SUB057 — Format partition B (tracks 258–513)
0CEBh–0D4Fh SUB058 — Format partition C (tracks 514–769)
0D50h–0DB4h SUB059 — Format partition D (tracks 770–1223)
0DB5h–0E15h SUB060 — Format ALL partitions + bad track mgmt
0E16h–0EDFh SUB061 — Bad track management menu (D/M/ENTER)
0EE0h–0F20h SUB062 — Display defective track list
0F21h–0F58h SUB063 — Scan bad track table for next defect
0F59h–0FE1h SUB064 — Auto-map defective tracks to alternates
0FE2h–1027h SUB065 — Find next available alternate track
1028h–106Eh SUB066 — Write/verify alternate track
106Fh–10D5h SUB067 — Copy + verify bad→alternate track data
10D6h–1113h SUB068 — Find next bad track entry
1114h–1195h SUB069 — Write DSIZE: directory marker to disk
1196h–11ACh SUB070 — Format single HDD track (print + SUB005)
11ADh–11D3h SUB071 — Verify single HDD track (print + SUB009)
11D4h–1224h SUB072 — Verify E5h pattern (8 × 1024 bytes)
1225h–1256h SUB073 — Display hex error code (* ERROR CODE : XXH *)
125Ah–128Bh SUB075 — Print leading spaces for number alignment
128Ch–129Fh SUB076 — Print decimal track number
12A0h–12A7h SUB077 — Print space character
12A8h–12B5h SUB078 — Print $-terminated string (BDOS 9)
12B6h–12BEh SUB079 — Print CR/LF
12BFh–12E3h SUB080 — Initialise bad track table (1224 zeros)
12E4h–12F9h SUB081 — Record bad track in table
12FAh–1308h SUB082 — Console output (BDOS 2)
1309h–1312h SUB083 — Keyboard input (BDOS 6 polling)
1313h–1381h SUB084 — Floppy format main: detect drive, patch BIOS
1382h–1514h SUB085 — Floppy format loop (track 0 SD + tracks 1+ DD)
1515h–1520h SUB086 — BDOS 6 direct console input (polling)
1521h–1529h SUB087 — BDOS 2 echo character
152Ah–1538h SUB088 — Print track number during floppy format
1539h–155Ah SUB089 — Recursive decimal number print
155Bh–155Fh SUB090 — Short delay (4× EX (SP),HL)
1560h–1569h SUB091 — Patched BIOS Read (self-modifying JP)
156Ah–156Ch SUB092 — Patched BIOS Write (self-modifying JP)
156Dh–1571h SUB093 — FDC Seek command (02h)
1572h–1573h SUB094 — FDC Seek with verify (52h)
1574h–1587h SUB095 — FDC command issue + wait for INTRQ
1588h–15B0h SUB096 — FDC status check with timeout
15B1h–15BBh SUB097 — Print FDC error message (BDOS 9)
15BCh–15F2h SUB098 — FDC Write Track command (F0h)
15F3h–15F9h SUB099 — Wait for FDC not busy
15FAh–1605h SUB100 — FDC Force Interrupt (D0h)
1606h–166Bh SUB101 — Build SD format buffer (track 0, 18 sectors)
166Ch–16DFh SUB102 — Build DD format buffer (tracks 1+, 17 sectors)
16E0h–16E6h SUB103 — Fill buffer helper (B bytes of A)
16E7h–1D06h (strings) — All user-facing messages
1D07h–1D09h (data) — CR/LF + '$' (used by SUB079)
1D0Ah DATA20 — Temp storage for error code display
1D0Bh DATA21 — Verify enable flag (01h=verify, 00h=skip)
1D0Ch SUB104 — Patched JP to BIOS SELDSK (self-modifying)
1D0Fh SELF_MOD05 — FDC error flag
1D10h SELF_MOD06 — Alternate mapping mode (0=display, 1=write)
1D11h SELF_MOD07 — Sides: 01h=single, 02h=double
1D12h SELF_MOD08 — Current FDC track number
1D13h SELF_MOD09 — Current FDC side (00h or 01h)
1D14h–1D15h SELF_MOD10 — Current bad/alternate track number
1D16h–1D17h SELF_MOD11 — Alternate track number (for remap)
1D18h–1D19h SELF_MOD12 — Bad track count
1D1Ah–1D1Bh SELF_MOD13 — Available track count (starts at 04C8h)
1D1Ch–1D1Dh SELF_MOD15 — Bad track scan pointer (forward)
1D1Eh–1D1Fh SELF_MOD16 — Alternate scan pointer (backward)
1D20h–1D21h SELF_MOD17 — Remaining tracks to scan
1D22h–21E9h SELF_MOD18 — Bad track table (1224 bytes, one per track)
21EAh+ (buffer) — I/O buffer for format/verify data
4259h+ (code) — Relocated BIOS routines (SELDSK dispatch etc.)
| Function | C reg | Name | Usage in MFORMAT |
|---|---|---|---|
| 2 | 02h | Console Output | Echo typed characters, print digits (SUB082) |
| 6 | 06h | Direct Console | Non-blocking keyboard input (SUB086) |
| 9 | 09h | Print String | Display $-terminated messages (SUB078) |
MFORMAT uses extensive self-modifying code, more so than MSYSGEN:
* ERROR CODE : XXH * and the track is marked
defective in the bad track table (full format mode only).All error messages are preceded by a BEL character (07h) for audible alert.
Like the BIOS and MSYSGEN, MFORMAT has no timeout on SASI bus phase polls. If the hard disk controller hangs, the program will busy-wait forever. The FDC status check (SUB096) does have a finite timeout loop (counts down HL from 0000h twice).