Percom DoubleDOS 4.24 for the Model I - The Boot Sector Disassembled

Page Customization

Introduction/Summary

Percom DoubleDOS 4.24 Boot Sector Disassembly (Model I)

DoubleDOS (DBLDOS in Percom's manual) is the double density disk operating system Percom Data Company supplied with the Percom Doubler, the adapter that lets the Model I Expansion Interface read and write double density diskettes. Its files carry the text MODIFIED FOR DOUBLE DENSITY OPERATION BY JAMES W STUTSMAN - COPYRIGHT (c) 1980. The boot sector is cylinder 0, sector 0 of a DoubleDOS system diskette: 256 bytes, the only sector of the single density cylinder 0 that holds data (sectors 1-9 hold E5H). The directory has no BOOT/SYS entry; DIR/SYS is its first file.

When the computer is reset with a diskette in drive 0, the ROM reads this sector into 4200H-42FFH and jumps to 4200H. The boot sector switches the Doubler to double density with the command code FFH, seeks and reads the sectors of SYS0/SYS from cylinder 1, sector 0 on into the buffer 4D00H-4DFFH, and loads the records of SYS0/SYS as it goes: load blocks to their addresses, every other record passed over, until the transfer address record sends it to 4E00H. It does not read the directory: SYS0/SYS is always the first file of the diskette's first block of ten sectors (the directory's entry for SYS0/SYS, extent 00H 02H, says the same).

The boot sector is the same, byte for byte, on the DoubleDOS 4.20, 4.23 and 4.24 system diskettes.

How the Boot Sector Reads a Sector

StepWhat the code does
1Drive 0 is selected (01H to 37E1H, 4250H) before each read.
2The sector goes into the sector register and the cylinder into the data register in one store of Register Pair DE at 37EEH (4279H), then Seek 13H (stepping rate 3, no verify) runs to the end.
3Read Sector 88H is issued and every byte is taken by polling DATA REQUEST in the status register (4293H-42AAH), with three tests of the bit for each test of BUSY so that the loop keeps up with double density.
4The final status is masked with 9CH (not ready, record not found, CRC error, lost data); a failed read is tried once more (4275H), and a second failure shows DISK ERROR and stops (4265H).

Notes

The step to the next cylinder at 425AH-4261H sets the sector to 12H instead of 00H. It is never used: SYS0/SYS takes 13 sectors, sectors 0-12 of cylinder 1.

The interrupts stay off from the ROM's power up to the jump into SYS0/SYS, and the boot sector uses its own stack below 41FCH.

Variables

Memory and Hardware Outside the Boot Sector

Address
Size
Contents
37E1H
1 byte
Drive Select Latch
01H, drive 0, stored before each read (4252H).
37ECH
1 byte
Controller Command (write) and Status (read)
FFH: the Doubler's switch to double density (4200H); D0H: Force Interrupt (4202H, 42ADH); 13H: Seek (4280H); 88H: Read Sector (428AH). The status is polled for BUSY and DATA REQUEST and masked with 9CH at the end.
37EDH
1 byte
Track Register
Set to 00H (420BH).
37EEH
1 byte
Sector Register
The sector to read (Register E, 4279H).
37EFH
1 byte
Data Register
The cylinder for the Seek (Register D, 4279H), then each byte read (4299H).
41F4H-41FBH
8 bytes
Stack
The Stack Pointer is set to 41FCH (4204H); the deepest use is the loader's call, the call of 4275H, the call of 4279H and its two pushes.
4D00H-4DFFH
256 bytes
Sector Buffer
Each sector of SYS0/SYS in turn (Register Pair BC of the alternate set, 4211H).

Registers Kept Across the Byte Fetch

Register
Size
Contents
BC'
2 bytes
Buffer Pointer
4DFFH at the start; Register C wraps to 00H when a new sector is needed (424DH).
DE'
2 bytes
Disk Address
Register D the cylinder, Register E the next sector; 0100H at the start (420EH).

Outside Routines

AddressUse
01C9HROM: Clear the Screen
4207H.
0033HROM: Display a Character
Register A at the cursor (4269H).
0040HROM: Line Input
Waits for ENTER after DISK ERROR (4271H).
4E00HSYS0/SYS Transfer Address
Reached by JP (HL) at 424BH.

Disassembly

4200H - Switch the Doubler to Double Density and Set Up

The ROM's power up routine reads cylinder 0, sector 0 of drive 0 into 4200H-42FFH in single density and jumps to 4200H (06C9H). It leaves Register Pair HL pointing at the controller's command and status register 37ECH, Register Pair DE at the data register 37EFH and Register Pair BC at 4200H, with the interrupts off and the head on cylinder 0.

4200
LD (HL),FFH 36 FF
Store FFH through Register Pair HL into the controller's command register at 37ECH (Register Pair HL still holds 37ECH from the ROM's read at 06A4H). The Percom Doubler takes the command code FFH as the order to switch to double density, the recording mode of every cylinder of the diskette from cylinder 1 on.
4202
LD (HL),D0H 36 D0
Store D0H through Register Pair HL into the command register at 37ECH: Force Interrupt with no condition, which ends the FFH just written so that the controller is idle and ready for the Seek at 4280H.
4204
LD SP,41FCH 31 FC 41
Load the Stack Pointer with 41FCH. The boot sector's calls and pushes use the stack below 41FCH, in memory that no load block of SYS0/SYS fills.
4207
Call the ROM's clear screen routine at 01C9H: the screen is cleared and the cursor goes to the top left corner while SYS0/SYS loads.
420A
XOR A AF
Clear Register A to 00H, the value for the track register stored at 420BH.
420B
LD (37EDH),A 32 ED 37
Store Register A (00H) in the controller's track register at 37EDH. The head is on cylinder 0, where the ROM's Restore (command 03H at 06AAH) left it, so the controller now counts from cylinder 0 for the Seek of 4280H.
420E
LD DE,0100H 11 00 01
Load Register Pair DE with 0100H: Register D is cylinder 1 and Register E is sector 0, where SYS0/SYS starts. The boot sector does not read the directory: SYS0/SYS always begins at the first sector of cylinder 1, the first sector of the diskette's first block of ten sectors.
4211
LD BC,4DFFH 01 FF 4D
Load Register Pair BC with 4DFFH, the buffer pointer of the byte fetch at 424CH. Register C is FFH so that the first INC C at 424DH wraps to 00H and reads the first sector into 4D00H-4DFFH.
4214
EXX D9
Exchange Register Pairs BC, DE and HL with the alternate set. The byte fetch at 424CH keeps the buffer pointer in Register Pair BC and the cylinder and sector in Register Pair DE of the alternate set; the loader uses the main set.

4215H - Load SYS0/SYS Record by Record

SYS0/SYS is a load module: a record type byte, a length byte, then the record. Type 01H is a load block (two address bytes and length minus 2 data bytes), type 02H the transfer address, and every other type (05H the name, 1FH the copyright text) is passed over by its length.

4215
Call 424CH to fetch the next byte of SYS0/SYS into Register A: the record type byte.
4218
DEC A 3D
Decrement Register A, the record type. A load block (type 01H) leaves 00H and the Z FLAG set.
4219
If the NZ FLAG is set (the record is not a load block), jump to 4232H to test for the transfer address and pass over any other record.
421B
Call 424CH to fetch the length byte of the load block into Register A: the count of the address bytes and the data bytes that follow.
421E
LD B,A 47
Copy the length from Register A into Register B, the count of bytes left in this record.
421F
Call 424CH to fetch the low byte of the load address into Register A.
4222
LD L,A 6F
Copy the low byte of the load address from Register A into Register L.
4223
DEC B 05
Decrement Register B, the bytes left in the record, for the low address byte just fetched.
4224
Call 424CH to fetch the high byte of the load address into Register A.
4227
LD H,A 67
Copy the high byte of the load address from Register A into Register H. Register Pair HL is now the address where the record's data goes.
4228
DEC B 05
Decrement Register B, the bytes left in the record, for the address byte (on later passes, for the data byte just stored at 422EH).
4229
If the Z FLAG is set (Register B is 00H, the record is finished), jump back to 4215H for the next record's type byte. A length byte of 02H gives a block of no data.
422B
Call 424CH to fetch the next data byte of the load block into Register A.
422E
LD (HL),A 77
Store the data byte from Register A at the load address in Register Pair HL.
422F
INC HL 23
Increment Register Pair HL to the next load address.
4230
Jump back to 4228H to count the byte just stored and fetch the next one.
4232
DEC A 3D
Decrement Register A again: the record type minus 2. A transfer address record (type 02H) leaves 00H and the Z FLAG set.
4233
If the Z FLAG is set (a transfer address record), jump to 4240H to take the address and start SYS0/SYS.
4235
Call 424CH to fetch the length byte of the record that is passed over (the name record 05H, the copyright record 1FH) into Register A.
4238
LD B,A 47
Copy the length from Register A into Register B, the number of bytes to pass over.
4239
Call 424CH to fetch the next byte of the record into Register A; the byte is not used.
423C
Decrement Register B and loop back to 4239H until all the bytes of the record have been passed over. A length byte of 00H passes over 256 bytes.
423E
Jump back to 4215H for the type byte of the next record.

4240H - Jump to the Transfer Address of SYS0/SYS

The transfer address record is 02H, a length byte (02H) and the address, 4E00H in SYS0/SYS.

4240
Call 424CH to fetch the length byte of the transfer address record into Register A; its value is not used.
4243
Call 424CH to fetch the low byte of the transfer address into Register A.
4246
LD L,A 6F
Copy the low byte of the transfer address from Register A into Register L.
4247
Call 424CH to fetch the high byte of the transfer address into Register A.
424A
LD H,A 67
Copy the high byte of the transfer address from Register A into Register H. Register Pair HL is now 4E00H, the transfer address of SYS0/SYS.
424B
JP (HL) E9
Jump to the address in Register Pair HL, the start of SYS0/SYS at 4E00H. The boot sector's work is done; the Stack Pointer still holds 41FCH and the interrupts are still off.

424CH - Fetch the Next Byte of SYS0/SYS

Returns the next byte of SYS0/SYS in Register A. The buffer is 4D00H-4DFFH; when it is used up, the next sector is read into it. Register Pair BC of the alternate set is the buffer pointer, Register Pair DE of the alternate set the cylinder and sector of the next sector.

424C
EXX D9
Exchange Register Pairs BC, DE and HL with the alternate set: Register Pair BC is now the buffer pointer and Register Pair DE the cylinder (Register D) and sector (Register E) of the next sector to read.
424D
INC C 0C
Increment Register C, the low byte of the buffer pointer in Register Pair BC. Register C wraps to 00H (Z FLAG set) when the whole buffer 4D00H-4DFFH has been used.
424E
If the NZ FLAG is set (bytes are left in the buffer), jump to 4262H to take the byte at Register Pair BC.
4250
LD A,01H 3E 01
Load Register A with 01H, the select bit of drive 0.
4252
LD (37E1H),A 32 E1 37
Store Register A (01H) in the drive select latch at 37E1H: drive 0 is selected and its motor kept turning for the read.
4255
Call 4275H to read cylinder Register D, sector Register E into the buffer at Register Pair BC (4D00H), with a second try when the first fails. The Z FLAG returns set when the sector was read without error.
4258
If the NZ FLAG is set (both tries failed), jump to 4265H to display DISK ERROR and stop.
425A
INC E 1C
Increment Register E to the next sector of cylinder Register D.
425B
LD A,E 7B
Copy the next sector number from Register E into Register A for the test at 425CH.
425C
CP 12H FE 12
Compare Register A, the next sector, with 12H: a cylinder has the 18 sectors 0-17 in double density, so 12H is past its last sector.
425E
If the NZ FLAG is set (the sector is still on this cylinder), jump to 4262H to take the first byte of the sector just read.
4260
LD E,A 5F
Store Register A in Register E. Register A holds 12H here, so Register E stays 12H, not 00H: the step to the next cylinder would ask for the missing sector 18 and fail. SYS0/SYS takes 13 sectors, all on cylinder 1, so this code is never reached.
4261
INC D 14
Increment Register D to the next cylinder, the step that the bad sector number of 4260H makes useless.
4262
LD A,(BC) 0A
Load Register A with the byte of SYS0/SYS at the buffer pointer in Register Pair BC.
4263
EXX D9
Exchange Register Pairs BC, DE and HL with the alternate set again: the loader's load address in Register Pair HL and its count in Register B come back, the buffer pointer and the disk address are kept in the alternate set.
4264
RET C9
Return to the loader with the byte of SYS0/SYS in Register A.

4265H - Display DISK ERROR and Stop

Reached from 4258H when a sector of SYS0/SYS cannot be read in two tries.

4265
LD HL,42B4H 21 B4 42
Load Register Pair HL with 42B4H, the message 17H, E8H, DISK ERROR, 0DH.
4268
LD A,(HL) 7E
Load Register A with the character of the message at Register Pair HL.
4269
Call the ROM's display routine at 0033H to show the character in Register A at the cursor (17H switches the screen to 32 characters a line, E8H gives 40 spaces).
426C
INC HL 23
Increment Register Pair HL to the next character of the message.
426D
CP 0DH FE 0D
Compare Register A, the character just shown, with 0DH, the carriage return that ends the message.
426F
If the NZ FLAG is set (not the end of the message), jump back to 4268H for the next character.
4271
Call the ROM's line input routine at 0040H, which waits for a line from the keyboard ended by ENTER. Register Pair HL (42C1H, just past the message) is its buffer and Register B (4DH from the buffer pointer popped at 42B1H) its length; the line is not used.
4274
HALT 76
Stop the processor. The interrupts are off since the ROM's power up, so only the RESET button starts the computer again.

4275H - Read a Sector, Two Tries

Reads cylinder Register D, sector Register E into the buffer at Register Pair BC. Returns the Z FLAG set when the read succeeded.

4275
Call 4279H for the first try at reading cylinder Register D, sector Register E into the buffer at Register Pair BC.
4278
RET Z C8
If the Z FLAG is set (the first try read the sector), return to the caller at 4258H. Otherwise the code runs on into 4279H for the second try, which returns to the caller itself.

4279H - Seek and Read One Sector

Seeks to cylinder Register D and reads sector Register E into the buffer at Register Pair BC, polling the controller's status register for each byte. Returns Register A the error bits of the status and the Z FLAG set when there are none.

4279
LD (37EEH),DE ED 53 EE 37
Store Register Pair DE at 37EEH: Register E, the sector, goes into the controller's sector register 37EEH and Register D, the cylinder, into its data register 37EFH, the destination of the Seek.
427D
LD HL,37ECH 21 EC 37
Load Register Pair HL with 37ECH, the controller's command register (write) and status register (read).
4280
LD (HL),13H 36 13
Store 13H through Register Pair HL into the command register at 37ECH: Seek (bits 7-4 0001) to the cylinder in the data register, without head load or verify, at stepping rate 3 (bits 1-0), the slowest rate.
4282
BIT 0,(HL) CB 46
Test bit 0 of the status register at Register Pair HL (37ECH), the BUSY bit.
4284
If the Z FLAG is set (BUSY is not yet set), jump back to 4282H to wait until the controller has taken the Seek command.
4286
BIT 0,(HL) CB 46
Test bit 0 of the status register at Register Pair HL (37ECH), the BUSY bit.
4288
If the NZ FLAG is set (the Seek is still running), jump back to 4286H to wait until the head is on the cylinder.
428A
LD (HL),88H 36 88
Store 88H through Register Pair HL into the command register at 37ECH: Read Sector (bits 7-5 100), one sector (bit 4 clear), with no settling delay (bit 2 clear) and no side compare (bit 1 clear).
428C
PUSH DE D5
Save Register Pair DE (the cylinder and sector) on the stack; Register Pair DE is used for the data register address until 42B2H.
428D
LD DE,37EFH 11 EF 37
Load Register Pair DE with 37EFH, the controller's data register, where each byte read is taken.
4290
PUSH BC C5
Save Register Pair BC, the buffer pointer, on the stack.
4291
POP BC C1
Take the buffer pointer back off the stack into Register Pair BC. Together with the PUSH BC at 4292H this gives the controller time to set BUSY for the Read Sector command before the status is tested at 4293H.
4292
PUSH BC C5
Save Register Pair BC, the buffer pointer, on the stack again, for the POP BC at 42B1H, which hands the pointer back to the byte fetch unchanged.
4293
LD A,(HL) 7E
Load Register A with the status register at Register Pair HL (37ECH).
4294
AND 03H E6 03
Mask Register A with 03H to keep bit 1 (DATA REQUEST) and bit 0 (BUSY).
4296
If the parity is odd (exactly one of the two bits set: BUSY while the sector is still being found), jump back to 4293H. The loop ends with both bits set (the first byte is ready) or both clear (the command has ended without data).
4299
LD A,(DE) 1A
Load Register A with the data byte from the data register at Register Pair DE (37EFH).
429A
LD (BC),A 02
Store the byte from Register A in the buffer at Register Pair BC.
429B
INC BC 03
Increment Register Pair BC to the next byte of the buffer.
429C
BIT 1,(HL) CB 4E
Test bit 1 of the status register at Register Pair HL (37ECH), DATA REQUEST.
429E
If the NZ FLAG is set (the next byte is ready), jump back to 4299H to take it.
42A0
BIT 1,(HL) CB 4E
Test bit 1 of the status register at Register Pair HL (37ECH) again, DATA REQUEST; the three tests in a row keep the loop short enough for a double density byte.
42A2
If the NZ FLAG is set (the next byte is ready), jump back to 4299H to take it.
42A4
BIT 1,(HL) CB 4E
Test bit 1 of the status register at Register Pair HL (37ECH) a third time, DATA REQUEST.
42A6
If the NZ FLAG is set (the next byte is ready), jump back to 4299H to take it.
42A8
BIT 0,(HL) CB 46
Test bit 0 of the status register at Register Pair HL (37ECH), the BUSY bit.
42AA
If the NZ FLAG is set (the Read Sector command is still running), jump back to 429CH to wait for the next DATA REQUEST.
42AC
LD A,(HL) 7E
Load Register A with the final status of the Read Sector command from the status register at Register Pair HL (37ECH).
42AD
LD (HL),D0H 36 D0
Store D0H through Register Pair HL into the command register at 37ECH: Force Interrupt with no condition, which leaves the controller idle.
42AF
AND 9CH E6 9C
Mask Register A, the status, with 9CH to keep bit 7 (NOT READY), bit 4 (RECORD NOT FOUND), bit 3 (CRC ERROR) and bit 2 (LOST DATA). The Z FLAG is set when the sector was read without error.
42B1
POP BC C1
Restore Register Pair BC, the buffer pointer as it was before the read, from the stack.
42B2
POP DE D1
Restore Register Pair DE, the cylinder in Register D and the sector in Register E, from the stack.
42B3
RET C9
Return with Register A the error bits and the Z FLAG set when there are none.

42B4H - The DISK ERROR Message and the Copyright Text

The rest of the sector is data.

42B4
DEFB 17H 17
The first character of the message, 17H: the ROM's display routine switches the screen to 32 characters a line, so the message is shown in large letters.
42B5
DEFB E8H E8
The second character, E8H: a space compression code (C0H plus 40), which the ROM's display routine shows as 40 spaces, bringing the message to the middle of the screen.
42B6-42BF
DEFM "DISK ERROR" 44 49 53 4B 20 45 52 52 4F 52
The ten characters DISK ERROR, displayed by 4268H-426FH when a sector of SYS0/SYS cannot be read in two tries.
42C0
DEFB 0DH 0D
The carriage return 0DH that ends the message: 426DH stops displaying after it.
42C1-42CF
DEFB 00H x 15 00 x 15
Fifteen bytes of 00H that no instruction reads. They are the start of the line input buffer of 4271H, which the ROM fills only with what is typed before ENTER.
42D0-42FF
DEFM "COPYRIGHT (c) 1980 BY PERCOM DATA COMPANY - JWS " 43 4F 50 59 52 49 47 48 54 20 28 63 29 20 31 39 38 30 20 42 59 20 50 45 52 43 4F 4D 20 44 41 54 41 20 43 4F 4D 50 41 4E 59 20 2D 20 4A 57 53 20
The 48 characters COPYRIGHT (c) 1980 BY PERCOM DATA COMPANY - JWS, ending in a space; JWS is James W. Stutsman, who adapted the system for double density. No instruction reads the text.