TRS-80 DOS - VTOS v4.0.2 for the Model I - BOOT/SYS Disassembled

Page Customization

Introduction/Summary:

VTOS 4.0.2 BOOT/SYS Disassembly - Boot Sector (Model I)

The BOOT/SYS file is the first thing loaded when the TRS-80 boots from disk. The Model I ROM boot routine reads Track 0, Sector 0 from the floppy disk into RAM at 4200H and jumps to it. The BOOT/SYS file in VTOS 4.0.2 is unique among TRS-80 operating systems because it contains two completely independent boot code sectors: one for the standard Radio Shack Expansion Interface (single-density, WD1771 FDC accessed via memory-mapped I/O at 37ECH-37EFH) and one for the Lobo LX-80 Expansion Interface (which uses a different disk controller interface). This dual-platform design is confirmed by Tim Mann's account of VTOS internals.

Both boot sectors share the same fundamental task: read SYS0/SYS from the disk, parse its /CMD-format load module structure, load the code into memory, and jump to the SYS0 entry point. If SYS0 cannot be found or a disk error occurs, the boot code displays "NO SYSTEM" or "DISK ERROR" and halts.

The BOOT/SYS file on disk occupies 5 sectors (1280 bytes) organized as follows:

SectorFile OffsetRAM AddressPurpose
00000H-00FFH4200H-42FFHSingle-density boot code (Radio Shack Expansion Interface)
10100H-01FFH4200H-42FFHDouble-density boot code (Lobo LX-80 Interface)
20200H-02FFH4400H-44FFHDirectory sector: jump vector table, disk serial/password, Disk Geometry parameters
30300H-03FFH4500H-45FFHGAT (Granule Allocation Table) and disk label
40400H-04FFH4600H-46FFHHIT (Hash Index Table) - empty on this disk image

Only one boot sector is loaded during any given boot. The Model I ROM loads Track 0, Sector 0, which on a standard Radio Shack formatted disk is the single-density sector (Sector 0). On a Lobo LX-80 formatted disk, the ROM loads the double-density sector (Sector 1). Both sectors load at 4200H and execute from there.

The boot code structure follows the TRSDOS 2.x convention established by Randy Cook. It begins with 00H FEH, which serves dual purpose: as Z80 code it is NOP / CP 11H (harmless), but it also functions as a /CMD comment record header if the file is loaded as a /CMD program (the BOOT/SYS.WHO easter egg trick from TRSDOS works by exploiting this dual interpretation).

Ihteresting Tidbits about the Lobo portion:

  • The LX-80 boot uses IY-indexed memory access (FD 7E xx / FD 77 xx) to read and write a hardware descriptor table, abstracting the FDC interface so the same boot code works on different disk controllers.
  • It includes a proper 8-bit shift-and-add multiply routine at 42D3H (ADD A,A / SLA E / JR NC / ADD A,D / DJNZ) that computes sectors-per-track dynamically from drive parameters.
  • Self-modifying code at 422DH patches the SUB operand at 42A5H with the computed sectors-per-track, replacing the single-density boot's hardcoded SUB 0AH (10 sectors/track). This is the only self-modifying code in the file.
  • The FDC dispatch at 42D1H is a single JP (IY) instruction, which is an elegant hardware abstraction layer - the LX-80 ROM sets IY to point to its FDC driver before loading the boot sector.

Key Memory Locations

AddressBytesPurpose
37E0H1Drive select latch (Model I, memory-mapped). Bits 0-3 select drives 0-3.
37E1H1Drive select latch (alternate address, same physical latch as 37E0H). Boot code writes 01H here to select Drive 0.
37ECH1WD1771 FDC Command/Status Register (memory-mapped)
37EDH1WD1771 FDC Track Register (memory-mapped)
37EEH1WD1771 FDC Sector Register (memory-mapped)
37EFH1WD1771 FDC Data Register (memory-mapped)
4200H256Boot sector load address. Active boot code occupies 4200H-42FFH.
41E0H-Stack pointer for single-density boot (grows downward)
4160H-Stack pointer for double-density boot (grows downward)
5100H256Sector buffer. Boot code reads disk sectors here for /CMD parsing.
5116H2Directory entry pointer within loaded sector buffer (track/sector of SYS0)

Major Routines - Single-Density Boot (Sector 0)

AddressName / Purpose
4200HBoot Entry
Initialization: disable interrupts, set stack, select drive 0, read directory
4242H/CMD Parser
Main loop: reads /CMD record type bytes, dispatches to data load (type 01), skip (type 02), or transfer (type 03)
4279HGet Next Byte
Returns next byte from sector buffer; auto-reads next sector when buffer exhausted
429EHDisplay Message
Displays 03H-terminated string at (HL) via ROM CALL 0033H
42ACHRead Sector
Reads a single sector from disk using WD1771 FDC via memory-mapped I/O
42B4HFDC Core
Low-level FDC command execution: issues command, waits for completion, transfers data
42E4HInit Screen Msg
Data: screen clear codes (1CH=clear screen, 1FH=home cursor), terminated by 03H
42E7HNo System Msg
Data: "NO SYSTEM" with screen positioning prefix 17H E8H
42F3HDisk Error Msg
Data: "DISK ERROR" with screen positioning prefix 17H E8H

Major Routines - Double-Density Boot (Sector 1)

AddressName / Purpose
4200HBoot Entry
Initialization: disable interrupts, set stack to 4160H, configure IY for Lobo interface
4260H/CMD Parser
Same logic as single-density, with Lobo-specific sector read calls
4297HGet Next Byte
Same function, calls Lobo read sector routine
42B7HDisplay Message
Same display function as single-density
42C5HRead Sector
Disk read using Lobo LX-80 interface (IY-indexed port access)
42D1HLX-80 FDC Dispatch
Dispatches FDC command via JP (IY) for Lobo hardware abstraction
42D3HCRC/Multiply
CRC or sector-to-granule calculation using shift-and-add multiply loop
42E4HInit Screen Msg
Data: 03H (immediate terminator - no screen clear in DD boot)
42E7HNo System Msg
Data: "NO SYSTEM" (same text, shifted 2 bytes earlier in sector)
42F1HDisk Error Msg
Data: "DISK ERROR"

Disk Structures (Sectors 2-4)

OffsetSectorContent
0200H2Directory sector header: 40H at offset 0 (64 entries flag)
0220H-0247H2Jump vector table: four JP 4600H entries with drive/track/sector parameters for SYS overlay loading
0248H-026FH2Additional entry point stubs (C9H = RET at 0248H, 0252H, 025CH, 0266H)
02C0H-02C7H2Disk serial/password: "QS81334Q"
02C8H-02DFH2Disk geometry, granule configuration, allocation info
0300H-03CAH3GAT (Granule Allocation Table): FFH=allocated, FCH=locked/system, 00H=free
03CBH-03DFH3Disk label: 40H (track count), 00H 01H E0H 42H (load params), "VTOS40:F08/21/80" (name:format/date)
03E0H-03FFH3Padding (0DH + spaces)
0400H-04FFH4HIT (Hash Index Table): all zeros (empty on this disk image)

Cross-Reference Notes

The boot code calls into Model I ROM routines at 0033H (display character) and 0040H (line input / wait). It reads directory sectors from Track 17 and locates the SYS0/SYS directory entry to determine where SYS0 is stored on disk. The jump vector table at 0220H contains four JP 4600H instructions, indicating that SYS0 loads at or dispatches through 4600H. This is consistent with the TRSDOS 2.x architecture where the resident DOS occupies 4000H-4CFFH and overlay SYS files load into a shared overlay area.

VTOS 4.0.2 served as the direct ancestor of LDOS. According to Tim Mann, who was one of the five core LDOS developers, Lobo Drives obtained rights to VTOS but could not obtain source code from Randy Cook, so all LDOS development was done from raw machine code disassembly using Roy Soltoff's disassembler.

Disassembly of the TRS-80 Boot Sector (Sector 0):

4200H - Boot Entry Point and Initialization

Single-density boot code entry point. The Model I ROM loads Track 0, Sector 0 to 4200H and jumps here. This code initializes the CPU, selects Drive 0, reads the directory to locate SYS0/SYS, and begins loading it into memory.

4200
NOP 00
No operation. This byte, combined with the next (FEH), forms the /CMD comment record header 00H FEH. As Z80 code it is harmless. As a /CMD load module, the 00H byte indicates a comment record type, allowing the boot sector to also function as a loadable /CMD file (this is the same dual-purpose trick Randy Cook used in TRSDOS 2.x to enable the BOOT/SYS.WHO easter egg).
4201
CP 11H FE 11
Compare Register A against 11H. As Z80 code, this compares the accumulator (which contains an undefined value on cold boot) against 11H and sets flags, but the result is immediately discarded. The FEH byte is the /CMD comment record length byte (254 = remaining bytes in this 256-byte sector are "comment" data, causing a /CMD loader to skip over the entire boot sector).
4203
DI F3
Disable all maskable interrupts. This is the true start of boot initialization. Interrupts must be disabled during boot to prevent any interrupt handlers (which have not been set up yet) from executing during disk I/O.
4204
LD SP,41E0H 31 E0 41
Set the stack pointer to 41E0H. The stack grows downward from here, providing workspace below the boot code at 4200H. This gives 480 bytes of stack space (41E0H down to 4000H).
4207
LD IY,0000H FD 21 00 00
Set Index Register IY to 0000H. In the single-density boot, IY is initialized to zero and not used further. In the double-density boot (Sector 1), IY points to the Lobo LX-80 hardware interface vector table.
420B
Point Register Pair HL to the screen initialization message at 42E4H. This message contains 1CH (clear screen), 1FH (home cursor), and 03H (terminator).
420E
GOSUB to the Display Message routine at 429EH to clear the screen and home the cursor. The routine displays characters from (HL) until it encounters a 03H delimiter.
4211
LD A,01H 3E 01
Load Register A with 01H to select Drive 0. On the Model I, the drive select latch at 37E0H-37E3H uses bit 0 for Drive 0, bit 1 for Drive 1, bit 2 for Drive 2, bit 3 for Drive 3.
4213
LD (37E1H),A 32 E1 37
Store the drive select value (01H = Drive 0) to the drive select latch at 37E1H. This activates Drive 0's motor and connects it to the FDC. Address 37E1H is an alias for the same physical latch at 37E0H.
4216
LD A,(4202H) 3A 02 42
Fetch the byte at 4202H into Register A. Address 4202H is within the boot code itself and contains 11H (the operand of the CP 11H instruction at 4201H). This value is used as the directory track number: Track 17 (11H = 17 decimal), which is the standard directory track for TRSDOS-compatible DOSes on 35/40-track disks.
4219
LD D,A 57
Copy the directory track number (11H = Track 17) from Register A to Register D. Register D will hold the track number for the sector read routine.
421A
LD E,04H 1E 04
Load Register E with 04H (sector 4). This is the directory sector where SYS0/SYS's directory entry is expected. In TRSDOS 2.x and VTOS, SYS0/SYS is always at a fixed directory position on Track 17, Sector 4.
421C
LD BC,5100H 01 00 51
Point Register Pair BC to 5100H, the 256-byte sector read buffer. Disk sectors will be read into this buffer by the sector read routine.
421F
GOSUB to the Read Sector routine at 42ACH. This reads the directory sector at Track 17, Sector 4 into the buffer at 5100H. On return, the Z FLAG indicates success (Z=read OK) or failure (NZ=error).
4222
If the NZ FLAG (Not Zero) is set, the sector read failed. JUMP to 4294H to display the "DISK ERROR" message and halt.
4224
LD A,(5100H) 3A 00 51
Fetch the first byte of the directory sector from the buffer at 5100H into Register A. This byte contains directory entry flags. Bit 4 (10H) indicates whether a valid file entry exists at this position.
4227
AND 10H E6 10
Mask Register A with 10H (00010000 binary) to isolate bit 4. If bit 4 is set, a valid directory entry exists for SYS0/SYS. If bit 4 is clear, SYS0 was not found at the expected directory position.
4229
Point Register Pair HL to the "NO SYSTEM" error message at 42E7H. This is loaded preemptively in case the directory entry check fails.
422C
If the Z FLAG (Zero) is set (bit 4 was clear, meaning no valid SYS0 entry), JUMP to 4297H to display the "NO SYSTEM" message and halt. If bit 4 was set (valid entry found), fall through to begin loading SYS0.

SYS0 Located
At this point, the directory sector is in the buffer at 5100H and contains a valid SYS0/SYS entry. The code now extracts the starting track/sector from the directory entry to begin reading the SYS0 /CMD load module from disk.

422E
EXX D9
Switch to the alternate register set (BC', DE', HL'). The alternate registers will be used to track the current disk position (track in D', sector in E') and the sector buffer pointer (BC') while the primary registers handle the /CMD load module parsing.
422F
LD HL,(5116H) 2A 16 51
Fetch the 2-byte value at 5116H from the directory sector buffer into Register Pair HL. Offset 16H within the directory entry contains the starting granule/track/sector information for SYS0/SYS. The low byte (L) contains the starting sector, and the high byte (H) contains encoded track/granule data.
4232
LD D,L 55
Copy the starting sector number from Register L to Register D. (This appears to be part of a track/sector decode sequence; the registers are shuffled as the encoded directory data is unpacked.)
4233
LD A,H 7C
Copy the high byte of the directory pointer (containing encoded track/granule info) from Register H to Register A for bit manipulation.
4234
RLCA 07
Rotate Register A left 1 bit (bit 7 moves to bit 0 and into Carry). First step of extracting the track number from the encoded granule byte.
4235
RLCA 07
Rotate Register A left 1 bit again. Second rotation.
4236
RLCA 07
Rotate Register A left 1 bit again. After three left rotations, the upper 3 bits of the original value are now in bits 0-2, and the lower 5 bits are in bits 3-7.
4237
AND 07H E6 07
Mask Register A with 07H (00000111 binary) to isolate the 3-bit granule number that was originally in the top 3 bits of the encoded byte. This gives the granule number within the track.
4239
LD H,A 67
Store the granule number (0-7) in Register H temporarily for the multiplication that follows.
423A
RLCA 07
Rotate Register A left 1 bit, effectively multiplying the granule number by 2.
423B
RLCA 07
Rotate Register A left 1 bit again, effectively multiplying by 4 total.
423C
ADD A,H 84
ADD the original granule number (Register H) to the result (granule x 4), giving granule x 5. This converts a granule number to a sector offset, since each granule contains 5 sectors in VTOS/TRSDOS 2.x format.
423D
LD E,A 5F
Store the computed starting sector number (granule x 5) into Register E'. Combined with the track number in D', this gives the starting disk address for reading SYS0.
423E
LD BC,51FFH 01 FF 51
Point Register Pair BC' to 51FFH, which is the last byte of the 256-byte sector buffer at 5100H. The Get Next Byte routine at 4279H increments C' before reading, so starting at 51FFH means the first increment wraps C' to 00H, making the effective starting pointer 5100H (but triggering an immediate sector read since C' wraps past FFH).
4241
EXX D9
Switch back to the primary register set. The alternate registers now hold: D'=starting track, E'=starting sector, BC'=51FFH (buffer pointer, will trigger first read). The primary registers are now free for /CMD parsing.

4242H - /CMD Load Module Parser

Main loop that parses the /CMD load module format to load SYS0/SYS into memory. The /CMD format uses record type bytes: 01H = data block (load code into memory), 02H = skip block (discard bytes), 03H = transfer address (jump to loaded code). This parser reads the type byte, dispatches accordingly, and loops until a transfer record is found.

4242
GOSUB to the Get Next Byte routine at 4279H to read the next byte from the /CMD load module stream. On return, Register A contains the byte read. This is the /CMD record type byte: 01H=data, 02H=skip, 03H=transfer.
4245
DEC A 3D
DECrement Register A by 1. If the record type was 01H (data block), A becomes 0 and the Z FLAG is set. If it was 02H (skip) or 03H (transfer), A becomes 1 or 2 respectively.
4246
If the NZ FLAG (Not Zero) is set (record type was NOT 01H), JUMP to 425FH to handle skip (02H) or transfer (03H) records.

Type 01H Data Block Handler
The record type was 01H (data block). The next bytes in the stream are: length byte (2's complement), followed by 2-byte load address (low/high), followed by the data bytes. The length byte represents (256 - actual_length); the actual number of data bytes is (256 - length_byte).

4248
GOSUB to Get Next Byte at 4279H to read the length byte of the data block. Register A now holds the 2's complement length: actual data byte count = (256 - A), but here it is used directly as a down-counter.
424B
LD B,A 47
Copy the length byte from Register A to Register B. Register B will serve as the byte counter for reading the data block.
424C
GOSUB to Get Next Byte at 4279H to read the low byte of the load address. Register A now holds the destination address low byte.
424F
LD L,A 6F
Store the load address low byte into Register L.
4250
DEC B 05
DECrement the length counter in Register B by 1 (to account for the low address byte just read).
4251
GOSUB to Get Next Byte at 4279H to read the high byte of the load address. Register A now holds the destination address high byte.
4254
LD H,A 67
Store the load address high byte into Register H. Register Pair HL now points to the memory location where the data bytes should be loaded.
4255
DEC B 05
DECrement the length counter in Register B by 1 (to account for the high address byte just read).
4256
If the Z FLAG (Zero) is set (counter reached zero, meaning the length byte was 02H and both address bytes consumed all the data), LOOP BACK to 4242H to process the next /CMD record. This handles the edge case of an empty data block.

Data Copy Loop
Register Pair HL points to the destination in memory. Register B holds the remaining byte count. Each iteration reads one byte from the /CMD stream and stores it at (HL), then advances HL.

4258
GOSUB to Get Next Byte at 4279H to read the next data byte from the /CMD stream.
425B
LD (HL),A 77
Store the data byte from Register A into memory at the address pointed to by Register Pair HL. This loads one byte of SYS0 code into its target RAM location.
425C
INC HL 23
INCrement Register Pair HL by 1 to advance the destination pointer to the next memory location.
425D
LOOP BACK to 4255H to decrement the byte counter (B) and continue copying data bytes until the counter reaches zero.
425F
DEC A 3D
DECrement Register A by 1 again. At entry, A was (record_type - 1). After this DEC: if original type was 02H (skip), A becomes 0 and Z is set. If original type was 03H (transfer), A becomes 1.
4260
If the Z FLAG is set (record type was 02H = skip block), JUMP to 426DH to handle the skip/comment record.

Type 02H Skip Block Handler
The record type was 02H (skip/comment block). The next byte is a length, followed by that many bytes to be read and discarded.

4262
GOSUB to Get Next Byte to read the skip block length.
4265
LD B,A 47
Copy the skip length from Register A to Register B as a byte counter.
4266
GOSUB to Get Next Byte to read and discard one byte from the skip block.
4269
DECrement B and LOOP BACK to 4266H if not zero. This discards the remaining bytes of the skip block.
426B
LOOP BACK to 4242H to process the next /CMD record after the skip block is consumed.

Type 03H Transfer Record Handler
The record type was 03H (transfer address). The next 3 bytes are: a length/flag byte (discarded), followed by the 2-byte execution address (low/high). After reading the execution address, the code jumps there to begin executing SYS0.

426D
GOSUB to Get Next Byte to read the transfer record's first byte (length/flag, discarded).
4270
GOSUB to Get Next Byte to read the low byte of the transfer (execution) address.
4273
LD L,A 6F
Store the execution address low byte into Register L.
4274
GOSUB to Get Next Byte to read the high byte of the transfer (execution) address.
4277
LD H,A 67
Store the execution address high byte into Register H. Register Pair HL now contains the entry point of SYS0.
4278
JP (HL) E9
JUMP to the address in Register Pair HL, transferring control to SYS0. The boot process is complete. SYS0 will initialize the rest of the DOS and bring the system to the VTOS Ready prompt.

4279H - Get Next Byte From /CMD Stream

Reads the next sequential byte from the /CMD load module being loaded from disk. Uses the alternate register set to track the sector buffer position (BC') and current disk track/sector (DE'). When the buffer is exhausted (C' wraps from FFH to 00H), automatically reads the next sequential sector from disk and resets the buffer pointer.

4279
EXX D9
Switch to the alternate register set. BC' is the sector buffer pointer (5100H-51FFH), DE' is the current track/sector on disk.
427A
INC C 0C
INCrement Register C' (the low byte of the buffer pointer) by 1. If C' was FFH, it wraps to 00H and the Z FLAG is set, indicating the buffer is exhausted and a new sector must be read.
427B
If the NZ FLAG is set (buffer not exhausted), JUMP to 4291H to read the next byte from the buffer and return.

Buffer Exhausted
C' wrapped to 00H, meaning all 256 bytes of the current sector have been consumed. The code must read the next sequential sector from disk into the buffer at 5100H.

427D
PUSH BC C5
Save Register Pair BC' (the buffer pointer) onto the stack.
427E
LD A,01H 3E 01
Load Register A with 01H (Drive 0 select value).
4280
LD (37E1H),A 32 E1 37
Store the drive select value (01H) to the drive select latch at 37E1H to ensure Drive 0 is still selected. The drive motor may have spun down during processing, so this re-selects it.
4283
GOSUB to the Read Sector routine at 42ACH to read the next sector (track in D', sector in E') into the buffer at BC' (5100H).
4286
If the NZ FLAG is set (disk read error), JUMP to 4294H to display "DISK ERROR" and halt.
4288
POP BC C1
Restore Register Pair BC' (the buffer pointer, now pointing to 5100H since C' was reset to 00H).
4289
INC E 1C
INCrement Register E' (the sector number) by 1 to advance to the next sector for the subsequent read.
428A
LD A,E 7B
Copy the new sector number from Register E' to Register A to check for track boundary crossing.
428B
SUB 0AH D6 0A
SUBtract 0AH (10 decimal) from Register A. VTOS uses 10 sectors per track (sectors 0-9). If the sector number has reached 10, it means we have crossed a track boundary and need to advance to the next track.
428D
If the NZ FLAG is set (sector number has not reached 10), JUMP to 4291H to read the byte and return. The current track is still valid.
428F
LD E,A 5F
Store the result of the subtraction (0) into Register E', resetting the sector counter to 0 for the new track.
4290
INC D 14
INCrement Register D' (the track number) by 1 to advance to the next track on disk.
4291
LD A,(BC) 0A
Fetch the byte at the address pointed to by Register Pair BC' from the sector buffer into Register A. This is the next byte from the /CMD load module stream.
4292
EXX D9
Switch back to the primary register set. Register A contains the byte just read, which is preserved across the EXX switch.
4293
RET C9
Return to the caller with the next /CMD byte in Register A.

4294H - Error Handler: Display Message and Halt

Error exit point. Loads HL with the "DISK ERROR" message pointer at 42F3H, then falls through to display the message and halt the CPU. The "NO SYSTEM" error path at 422CH jumps to 4297H with HL already pointing to 42E7H.

4294
Point Register Pair HL to the "DISK ERROR" message string at 42F3H.
4297
GOSUB to the Display Message routine at 429EH to print the error message (either "NO SYSTEM" or "DISK ERROR" depending on how we arrived here).
429A
GOSUB to the ROM line input routine at 0040H. This waits for the user to press a key (providing a "press any key" pause before halting). On the Model I, ROM 0040H reads a line of keyboard input.
429D
HALT 76
HALT the CPU. Since interrupts are disabled (DI was executed at 4203H), this permanently halts the processor. The user must press RESET to restart.

429EH - Display Message Routine

Displays a 03H-terminated string. On entry, Register Pair HL points to the first character of the message. Characters are displayed one at a time via the ROM character output routine at 0033H until a 03H delimiter byte is encountered.

429E
PUSH HL E5
Save Register Pair HL (message pointer) onto the stack. The original pointer is preserved so the caller can reference it after the display routine returns.
429F
LD A,(HL) 7E
Fetch the next character from the message string at the address pointed to by Register Pair HL into Register A.
42A0
CP 03H FE 03
Compare Register A against 03H (ETX, the message terminator). If Register A equals 03H, the Z FLAG is set and the message is complete.
42A2
If the Z FLAG (Zero) is set (character is the 03H terminator), JUMP to 42AAH to restore HL and return.
42A4
GOSUB to the ROM character display routine at 0033H to output the character in Register A to the video screen at the current cursor position, then advance the cursor.
42A7
INC HL 23
INCrement Register Pair HL by 1 to advance the message pointer to the next character.
42A8
LOOP BACK to 429FH to fetch and display the next character.
42AA
POP HL E1
Restore Register Pair HL from the stack (the original message pointer).
42AB
RET C9
Return to the caller.

42ACH - Read Sector Routine

Reads a single 256-byte sector from the floppy disk into the buffer at (BC). On entry: D=track number, E=sector number, BC=buffer address. This routine first calls the FDC core at 42B4H. If the FDC core returns Z (success), this routine returns Z. If the FDC core returns NZ (error), the routine retries by falling through to call 42B4H directly with the buffer address in BC.

42AC
PUSH BC C5
Save Register Pair BC (sector buffer address, typically 5100H) onto the stack.
42AD
GOSUB to the FDC Core routine at 42B4H to attempt reading the sector. On return: Z FLAG set = success, NZ FLAG set = error.
42B0
POP HL E1
Restore the saved buffer address from the stack into Register Pair HL (note: was pushed as BC, popped as HL).
42B1
RET Z C8
If the Z FLAG (Zero) is set (read succeeded on first attempt), return to the caller with Z set indicating success.

Retry on Error
The first read attempt failed. The code falls through to retry. Register Pair HL now contains the buffer address (popped from the stack), and it is copied to BC for the retry call to 42B4H.

42B2
LD B,H 44
Copy the buffer address high byte from Register H to Register B.
42B3
LD C,L 4D
Copy the buffer address low byte from Register L to Register C. Register Pair BC now holds the buffer address for the retry attempt. The code falls through into the FDC Core at 42B4H for the second (and final) attempt.

42B4H - FDC Core: Low-Level Disk Read

Performs a low-level sector read using the WD1771 FDC via memory-mapped I/O at 37ECH-37EFH. On entry: D=track, E=sector, BC=buffer address. The routine writes the track and sector numbers to the FDC registers, issues a Seek command (1BH) to position the head, waits for the FDC to become ready, issues a Read Sector command (88H), then transfers 256 bytes from the FDC data register to the buffer using a tight polling loop. On return: Z FLAG set = success, NZ FLAG set = error.

42B4
LD (37EEH),DE ED 53 EE 37
Store Register Pair DE to 37EEH. This writes the sector number (E) to the FDC Sector Register at 37EEH, and the track number (D) to 37EFH (the FDC Data Register). The FDC Data Register must contain the target track number before a Seek command is issued.
1771 FDC Registers WrittenFunction Description
37EEH (Sector Reg)37EFH (Data Reg)Summary
E = Sector NumberD = Track NumberSector register receives E, Data register receives D (target track for Seek)
42B8
LD HL,37ECH 21 EC 37
Point Register Pair HL to the FDC Command/Status Register at 37ECH. This register is used both to issue commands (write) and to read status (read).
42BB
LD (HL),1BH 36 1B
Write the Seek command byte 1BH to the FDC Command Register at 37ECH. This tells the WD1771 to seek to the track number previously loaded into the Data Register (37EFH).
1771 FDC Command: 1BH (00011011)Function Description
Bit 7Bit 6Bit 5Bit 4Bit 3Bit 2Bit 1Bit 0Summary of Bits
00011011Command=Seek
Bit 7-4: Command Code (0001)
h=1: Enable Head Load/Settle delay
V=0: No track verification
r1,r0=11: Stepping Motor Rate = 15ms (slowest, most compatible)

FDC Timing Delay
After issuing the Seek command, the code must wait for the WD1771 to process it. The four EX (SP),HL instructions below create a timing delay by performing stack exchanges (each takes 19 T-states). This is the critical WD1771 delay that early TRSDOS famously omitted, causing disk errors. The 1771 requires several microseconds after receiving a command before its status register is valid.

42BD
EX (SP),HL E3
Exchange the value at the top of the stack with Register Pair HL. This is a timing delay instruction (19 T-states). HL still points to 37ECH after the exchange pair completes.
42BE
EX (SP),HL E3
Exchange again, restoring the original values. Second delay cycle (19 T-states).
42BF
EX (SP),HL E3
Third exchange/delay (19 T-states).
42C0
EX (SP),HL E3
Fourth exchange/delay (19 T-states). Total delay = 76 T-states = approximately 43 microseconds at 1.77 MHz, sufficient for the WD1771 to begin processing the command.
42C1
LD A,(HL) 7E
Read the FDC Status Register at 37ECH (pointed to by HL) into Register A.
42C2
RRCA 0F
Rotate Register A right 1 bit. This moves bit 0 (the Busy flag) into the Carry flag. If the FDC is still busy executing the Seek, the Carry flag will be set.
42C3
If the CARRY FLAG is set (FDC still busy), LOOP BACK to 42C1H to keep polling the status register until the Seek completes.

Seek Complete
The FDC has finished seeking to the target track. Now issue a Read Sector command to read the data.

42C5
LD (HL),88H 36 88
Write the Read Sector command byte 88H to the FDC Command Register at 37ECH.
1771 FDC Command: 88H (10001000)Function Description
Bit 7Bit 6Bit 5Bit 4Bit 3Bit 2Bit 1Bit 0Summary of Bits
10001000Command=Read Sector
Bit 7-5: Read Command (100)
m=0: Single Record
b=1: IBM format sector
E=0: No head load delay (head already loaded from Seek)
Remainder: 00
42C7
PUSH DE D5
Save Register Pair DE (track/sector numbers) onto the stack for later use.
42C8
LD DE,37EFH 11 EF 37
Point Register Pair DE to the FDC Data Register at 37EFH. Data bytes will be read from this register as the FDC transfers the sector contents.
42CB
EX (SP),HL E3
Exchange top-of-stack with HL. This swaps: HL gets the saved DE value (track/sector), and the stack now holds 37ECH (the FDC status register address). First timing delay after Read Sector command.
42CC
EX (SP),HL E3
Exchange again. HL is restored to 37ECH (FDC status register), stack holds track/sector. Second timing delay.

Data Transfer Loop
The FDC is now reading the sector. The code polls the status register and transfers each byte as it becomes available. Bit 1 (DRQ, Data Request) indicates a byte is ready. Bit 0 (Busy) indicates the read is still in progress.

42CD
JUMP to 42DAH to enter the data transfer polling loop.
42CF
RRCA 0F
Rotate Register A (FDC status) right 1 bit. Bit 0 (Busy) moves to Carry. After this, original bit 1 (DRQ) is now in bit 0.
42D0
If the NO CARRY FLAG is set (bit 0/Busy was clear), the FDC is no longer busy. JUMP to 42DCH to check final status and exit. The sector read is complete.
42D2
LD A,(HL) 7E
Read the FDC Status Register at 37ECH into Register A for the next poll iteration.
42D3
BIT 1,A CB 4F
Test bit 1 (DRQ - Data Request) of the FDC status in Register A. If DRQ is set, a data byte is available in the Data Register. If DRQ is clear, no byte is ready yet.
42D5
If the Z FLAG is set (DRQ bit is clear, no data byte ready), LOOP BACK to 42CFH to check the Busy bit and continue polling.

Data Byte Ready
DRQ is set, meaning a byte is available in the FDC Data Register at 37EFH. Read it and store it in the sector buffer.

42D7
LD A,(DE) 1A
Fetch the data byte from the FDC Data Register at 37EFH (pointed to by Register Pair DE) into Register A.
42D8
LD (BC),A 02
Store the data byte from Register A into the sector buffer at the address pointed to by Register Pair BC.
42D9
INC BC 03
INCrement Register Pair BC by 1 to advance the buffer pointer to the next position.
42DA
LOOP BACK to 42D2H to poll for the next data byte.

Read Complete - Check Final Status
The FDC is no longer busy (Busy bit cleared). Check the status register for errors. The relevant error bits are: bit 2 (Lost Data), bit 3 (CRC Error), bit 4 (Record Not Found), bit 6 (Write Protected, not relevant for reads).

42DC
LD A,(HL) 7E
Read the final FDC Status Register value from 37ECH into Register A.
42DD
AND 5CH E6 5C
Mask Register A with 5CH (01011100 binary) to isolate the error bits: bit 2 (Lost Data), bit 3 (CRC Error), bit 4 (Record Not Found), bit 6 (Write Protected). If any of these bits are set, an error occurred.
1771 FDC Status Mask: 5CH (01011100)Function Description
Bit 7Bit 6Bit 5Bit 4Bit 3Bit 2Bit 1Bit 0Summary of Bits
01011100Type II Status Error Mask
Bit 6: Write Protected (not expected during read)
Bit 4: Record Not Found
Bit 3: CRC Error
Bit 2: Lost Data
Result: Z=no errors, NZ=error detected
42DF
POP DE D1
Restore Register Pair DE (track/sector numbers) from the stack.
42E0
RET Z C8
If the Z FLAG (Zero) is set (no error bits set in the AND mask), return to the caller with Z set indicating a successful read.
42E1
LD (HL),D0H 36 D0
Write D0H (Force Interrupt command) to the FDC Command Register at 37ECH. This aborts any in-progress FDC operation and resets the FDC to a clean state after an error.
1771 FDC Command: D0H (11010000)Function Description
Bit 7Bit 6Bit 5Bit 4Bit 3Bit 2Bit 1Bit 0Summary of Bits
11010000Command=Force Interrupt
Bit 7-4: Command Code (1101)
I3=0, I2=0, I1=0, I0=0: Terminate without interrupt (immediate reset)
42E3
RET C9
Return to the caller with NZ set (the AND 5CH result was non-zero, indicating a disk error). The Force Interrupt command has been issued to reset the FDC.

42E4H - Data: Screen Initialization and Error Messages

Three 03H-terminated message strings used by the boot code. The screen initialization message clears the display, and the two error messages are shown when the boot process fails.

42E4
DEFM 1CH 1FH 03H 1C 1F 03
Screen Initialization Message
Three bytes: 1CH = clear screen (Model I control code), 1FH = home cursor (move cursor to top-left position), 03H = message terminator. This is displayed by CALL 429EH at boot entry to prepare the screen.
42E7
DEFM 17H E8H "NO SYSTEM" 03H 17 E8 4E 4F 20 53 59 53 54 45 4D 03
No System Message
Screen position prefix (17H E8H) followed by the text "NO SYSTEM" and a 03H terminator. The 17H byte is a cursor positioning control code and E8H specifies the screen position. This message is displayed when the SYS0/SYS directory entry is not found at the expected location on Track 17.
42F3
DEFM 17H E8H "DISK ERROR" 03H 17 E8 44 49 53 4B 20 45 52 52 4F 52 03
Disk Error Message
Screen position prefix (17H E8H) followed by the text "DISK ERROR" and a 03H terminator. This message is displayed when the FDC sector read routine returns an error (CRC error, lost data, or record not found).

Disassembly - Double-Density Boot (Sector 1):

4200H - Double-Density Boot Entry Point

Lobo LX-80 double-density boot code entry point. This is an alternate boot sector that loads at the same 4200H address but is stored in Sector 1 of the BOOT/SYS file. It executes on Lobo LX-80 hardware, which uses IY-indexed I/O for FDC access instead of the Radio Shack memory-mapped interface. The initialization configures drive parameters from the IY-pointed hardware descriptor table and computes the sectors-per-track dynamically rather than using the hardcoded value of 10 from the single-density boot.

4200
NOP 00
No operation. Same dual-purpose /CMD comment header as the single-density boot (00H FEH).
4201
CP 11H FE 11
Compare Register A against 11H. The FEH byte is the /CMD comment record length byte (skip 254 bytes).
4203
DI F3
Disable all maskable interrupts for the boot sequence.
4204
LD SP,4160H 31 60 41
Set the stack pointer to 4160H. This is 128 bytes lower than the single-density boot's SP of 41E0H, allowing more stack space for the LX-80's deeper call chain through the IY-dispatched routines.
4207
Point Register Pair HL to the screen initialization message at 42E2H. In this sector, the init message is at 42E2H (2 bytes earlier than Sector 0's 42E4H) because the double-density boot code is slightly shorter, leaving more room at the end for data.
420A
GOSUB to the Display Message routine at 42B7H to clear the screen and home the cursor.
420D
LD A,(4202H) 3A 02 42
Fetch the directory track number from 4202H (contains 11H = Track 17, embedded in the CP 11H instruction at 4201H).
4210
LD (IY+09H),A FD 77 09
Store the directory track number (11H) to the Lobo LX-80 hardware descriptor at IY+09H. This sets the target track in the LX-80's drive parameter block, which the FDC dispatch routine at 42D1H will use when issuing seek commands.

Drive Configuration
The next instructions read and modify the LX-80 drive descriptor at IY+03H to configure the density and sector size for this boot operation. The descriptor byte at IY+03H contains drive flags; the code forces bits 0-1 to 11 (binary) while preserving the upper bits.

4213
LD A,(IY+03H) FD 7E 03
Fetch the drive configuration byte from IY+03H into Register A. This byte contains drive flags including density and sector size settings.
4216
AND FCH E6 FC
Mask Register A with FCH (11111100 binary) to clear bits 0 and 1, preserving the upper 6 bits of the drive configuration.
4218
OR 03H F6 03
OR Register A with 03H (00000011 binary) to force bits 0-1 to 11. This sets the sector size/density mode to the configuration needed for reading the directory sector.
421A
LD (IY+03H),A FD 77 03
Write the modified drive configuration byte back to IY+03H, updating the LX-80 descriptor with the correct density settings.

Sectors-Per-Track Computation
The following code extracts drive geometry information from the LX-80 descriptor at IY+07H and IY+08H, then computes the number of sectors per track using the multiply routine at 42D3H. This computed value is patched into the SUB instruction at 42A4H via self-modifying code, replacing the hardcoded sectors-per-track used in the single-density boot.

421D
LD A,(IY+07H) FD 7E 07
Fetch the drive geometry byte from IY+07H into Register A. This byte encodes track/head information for the current drive configuration.
4220
LD D,A 57
Save a copy of the geometry byte in Register D for the XOR operation below.
4221
AND 1FH E6 1F
Mask Register A with 1FH (00011111 binary) to isolate the lower 5 bits, extracting one component of the drive geometry (sectors per granule or granules per track).
4223
LD E,A 5F
Store the masked value into Register E as the multiplicand for the sectors-per-track calculation.
4224
INC E 1C
INCrement Register E by 1. The stored value is zero-based, so adding 1 converts it to a 1-based count.
4225
XOR D AA
XOR Register A (lower 5 bits of geometry) with Register D (full geometry byte). This isolates the upper bits (bits 5-7) that were masked off by the AND 1FH, yielding the complementary geometry component.
4226
RLCA 07
Rotate Register A left. First step of shifting the upper-bit geometry field down to the low bit positions.
4227
RLCA 07
Rotate left again.
4228
RLCA 07
Rotate left a third time. After three left rotates, the original bits 5-7 are now in bits 0-2.
4229
INC A 3C
INCrement Register A by 1 to convert from zero-based to 1-based count. Register A now holds one geometry factor and Register E holds the other.
422A
GOSUB to the 8-bit Multiply routine at 42D3H. This computes A = A x E, giving the total sectors per track by multiplying the two geometry factors together.
422D
LD (42A5H),A 32 A5 42
Self-Modifying Code
Store the computed sectors-per-track value into address 42A5H. This overwrites the operand byte of the SUB instruction at 42A4H. At assembly time, 42A5H contains 00H (SUB 00H, which does nothing). At runtime, it becomes SUB nn where nn is the actual sectors-per-track for this drive, enabling the Get Next Byte routine to correctly detect track boundary crossings on drives with any sector count.

Read Directory Sector
Now read the directory sector to locate SYS0/SYS, just as the single-density boot does.

4230
LD A,(4202H) 3A 02 42
Fetch the directory track number (11H = Track 17) again from the embedded CP operand at 4202H.
4233
LD D,A 57
Copy the track number to Register D.
4234
LD E,04H 1E 04
Load Register E with 04H (Sector 4), the directory sector containing SYS0/SYS.
4236
LD BC,5100H 01 00 51
Point Register Pair BC to the sector buffer at 5100H.
4239
GOSUB to the LX-80 Read Sector routine at 42C5H to read Track 17, Sector 4 into the buffer.
423C
LD A,(5100H) 3A 00 51
Fetch the first byte of the directory sector from the buffer.
423F
AND 10H E6 10
Mask with 10H to test bit 4 (valid entry flag).
4241
Point Register Pair HL to the "NO SYSTEM" message at 42E5H (preloaded in case the check fails).
4244
If the Z FLAG is set (bit 4 clear, no valid SYS0 entry), JUMP to 42B0H to display "NO SYSTEM" and halt.

SYS0 Located - Extract Starting Track/Sector
The directory entry is valid. Extract the starting track/sector from the directory entry and set up for /CMD parsing. The granule-to-sector conversion in this boot is more complex than the single-density version because it uses the dynamically computed sectors-per-granule from the drive parameters.

4246
EXX D9
Switch to alternate register set for disk position tracking.
4247
LD HL,(5116H) 2A 16 51
Fetch the 2-byte directory entry track/granule pointer from 5116H into Register Pair HL.
424A
LD A,H 7C
Copy the high byte (encoded granule/track data) to Register A.
424B
RLCA 07
Rotate left (first of three rotations to extract the granule number from the upper bits).
424C
RLCA 07
Rotate left (second rotation).
424D
RLCA 07
Rotate left (third rotation). The 3-bit granule number is now in bits 0-2.
424E
AND 07H E6 07
Mask with 07H to isolate the granule number (0-7).
4250
LD E,A 5F
Store the granule number in Register E' as the multiplicand.
4251
LD A,(IY+08H) FD 7E 08
Fetch the sectors-per-granule parameter from the LX-80 descriptor at IY+08H.
4254
AND 1FH E6 1F
Mask with 1FH to isolate the lower 5 bits (sectors-per-granule count, zero-based).
4256
INC A 3C
INCrement to convert from zero-based to 1-based (actual sectors per granule).
4257
GOSUB to the Multiply routine at 42D3H. Computes A = sectors_per_granule x granule_number, giving the starting sector offset within the track.
425A
LD E,A 5F
Store the computed starting sector number in Register E'.
425B
LD D,L 55
Copy the starting track number from Register L' (low byte of the directory pointer) to Register D'. D'=track, E'=sector for the first read.
425C
LD BC,51FFH 01 FF 51
Point Register Pair BC' to 51FFH (sector buffer end; first INC C will wrap to 00H and trigger an immediate sector read).
425F
EXX D9
Switch back to primary register set. Alternate registers hold disk state.

4260H - /CMD Load Module Parser (Double-Density)

Identical logic to the single-density /CMD parser at 4242H, but calls the double-density Get Next Byte routine at 4297H instead of 4279H.

4260
GOSUB to the DD Get Next Byte routine at 4297H to read the /CMD record type byte.
4263
DEC A 3D
DECrement Register A. If record type was 01H (data block), Z is set.
4264
If NZ (not data block), JUMP to 427DH to handle skip (02H) or transfer (03H) records.

Type 01H Data Block
Read length byte, 2-byte load address, then copy data bytes to memory.

4266
Read the data block length byte.
4269
LD B,A 47
Copy length to Register B as byte counter.
426A
Read load address low byte.
426D
LD L,A 6F
Store load address low byte in Register L.
426E
DEC B 05
DECrement byte counter for the address byte consumed.
426F
Read load address high byte.
4272
LD H,A 67
Store load address high byte in Register H. HL now points to the destination.
4273
DEC B 05
DECrement byte counter for the second address byte.
4274
If Z (counter zero), LOOP BACK to 4260H for next /CMD record.
4276
Read next data byte from the /CMD stream.
4279
LD (HL),A 77
Store the data byte to memory at the destination address (HL).
427A
INC HL 23
INCrement the destination pointer.
427B
LOOP BACK to 4273H to decrement counter and continue.
427D
DEC A 3D
DECrement A again. If original type was 02H (skip), Z is now set.
427E
If Z (skip record), JUMP to 428BH for transfer address handler. (Note: this is reversed from the label naming - the Z branch handles skip and the fall-through handles type 02H.)

Type 02H Skip Block
Read and discard the specified number of bytes.

4280
Read the skip block length byte.
4283
LD B,A 47
Copy length to Register B as counter.
4284
Read and discard one byte from the skip block.
4287
DECrement B and LOOP BACK to 4284H if not zero.
4289
LOOP BACK to 4260H for the next /CMD record.

Type 03H Transfer Record
Read the execution address and jump to SYS0.

428B
Read and discard the transfer record flag byte.
428E
Read the execution address low byte.
4291
LD L,A 6F
Store execution address low byte in Register L.
4292
Read the execution address high byte.
4295
LD H,A 67
Store execution address high byte in Register H.
4296
JP (HL) E9
JUMP to the SYS0 entry point. Boot complete.

4297H - Get Next Byte (Double-Density)

Double-density version of the Get Next Byte routine. Functionally identical to the single-density version at 4279H, but calls the LX-80 Read Sector routine at 42C5H and uses the self-modified SUB instruction at 42A4H for track boundary detection instead of the hardcoded SUB 0AH.

4297
EXX D9
Switch to alternate register set (BC'=buffer pointer, DE'=track/sector).
4298
INC C 0C
INCrement buffer pointer low byte. If C' wraps from FFH to 00H, Z is set and a new sector must be read.
4299
If NZ (buffer not exhausted), JUMP to 42AAH to return the next buffered byte.
429B
PUSH BC C5
Save buffer pointer onto the stack.
429C
GOSUB to the LX-80 Read Sector routine at 42C5H to read the next sector into the buffer.
429F
If NZ (disk read error), JUMP to 42ADH to display "DISK ERROR" and halt.
42A1
POP BC C1
Restore buffer pointer from the stack.
42A2
INC E 1C
INCrement sector number E' for the next read.
42A3
LD A,E 7B
Copy sector number to Register A for boundary check.
42A4
SUB 00H D6 00
Self-Modifying Code
SUBtract the sectors-per-track value from Register A. The operand byte at 42A5H was initialized to 00H at assembly time but is overwritten at runtime by the LD (42A5H),A instruction at 422DH with the actual sectors-per-track computed from the drive parameters. If the result is zero, the sector count has reached the track boundary.
42A6
If NZ (sector number has not reached the track boundary), JUMP to 42AAH to return the byte.
42A8
LD E,A 5F
Store 0 (result of SUB) into Register E', resetting the sector counter for the new track.
42A9
INC D 14
INCrement Register D' (track number) to advance to the next track.
42AA
LD A,(BC) 0A
Fetch the next byte from the sector buffer at (BC') into Register A.
42AB
EXX D9
Switch back to primary register set.
42AC
RET C9
Return to caller with the byte in Register A.

42ADH - Error Handler (Double-Density)

Error exit for the double-density boot. Displays the error message and halts.

42AD
Point Register Pair HL to the "DISK ERROR" message at 42F1H.
42B0
GOSUB to Display Message at 42B7H to show the error message (either "NO SYSTEM" or "DISK ERROR" depending on entry path).
42B3
GOSUB to ROM line input at 0040H to wait for a keypress.
42B6
HALT 76
HALT the CPU permanently (interrupts are disabled).

42B7H - Display Message Routine (Double-Density)

Identical function to the single-density Display Message at 429EH. Displays 03H-terminated string at (HL) via ROM CALL 0033H.

42B7
PUSH HL E5
Save message pointer onto the stack.
42B8
LD A,(HL) 7E
Fetch the next character from the message string.
42B9
CP 03H FE 03
Compare against 03H (message terminator).
42BB
If Z (terminator found), JUMP to 42C3H to return.
42BD
GOSUB to ROM character display routine to output the character in Register A.
42C0
INC HL 23
INCrement message pointer to the next character.
42C1
LOOP BACK to 42B8H to display the next character.
42C3
POP HL E1
Restore the original message pointer from the stack.
42C4
RET C9
Return to the caller.

42C5H - Read Sector Routine (Lobo LX-80)

Reads a sector using the Lobo LX-80 disk interface. Unlike the single-density boot which accesses the WD1771 directly via memory-mapped I/O, this routine abstracts the FDC access through a jump vector at (IY), allowing the same boot code to work with different disk controller hardware. On entry: D=track, E=sector, BC=buffer address.

42C5
PUSH HL E5
Save Register Pair HL onto the stack (preserve caller's context).
42C6
LD H,B 60
Copy the buffer address high byte from Register B to Register H.
42C7
LD L,C 69
Copy the buffer address low byte from Register C to Register L. Register Pair HL now contains the buffer address (originally in BC), since the LX-80 dispatch routine expects the buffer pointer in HL.
42C8
LD B,09H 06 09
Load Register B with 09H. This is the LX-80 function code for "Read Sector". The dispatch routine at (IY) uses B as the function selector.
42CA
LD C,00H 0E 00
Load Register C with 00H. This is a sub-parameter for the read function (flags or drive number).
42CC
GOSUB to the LX-80 FDC Dispatch at 42D1H, which executes JP (IY) to call the LX-80 ROM's sector read handler. On return: Z=success, NZ=error.
42CF
POP HL E1
Restore Register Pair HL from the stack.
42D0
RET C9
Return to caller with Z/NZ indicating success/error.

42D1H - Lobo LX-80 FDC Dispatch

Hardware abstraction layer for the Lobo LX-80 disk interface. Executes JP (IY) to call the LX-80 ROM routine whose address is stored in Register Pair IY. IY was set during LX-80 ROM initialization (before the boot sector was loaded) to point to the LX-80's FDC driver entry point.

42D1
JP (IY) FD E9
JUMP to the address held in Register Pair IY. This transfers control to the Lobo LX-80 ROM's FDC driver. The LX-80 ROM interprets the function code in Register B (09H = read sector) and the parameters in the other registers (HL=buffer, D=track, E=sector, C=flags) to perform the disk operation. The LX-80 driver returns to the instruction after the CALL 42D1H with Z/NZ status.

42D3H - 8-Bit Multiply Routine

Performs an 8-bit unsigned multiply: A = A x E. Uses the shift-and-add algorithm, processing 8 bits of the multiplicand (E) from MSB to LSB. On entry: A=multiplier, E=multiplicand. On return: A=product (low 8 bits). Registers B and E are destroyed; BC is preserved via PUSH/POP.

42D3
PUSH BC C5
Save Register Pair BC onto the stack.
42D4
LD D,A 57
Copy the multiplier from Register A to Register D. Register D holds the value to be added on each '1' bit of the multiplicand.
42D5
XOR A AF
Set Register A to zero. This is the running product accumulator.
42D6
LD B,08H 06 08
Load Register B with 08H (8 iterations, one for each bit of the multiplicand).

Shift-and-Add Multiply Loop
For each bit of the multiplicand (Register E), shift the accumulator (A) left by 1, then shift the multiplicand left by 1. If the bit shifted out of E (into Carry) was 1, add the multiplier (D) to the accumulator.

42D8
ADD A,A 87
Shift the accumulator (Register A) left by 1 bit (equivalent to multiplying the running product by 2).
42D9
SLA E CB 23
Shift Register E (multiplicand) left by 1 bit. The MSB of E is shifted into the Carry flag. This processes the multiplicand from the most significant bit to the least significant bit.
42DB
If the NO CARRY FLAG is set (the bit shifted out of E was 0), JUMP to 42DEH to skip the addition. A '0' bit means no contribution to the product for this position.
42DD
ADD A,D 82
ADD the multiplier (Register D) to the accumulator (Register A). A '1' bit in the multiplicand contributes the multiplier value at this bit position.
42DE
DECrement B and LOOP BACK to 42D8H if not zero. Process the next bit of the multiplicand.
42E0
POP BC C1
Restore Register Pair BC from the stack.
42E1
RET C9
Return to the caller with the 8-bit product in Register A.

42E2H - Data: Screen Initialization and Error Messages (Double-Density)

Message strings for the double-density boot. Functionally identical content to the single-density boot's messages, but positioned 2 bytes earlier in the sector (starting at 42E2H instead of 42E4H) because the double-density boot code is 2 bytes shorter.

42E2
DEFM 1CH 1FH 03H 1C 1F 03
Screen Initialization Message
1CH = clear screen, 1FH = home cursor, 03H = terminator.
42E5
DEFM 17H E8H "NO SYSTEM" 03H 17 E8 4E 4F 20 53 59 53 54 45 4D 03
No System Message
Screen position prefix (17H E8H) followed by "NO SYSTEM" and 03H terminator.
42F1
DEFM 17H E8H "DISK ERROR" 03H 17 E8 44 49 53 4B 20 45 52 52 4F 52 03
Disk Error Message
Screen position prefix (17H E8H) followed by "DISK ERROR" and 03H terminator.
42FE
DEFM FFH FFH FF FF
Sector Padding
Two FFH bytes fill the final 2 bytes of the 256-byte sector. These are unused padding.

Disk Structures (Sectors 2-4):

Sector 2 (File Offset 0200H-02FFH) - Directory and System Entry Points

This sector contains the system directory information including a jump vector table for SYS overlay loading, stub entry points, the disk serial/password, and Disk Geometry disk geometry parameters. On a VTOS disk, this sector resides on Track 17 and is part of the directory track structure inherited from TRSDOS 2.x.

0200
DEFB 40H 40
Directory Header Byte
The value 40H (64 decimal) at the start of this sector. In TRSDOS-compatible DOSes, this typically indicates the maximum number of directory entries or a directory configuration flag.
0201-021F
NOP x 31 00 x 31
31 bytes of zero padding between the directory header and the jump vector table.

SYS Overlay Jump Vector Table
Four entries, each consisting of a JP 4600H instruction followed by 5 bytes of parameters. These vectors are used by the DOS to load SYS overlay files. The JP target of 4600H indicates that SYS0 or its overlay dispatcher is expected at that address. Each entry's parameter bytes encode the drive number, track, sector, and load address for the corresponding SYS file.

0220
JP 4600H C3 00 46
Jump Vector 0
Jump to the SYS overlay dispatcher at 4600H. This is the primary SYS0 entry point.
0223
DEFB 86H 01H 13H 22H 09H 86 01 13 22 09
Vector 0 Parameters
These parameters tell the overlay loader where to find the SYS file on disk:
  • 86H = configuration flags (bit 7 set = active, bits 1-2 = sector count encoding)
  • 01H = drive 0
  • 13H = track 19 (13H = 19 decimal)
  • 22H 09H = load address or sector/granule mapping.
0228
DEFB 24H 11H 24 11
Vector 0 Extended Parameters
  • 24H = additional configuration
  • 11H = directory track (17 decimal) cross-reference
022A
JP 4600H C3 00 46
Jump Vector 1
Second overlay vector, also dispatching through 4600H.
022D
DEFB 03H 02H 11H 22H 09H 03 02 11 22 09
Vector 1 Parameters
  • 03H = flags (lower priority, 2 sectors)
  • 02H = drive 0 configuration
  • 11H = Track 17
  • 22H 09H = load parameters
0232
DEFB 24H 11H 24 11
Vector 1 Extended Parameters
  • 24H = additional configuration
  • 11H = directory track (17 decimal) cross-reference
0234
JP 4600H C3 00 46
Jump Vector 2
Third overlay vector.
0237
DEFB 03H 04H 00H 22H 09H 03 04 00 22 09
Vector 2 Parameters
  • 03H = flags
  • 04H = 4 sectors or drive 2
  • 00H = track 0 or flags
  • 22H 09H = load parameters
023C
DEFB 24H 11H 24 11
Vector 2 Extended Parameters
  • 24H = additional configuration
  • 11H = directory track (17 decimal) cross-reference
023E
JP 4600H C3 00 46
Jump Vector 3
Fourth overlay vector.
0241
DEFB 03H 08H 11H 22H 09H 03 08 11 22 09
Vector 3 Parameters
  • 03H = flags
  • 08H = 8 sectors or drive 3
  • 11H = Track 17
  • 22H 09H = load parameters
0246
DEFB 24H 11H 24 11
Vector 3 Extended Parameters
  • 24H = additional configuration
  • 11H = directory track (17 decimal) cross-reference

Stub Entry Points
Four RET instructions at fixed offsets provide stub entry points for DOS functions that are not implemented in the boot sector. These will be overwritten when SYS0 loads and installs the full DOS entry point table.

0248
RET C9
Stub Entry Point 0
Immediate return. Placeholder for a DOS function not yet loaded.
0249-0251
NOP x 9 00 x 9
Zero padding between stub entries.
0252
RET C9
Stub Entry Point 1
Immediate return placeholder.
0253-025B
NOP x 9 00 x 9
Zero padding.
025C
RET C9
Stub Entry Point 2
Immediate return placeholder.
025D-0265
NOP x 9 00 x 9
Zero padding.
0266
RET C9
Stub Entry Point 3
Immediate return placeholder.
0267-02BF
NOP x 89 00 x 89
89 bytes of zero padding filling the remainder of the entry point area.

Disk Serial Number and Disk Geometry Parameters
The disk identification and geometry parameters begin at offset 02C0H. The serial/password is an 8-character ASCII string. The Disk Parameters define the physical disk geometry for the FDC driver.

02C0
DEFM "QS81334Q" 51 53 38 31 33 33 34 51
Disk Serial/Password
8-character disk identifier string "QS81334Q". In VTOS and TRSDOS, this serves as both a serial number and an optional disk password. Each formatted disk receives a unique identifier.
02C8
DEFB EDH 00H 00H 00H ED 00 00 00
Disk Geometry Info Bytes 0-3
EDH = Disk Geometry configuration flags (bit 7 set = configured, other bits encode density, sides, step rate). Three zero bytes follow as reserved/unused parameters.
02CC
DEFB 01H 00H 40H 01H 01 00 40 01
Disk Geometry Info Bytes 4-7
  • 01H = number of sides (1 = single-sided)
  • 00H = reserved
  • 40H = number of tracks (64 decimal, though 35 or 40 is typical for 5.25" drives - this may encode differently)
  • 01H = additional geometry parameter
02D0
DEFB 35H 00H 22H 0DH 50H 16H 35 00 22 0D 50 16
Disk Geometry Bytes 8-13
  • 35H = 53 decimal (possibly sectors per track or track count in an alternate encoding)
  • 00H = reserved
  • 22H 0DH = directory track configuration (Track 17 at 11H*2 = 22H, with 0DH sectors)
  • 50H 16H = additional parameters (load address high byte 50H = 5000H region, 16H = sector offset)
02D6
DEFB 08H 00H 08 00
Disk Geometry Bytes 14-15
  • 08H = sectors per granule or granule configuration
  • 00H = reserved
02D8
DEFB 52H FFH FFH BFH 52 FF FF BF
Disk Geometry Bytes 16-19
  • 52H = additional disk parameter
  • FFH FFH = allocation bitmap seed (all bits set = all granules initially allocated on a freshly formatted disk)
  • BFH = system granule mask
02DC
DEFB C0H 3CH 45H 3CH C0 3C 45 3C
Disk Geometry Bytes 20-23
  • C0H = high memory boundary or configuration flags
  • 3CH = 60 decimal (possibly timing constant)
  • 45H 3CH = additional parameters.
The value 3CH (60) appears twice and may relate to the 60 Hz timing for motor spin-up delays.
02E0-02FF
NOP x 32 00 x 32
32 bytes of zero padding filling the remainder of the sector.

Sector 3 (File Offset 0300H-03FFH) - GAT and Disk Label

The Granule Allocation Table (GAT) tracks which granules on the disk are in use. Each byte represents one track's allocation status, with individual bits representing granules within that track. The disk label at the end of the sector identifies the disk name, format, and creation date.

Granule Allocation Table
The GAT occupies bytes 0300H-03CAH. Each byte corresponds to one track on the disk. Byte values: FFH = all granules on this track are allocated, FCH = track is locked/reserved by the system (cannot be allocated to user files), FDH = partially allocated with system reservation, 00H = all granules on this track are free. The GAT covers up to 203 tracks (0300H-03CAH), though a standard 35-track or 40-track disk uses only the first 35-40 bytes.

0300-0309
DEFB FFH FFH FFH FFH FFH FFH FFH FFH FFH FFH FF FF FF FF FF FF FF FF FF FF
Tracks 0-9
All granules allocated (FFH). These tracks contain system files (BOOT/SYS, SYS0-SYS9, etc.).
030A
DEFB FDH FD
Track 10
FDH = 11111101 binary. Most granules allocated, with one granule partially free (bit 1 clear). This track contains the tail end of system files with one granule partially used.
030B-030F
DEFB FCH FCH FCH FCH FCH FC FC FC FC FC
Tracks 11-15
All locked/system reserved (FCH). These tracks are reserved for system use and cannot be allocated to user files.
0310-0319
DEFB FFH FFH FFH FFH FFH FFH FFH FFH FFH FFH FF FF FF FF FF FF FF FF FF FF
Tracks 16-25
All granules fully allocated (FFH).
031A
DEFB FFH FF
Track 26
Fully allocated.
031B
DEFB FDH FD
Track 27
FDH = mostly allocated, one granule partially free.
031C-031F
DEFB FCH FCH FCH FCH FC FC FC FC
Tracks 28-31
Locked/system reserved (FCH).
0320-0322
DEFB FCH FCH FCH FC FC FC
Tracks 32-34
Locked/system reserved. This is the end of a standard 35-track disk.
0323-035F
DEFB FFH x 61 FF x 61
Tracks 35-95
All allocated (FFH). On a 40-track disk, tracks 35-39 contain additional data. Tracks beyond the physical disk capacity are marked FFH (allocated) to prevent the DOS from attempting to use non-existent tracks.
0360-0382
DEFB FCH x 35 FC x 35
Tracks 96-130
Locked/system reserved (FCH). These track entries extend beyond any physical disk and are pre-initialized to prevent allocation.
0383-03CA
DEFB FFH x 72 FF x 72
Tracks 131-202
All allocated (FFH). Remaining GAT entries, all marked as used.

Disk Label
The final 53 bytes of the sector (03CBH-03FFH) contain the disk label record. This identifies the disk to the operating system and stores the disk name, format type, and creation date.

03CB
DEFB 40H 00H 01H E0H 42H 40 00 01 E0 42
Disk Label Header
  • 40H = track count (64 decimal, or 40H as a flag indicating 40-track disk in VTOS encoding)
  • 00H 01H = additional disk parameters
  • E0H 42H = 42E0H, which is the boot code transfer address (the last executable address in the boot sector before the data strings begin)
03D0
DEFM "VTOS40:F08/21/80" 0DH 56 54 4F 53 34 30 3A 46 30 38 2F 32 31 2F 38 30 0D
Disk Name and Date
"VTOS40" = disk/OS name (VTOS version 4.0). ":F" = format identifier (F = standard format). "08/21/80" = creation date (August 21, 1980). 0DH = carriage return terminator. This is the date the VTOS 4.0 master disk was formatted.
03E1-03FF
DEFM " " 20 x 31
Label Padding
31 space characters (20H) pad the remainder of the disk label field to fill the sector.

Sector 4 (File Offset 0400H-04FFH) - HIT (Hash Index Table)

The Hash Index Table (HIT) provides a quick lookup mechanism for directory searches. Each byte in the HIT corresponds to a directory entry position and contains a hash value computed from the filename. The DOS can quickly scan the HIT to find candidate directory entries without reading the full directory. On this disk image, the HIT is entirely empty (all zeros), indicating either no files beyond the system files, or that the HIT has been cleared.

0400-04FF
NOP x 256 00 x 256
Empty HIT
All 256 bytes are zero, indicating no filename hash entries. On a freshly formatted VTOS disk, the HIT would be populated as system files are written. An empty HIT suggests this sector was either zeroed during the disk image creation process or represents the state before SYS0 populates it during the first boot.