This is a disassembly of TRSDOS v2.3’s SYS0/SYS file. SYS0/SYS resides from 4300-4D00H

TRSDOS does quite a bit of sector work by using a buffer and then writing out the buffer when done. The buffer is usually set up being pointed to by the IX register, as that is 1 of 2 registers upon which math can be done. In the case of the buffered sector:

  • IX+00H = special DCB code area
  • IX+01H = special codes. Lowest 3 bits are the permission flags. Bit 4 signals that the buffer has been updated / is available for use. Bit 5 forces a read on the next file access. Bit 6 signifies the need for a POSITION call. Bit 7 specifies physical or logical I/O.
  • IX+02H = Reserved / Not Used
  • IX+03H = the LSB of the DCB Sector Buffer Address
  • IX+04H = the MSB of the DCB Sector Buffer Address
  • IX+05H = the next record address -OR – current byte offset in sector buffer to the current record
  • IX+06H = the drive number
  • IX+07H = the DIRECTORY SECTOR OFFSET (i.e., directory sector number with the HIT Index) or the OVERFLOW pointer
  • IX+08H = EOF offset held in the DCB
  • IX+09H = record size in bytes
  • IX+0AH = the LSB of the next sector number
  • IX+0BH = the MSB of the next sector number
  • IX+0CH = the LSB of the EOF sector held in the DCB -or- The number of records
  • IX+0DH = the MSB of the EOF sector held in the DCB -or- The number of records
  • IX+0EH = the track number of the lst GAP
  • IX+0FH = Granule count

The reason the below starts at 400C is that the ROM itself fills 4000H, 4003H, 4006H, and 4009H with JUMPs, but puts RETs at 400C and 400F, and an EI at 4012H.

400C
Jump point to the system overlay loaders
400F
Jump point to make the overlay call to load DEBUG
4012
Jump point to interrupt service routine
4015
DEFS 8
Keyboard DCB. This value is loaded by Level II
401D
DEFS 8
Video DCB. This value is loaded by Level II
4025
DEFS 8
Printer DCB. This value is loaded by Level II
402D
JUMP to 4400H to make the overla call to load SYS1/SYS with a Request Code of 1
4030
LD A,03H
Code for SYS1/SYS with a Request Code of 2
4032
RST 28H
Load and Execute the overlay selected by Register A
4033
JUMP to 44BBH. This enters the driver. LEVEL II calls this

Local Storage Area

4036
DEFS 7
Table to hold the keyboard row history
403D
DEFB 00
Video display byte used by Level II
403E
DEFB 21H
Reserved
403F
DEFB 01H
Reserved
4040
DEFB 00
Clock Interrupt Index
4041
DEFB 00
Seconds
4042
DEFB 00
Minutes
4043
DEFB 00
Hours
4047
DEFW 0000
Utility program load address
4049
DEFW 0000
Memory size
404B
DEFB 00
Current interrupt byte status
404C
DEFB 00
Interrupt subroutine mask

Interrupt task list

404D
DEFW 4537H
Address of interrupt service routine for interrupt bit 0
404F
DEFW 4537H
Address of interrupt service routine for interrupt bit 1
4051
DEFW 4537H
Address of interrupt service routine for interrupt bit 2
4053
DEFW 4537H
Address of interrupt service routine for interrupt bit 3
4055
DEFW 4537H
Address of interrupt service routine for interrupt bit 4
4057
DEFW 4537H
Address of interrupt service routine for interrupt bit 5
4059
DEFW 4537H
Address of interrupt service routine for interrupt bit 6
405B
DEFW 4537H
Address of interrupt service routine for interrupt bit 7

System Sector Buffer

4200
DEFS 256
System Sector Buffer Area

Track History Table

4300
DEFB 11H
Last Track Addressed on Drive 0
4301
DEFB 11H
Last Track Addressed on Drive 1
4302
DEFB 11H
Last Track Addressed on Drive 2
4303
DEFB 11H
Last Track Addressed on Drive 3

Directory Track List Table

4304
DEFB 11H
Directory Track for Drive 0
4305
DEFB 11H
Directory Track for Drive 1
4306
DEFB 11H
Directory Track for Drive 2
4307
DEFB 11H
Directory Track for Drive 3

Unit Flags and Addresses

4308
DEFB 00H
Last Disk Drive Selected
4309
DEFB 01H
Unit mask for the last drive selected
430A
DEFB 00H
DCB/Buffer address, current operation
430B
DEFB 00H
430C
DEFB 00H
DCB address for current operation
430D
DEFB 00H
430E
DEFB 02H
Last Overlay Load Request
430F
DEFB 00H
DEBUG Load Flag
4310
DEFB 00H
Reserved
4311
DEFB 00H
Reserved
4312
JUMP to 4BA0H to return with zero status. This will be JP 5BA4H if disk basic is loaded

DEBUG related

4315
DEFB 00H
This byte will be either 0 if DEBUG is off, or C3 if DEBUG is on
4316
DEFW 00H
This byte will be the address of the nucleus to load DEBUG
4318
-43BF
Command Line Buffer

4398H – Routine to wait for Disk Controller to Become NOT Busy

4398
LD A,(37ECH)
Get the controller status from (37ECH) and put it into Register A for testing/processing
439B
BIT 0,A
Test Bit 0 of Register A. This will be either Z if not busy or NZ if busy.
439D
RET Z
If the Z flag is set then the controller is not busy, so RETURN to the caller
439E
If we are here, then NZ was set and the controller is busy so loop back to 4398 until it isn’t busy.
43C0
Device List
43D8
Keyboard Driver
4400
Overlay call Vector for SYS1/SYS Request Code 1
4405
Overlay call Vector for SYS1/SYS Request Code 3
4409
Overlay call Vector for SYS4/SYS
440D
Overlay call Vector for SYS5/SYS
4410
Add an address to the Interrupt Task List Vector
4413
Remove an address from the Interrupt Task List Vector
4416
The INIT Vector
4419
The OPEN Vector
4420
The INIT Vector
4424
The OPEN Vector
4428
The CLOSE Vector
442C
The KILL Vector
4430
The LOAD FILE Vector
4433
The LOAD FILE IN SYSTEM DCB Vector
4436
The READ Vector
4439
The WRITE Vector
443C
The VERIFY Vector
443F
The REWIND Vector
4442
The POSITION Vector
4445
The BACKSPACE Vector
444B
The SKIP TO THE EOF Vector
4467
The SEND MESSAGE TO VIDEO Vector
446A
The SEND MESSAGE TO PRINTER Vector
446D
The DISPLAY CLOCK ON VIDEO Vector
4470
The DISPLAY DATE ON VIDEO Vector

4473H – Routine Process Overlay Load Requests.

4473
LD A,0D3H
Put the overlay request code for SYS1/SYS with Option 50 Selected Into Register A. This option JUMPs to 4FC0H to add a suffix to the file name
4475
RST 28H
Load and Execute the overlay selected by Register A
4476
LD A,0E3H
Put the overlay request code for SYS1/SYS with Option 60 Selected Into Register A. This option JUMPs to 507DH to parse a command list
4478
RST 28H
Load and Execute the overlay selected by Register A

4480H – System DCB – 32 Bytes

4480
DEFM ‘NAMENAME/EXT.PASSWORD:N’

44A0H – System DCB – 16 Bytes

44A0
DEFB 80H
Open Flag
44A1
DEFB 00H
Access Flags
44A2
DEFB 00H
Reserved
44A4
DEFB 4D00H
Sector Buffer Address
44A5
DEFB 00H
Offset to next record
44A6
DEFB 00H
Drive Number
44A7
DEFB 00H
Overflow Pointer
44A8
DEFB 00H
EOF Byte Address
44A9
DEFB 00H
Record Size
44AA
DEFB 0000H
Next Record Number
44AC
DEFB FFFFH
Number of Records
44AE
DEFB 00H
Track Number
44AF
DEFB 00H
Starting Number of Granules and NUmber of Granules

44B0H – Load Error Message Processor

44B0
PUSH AF
Save the contents of Register Pair AF (The Error Code) to the stack
44B1
LD A,86H
Put the overlay request code for SYS4/SYS into Register A
44B3
RST 28H
Load and Execute the overlay selected by Register A

44B4H – Load DEBUG

44B4
PUSH AF
Save the contents of Register Pair AF to the stack
44B5
LD A,87H
Put the overlay request code for SYS5/SYS into Register A
44B7
RST 28H
Load and Execute the overlay selected by Register A

44B8H – Enter Driver. This is called From LEVEL II ROM

44B8
PUSH HL
Save the contents of Register Pair HL (the DCB Address) to the stack
44B9
POP IX
POP the DCB Address into Register IX
44BB
LD L,(IX+01H)
Put the LSB of the driver address into Register L
44BE
LD H,(IX+02H)
Put the MSB of the driver address into Register H
44C1
LD A,(IX+00H)
Put the special DCB code into Register A
44C4
CP 10H
Compare the device code against 10H. This will return Z if A is 10H (meaning that this DCB is linked to another file), NZ if its not 10H, NC if A is equal to or greater than 10H, a C if A is less than 10H.
44C6
If the Z Flag is SET (meaning that this DCB is linked to another file), then JUMP to 44B8H
44C8
If NC is set then A is equal to or greater than 10H, meaning that this is a disk DCB, so JUMP to 47AEH
44CB
LD A,B
Put Register B (which is an OPCODE) into Register A for processing/testing
44CC
CP 02H
Compare the OPCODE against 02H. This will return Z if A is 02H (meaning that this DCB is linked to another file), NZ if its not 02H, NC if A is equal to or greater than 02H, a C if A is less than 02H.
44CE
Run the applicable driver

44CFH – Send a Message to the Video Display

44CF
PUSH HL
Save the contents of Register Pair HL (the beginning of the message to be displayed) to the stack
44D0
LD A,(HL)
Put the character pointed to by HL into Register A
44D1
CP 03H
Check Register A to see if a terminator (03H) is read. This will return Z if A is a terminator and NZ otherwise.
44D3
If the Z Flag is SET then we have a terminator, so JUMP to 44DDH
44D5
GOSUB to 0033H in the ROM to send the character held in Register A to the current device
44D8
INC HL
Bump Register Pair HL by 1 to move to the next character to be displayed
44D9
CP 0DH
Check Register A to see if a Carriage Return/Terminator (0DH) is read. This will return Z if A is a terminator and NZ otherwise.
44DB
If a carriage return/terminator was not read, LOOP back to 44D0H to display the character and keep going
44DD
POP HL
If we are here, then a CARRIAGE RETURN/TERMINATOR was read, so restore the beginning of the message to be displayed to Register Pair HL
44DE
RET
RETURN

44DFH – Send a Message to the Printer

44DF
PUSH HL
Save the contents of Register Pair HL (the beginning of the message to be displayed) to the stack
44E0
LD A,(HL)
Put the character pointed to by HL into Register A
44E1
CP 03H
Check Register A to see if a terminator (03H) is read. This will return Z if A is a terminator and NZ otherwise.
44E3
If the Z Flag is SET then we have a terminator, so JUMP to 44EDH
44E5
GOSUB to 003BH in the ROM to send the character held in Register A to the printer
44E8
INC HL
Bump Register Pair HL by 1 to move to the next character to be displayed
44E9
CP 0DH
Check Register A to see if a Carriage Return/Terminator (0DH) is read. This will return Z if A is a terminator and NZ otherwise.
44EB
If a carriage return/terminator was not read, LOOP back to 44D0H to display the character and keep going
44ED
POP HL
If we are here, then a CARRIAGE RETURN/TERMINATOR was read, so restore the beginning of the message to be displayed to Register Pair HL
44EE
RET
RETURN
44EF
NOP
Empty

4500H – Interrupt Task List Address. The subroutine at 4560H executes the task (based on the interrupt processor) and 4040H holds the counter used to select the next task.

4500
DEFW 45A3H
Address for TASK 0. This task is executed once every 8 interrupts (i.e., 200 milliseconds)
4502
DEFW 45A3H
Address for TASK 1. This task is executed once every 8 interrupts (i.e., 200 milliseconds)
4504
DEFW 45A3H
Address for TASK 2. This task is executed once every 8 interrupts (i.e., 200 milliseconds)
4506
DEFW 45A3H
Address for TASK 3. This task is executed once every 8 interrupts (i.e., 200 milliseconds)
4508
DEFW 45A3H
Address for TASK 4. This task is executed once every 8 interrupts (i.e., 200 milliseconds)
450A
DEFW 45A3H
Address for TASK 5. This task is executed once every 8 interrupts (i.e., 200 milliseconds)
450C
DEFW 45A3H
Address for TASK 6. This task is executed once every 8 interrupts (i.e., 200 milliseconds)
450E
DEFW 45A3H
Address for TASK 7. This task is executed once every 8 interrupts (i.e., 200 milliseconds)
4510
DEFW 45A3H
Address for TASK 8. This task is executed unconditionally once every clock interrupt (i.e., 25 milliseconds)
4512
DEFW 45A3H
Address for TASK 9. This task is executed unconditionally once every clock interrupt (i.e., 25 milliseconds)
4514
DEFW 45A3H
Address for TASK 10. This task is executed unconditionally once every clock interrupt (i.e., 25 milliseconds)
4516
DEFW 45A3H
Address for TASK 11. This task is executed unconditionally once every clock interrupt (i.e., 25 milliseconds)

4518H – Interrupt Service Routine. This is entered for all maskable interrupts.

4518
PUSH HL
Put Register Pair HL (the interrupt) into the Stack
4519
PUSH AF
Put Register Pair AF into the Stack
451A
LD A,(37E0H)
Fetch the interrupt status byte and put it into Register A
451D
LD HL,404B
Put the address where interrupt status is stored into Register Pair HL
4520
LD (HL),A
Put the interrupt status byte (held in Register A) into the address where interrupt status is stored (held in Register Pair HL)
4521
INC L
Bump Register L so that HL now points to 404CH, which is the address of the interrupt service routine mask byte
4522
AND (HL)
Compare Register A (interrupt status byte) against the memory location pointed to Register Pair HL’s contents (interrupt service routine mask byte), to see if they match
4523
If A does not match (HL) then JUMP to 452DH and ignore the interrupt
4525
INC L
If we are here, then the status byte matched the service routine, so we need to process the interrupt. First, Bump HL to point to 404DH, which is the start of the interrupt processor’s 8 item address list appearing above.
4526
RRA
Rotate the bits in A one bit to the right so that we can use the CARRY FLAG to see what bit is on. This will start at Bit 7 work toward Bit 0. In RRA, the contents of Bit 0 (which would be lost) are copied to the carry flag, and the carry flag contents are copied to Bit 7.
4527
If the current CARRY BIT is ONE, then JUMP to 4538H to call the Interrupt Task Routine associated with On List
4529
INC L
If we are here then we are moving to the next interrupt processor. Since each one is 2 bytes, we need to BUMP L twice. This is the first. When we JUMP to 4525H later, that’s the second.
452A
OR A
Test the rotated Register A to see if there are any remaining interrupt bits.
452B
If there still are remaining bits, then LOOP back to 4525H
452D
LD A,(3840H)
If we are here, then there were no are remaining bits, so load Register A with the contents of the BREAK KEY keyboard row
4530
AND 04H
MASK Register A (the contents of the BREAK keyboard row) against 00000100 to drop all bits except bit 2, which is the BREAK KEY. By doing this, we will either have a Z if there is no break or a NZ if there is a break.
4532
If NZ is set then we have a BREAK, so JUMP to 454FH to see if DEBUG is active and needs to be loaded. If so, it sets the flags and JUMPs to DEBUG.
4534
POP AF
Restore Register Pair AF from the stack
4535
POP HL
Restore the interrupt into Register Pair HL from the stack
4536
EI
Enable interrupts
4537
RET
RETURN to the point of the interrupt

4538H – Call Interrupt Task Routine Associated With On List

4538
PUSH AF
Save the contents of Register Pair AF (Interrupt Service Routine Context) to the stack
4539
PUSH BC
Save the contents of Register Pair BC (Interrupt Routine Context) to the stack
453A
PUSH DE
Save the contents of Register Pair DE (Interrupt Routine Context) to the stack
453B
PUSH HL
Save the contents of Register Pair HL (Interrupt Routine Context) to the stack
453C
PUSH IX
Save the contents of Register Pair IX to the stack
453E
LD DE,4547H
Set DE to point to the return address after executing the interrupt processor routine
4541
PUSH DE
Save the contents of Register Pair DE (return address after executing the interrupt processor routine) to the stack. Note: This simulates a CALL instruction
4542
LD E,(HL)
Put the LSB of the interrupt processor address (pointed to by HL) into Register E
4543
INC L
BUMP Register L by 1 so that HL points to the MSB of the processor address
4544
LD D,(HL)
Put the MSB of the interrupt processor address (pointed to by HL) into Register D
4545
EX DE,HL
SWAP Register Pair DE with Register Pair HL so that HL holds the interrupt processor address
4546
Run the applicable interrupt processor

4547H – Return Point for Interrupt Task Routines

4547
POP IX
RESTORE the interrupted service context back into Register Pair IX
4549
POP HL
Restore the stack into Register Pair HL
454A
POP DE
Restore the stack into Register Pair DE
454B
POP BC
Restore the stack into Register Pair BC
454C
POP AF
Restore the stack into Register Pair AF
454D
LOOP back to 4529H to look for more bits in the status byte

454FH – This subroutine is jumped to with we have a BREAK to exit. We need to see if DEBUG is active and needs to be loaded or not.

454F
LD HL,4315H
Point HL to the address of the DEBUG ACTIVE flag
4552
XOR A
Zero out A so we can compare that against 0
4553
CP (HL)
Compare the DEBUG ACTIVE flag (held at 4315H) to 0 to see if DEBUG is active or not
4554
If it is zero then DEBUG is not active so then JUMP right back to 4534H to continue the routine
4556
LD (HL),A
If we are here, theN DEBUG is active, so first set the DEBUG ACTIVE flag to zero
4557
INC L
Bump L to point to the LSB of the address containing the DEBUG LOAD request
4558
LD A,(HL)
Put the LSB of the address containing the DEBUG LOAD request into Register A
4559
INC L
Bump L to point to the MSB of the address containing the DEBUG LOAD request
455A
LD H,(HL)
Put the MSB of the address containing the DEBUG LOAD request into Register H (in preparation to put it into HL)
455B
LD L,A
Copy the LSB of the address containing the DEBUG LOAD request from Register A into Register L (to finish putting it in HL)
455C
POP AF
Restore the interrupted context into Register AF
455D
EX (SP),HL
Restore the interrupted HL by pushing it into the stack
455E
EI
Enable Interrupts
455F
RET
RETURN = load and execute DEBUG

4560H – Clock Interrupt Routine.

4560
LD A,08H
Put the index for the 8th entry in the interrupt task list table into Register A. Note: Entries 8-11 are executed unconditionally once every clock interrupt.
4562
GOSUB to 457BH to execute the task pointed to by Register A
4565
LD A,09H
Put the index for the 9th entry in the interrupt task list table into Register A
4567
GOSUB to 457BH to execute the task pointed to by Register A
456A
LD A,0AH
Put the index for the 10th entry in the interrupt task list table into Register A
456C
GOSUB to 457BH to execute the task pointed to by Register A
456F
LD A,0BH
Put the index for the 11th entry in the interrupt task list table into Register A
4571
GOSUB to 457BH to execute the task pointed to by Register A
4574
LD HL,4040
Point HL to the index address for tasks to be executed every clock interrupt
4577
INC (HL)
Bump the index address for tasks to be executed every clock interrupt by 1
4578
LD A,(HL)
Fetch the index address
4579
AND 07H
MASK the index address held in Register A against 0000 0111 to keep only bits 0-2, so that it falls within a value of 0-7

Now we are going to locate the task address and execute the task

457B
RLCA
Due to the way the index table was set up, the index address * 2 will give the LSB of the interrupt task list address. RLCA is a cheap way to multiply by 2
457C
LD L,A
Take the doubled index and put it into Register L to form the LSB of the address
457D
LD H,45H
Put a 45H into Register H so HL will be 45aa.
457F
LD (45A6H),HL
Put the interrupt task list address into 45A6H which holds the CURRENT task list address

Load the address for the FIRST level of indirection into DE

4582
LD E,(HL)
Put the LSB of the address for the first level of indirection into Register E
4583
INC L
Bump L to point to the MSB of the address for the first level of indirection
4584
LD D,(HL)
Put the MSB of the address for the first level of indirection into Register D
4585
PUSH DE
Store the address for the first level of indirection onto the Stack
4586
POP IX
Restore the address for the first level of indirection from the Stack into Register IX
4588
EX DE,HL
Swap DE and HL so that the first level of indirection is in Register Pair HL

Load the address for the SECOND level of indirection into DE

4589
LD E,(HL)
Put the LSB of the address for the second level of indirection into Register E
458A
INC L
Bump L to point to the MSB of the address for the second level of indirection
458B
LD D,(HL)
Put the MSB of the address for the second level of indirection into Register D
458C
EX DE,HL
Swap DE and HL so that the second level of indirection is in Register Pair HL
458D
Execute the applicable task and then return to either 457BH or 4560H, as applicable

458E – Routine to replace the address for the first pointer in the INTERRUPT TABLE with the contents held in Register Pair DE

458E
POP DE
Clear the stack
458F
LD A,(45A6H)
Fetch the LSB of the first pointer to the last interrupt task which was executed
4592
RRCA
Due to the way the index table was set up, the interrupt task list address divided by 2 will give you the index address. RRCA is a cheap way to divide by 2
4593
LD DE,45A3H
We are going to need to do different things if 45A3H holds an address from the interrupt task list, so put that address into Register Pair DE for testing
4596
CP 0CH
Compare the index for the last task against 12 (which is the maximum number of interrupts)
4598
RET NC
If NC, then we are not higher than 12, so RETURN to process the next interrupt
4599
RLCA
Due to the way the index table was set up, the index address * 2 will give the LSB of the interrupt task list address. RLCA is a cheap way to multiply by 2
459A
LD L,A
Take the doubled index and put it into Register L to form the LSB of the address
459B
LD H,45H
Put a 45H into Register H so HL will be 45xx
459D
DI
Disable Interrupts while we change the interrupt task list table
459E
459F
45A0
LD (HL),E
INC L
LD (HL),D
Put a 45A3H into (HL). Since 45A3H (the pointer to the last index) points to 45A2 which is a RET, this effectively ignores future calls to this index
45A1
EI
Enable interrupts
45A2
RET
RETURN to the caller routine

45A3 – This is default interrupt task list jump location

45A3
DEFW 45A2H
45A5
LD HL,0000H
Set HL to point to the address of the current interrupt task. The values in 45A6-45A7 will be reset by various routines
45A8
LD E,(HL)
Put the LSB of the task list address into Register E
45A9
INC HL
Bump HL by 1 to point to the MSB of the task list address
45AA
LD D,(HL)
Put the MSB of the task list address into Register D
45AB
EX DE,HL
Swap Register Pair DE with HL so that the task list address is now stored in HL
45AC
POP DE
Restore the first pointer back into DE
45AD
JUMP to 459DH to store the address of the first pointer for the current interrupt task

45AFH – Routine to maintain the clock. This routine gets called if CLOCK (ON)

45AF
DEFW 45B2H
45B1
DEFB 5
Count of the times entered
45B2
DEC (IX+02H)
DECrement (IX+02H) to see if we have been entered 5 times (for 1 second)
45B5
RET NZ
If we have not yet been entered 5 times, RETURN
45B6
LD (IX+02H),05H
If we HAVE been entered 5 times, then reset the counter to 5
45BA
LD B,03H
Set Register B to the maximum number of values to udpate
45BB
LD HL,4041H
Set Register Pair HL to point to the starting address for the time (which is the seconds)
45BF
LD DE,45EFH
Set Register Pair DE to point to the address for the maximum values in each time period
45C2
INC (HL)
Bump the seconds, the minutes, then hours
45C3
LD A,(DE)
Fetch the amximu value for the period being incremented
45C4
SUB (HL)
Compare the period being incremented to the maximum value to which it can be incremented
45C5
RET NZ
If we have NOT hit the maximum value, then RETURN
45C6
LD (HL),A
If we are here, then we have hit the maximum value the period can be incrementd to, so reset it to 0
45C7
INC L
Bump the increment by 1
45C8
INC E
Bump E by 1 so as to form a test to the next unit
45C9
LOOP back to 45C2H until all of the periods have been incremented OR no overflow from one period to the next (i.e., seconds into minutes)

45C3H – Continuing on the theme of incrementing periods if a lower period has overflowed (i.e., hours has flowed into an increased date)

45CB
INC L
Bump Register L to skip over the YEAR
45CC
INC (HL)
Bump the day value
45CD
INC L
Bump L to skip to the MONTH
45CE
LD A,(HL)
Fetch the MONTH value from (HL) and put it into Register A
45CF
DEC L
Backspace to the DAY
45D0
DEC A
DECrement the MONTH by 1
45D1
ADD A,E
ADD Register E to Register A so that A points to the index into the month table
45D2
LD E,A
Put the index into the month table back into Register E so Register Pair DE points to the month table
45D3
LD A,(DE)
Fetch the number of days for that month
45D4
CP (HL)
Compare the number of days for that month from the table against the current number of days (pointed at by HL)
45D5
RET NC
If NC is set, then we have not exceeded the test value, so the MONTH did not roll over. In this case, RETURN
45D6
LD A,(HL)
Fetch the current day number being pointed to by HL
45D7
CP 1EH
Compare that day number against 30
45D9
If NC is set, then the comparison was either EQUAL TO or GREATER THAN the test of 30, so JUMP to 45E1H
45DB
DEC L
If we are here, then it is February! First backspace to the YEAR
45DC
LD A,(HL)
Fetch the YEAR from (HL)
45DD
INC L
Skip forward to the DAY
45DE
AND 03H
MASK the year against 0000 0011 (which is a 3)
45E0
RET Z
If Z is set then it is NOT a leap year
45E1
LD (HL),01H
If we are here, then it is a leap year, so Reset the day to 1
45E3
INC L
BUMP HL to point to the MONTH
45E4
INC (HL)
INCrement the actual month value
45E5
LD A,(HL)
Fetch the month value we just wrote
45E6
SUB 0DH
SUBtract 13 to see if the year rolled over
45E8
RET C
If C is set then the comparison was LESS THAN the comparison value (of 13) so RETURN since we are not at the end of the year
45E9
LD (HL),01H
If we are here then we rolled over on the year, so put a JANUARY into (HL)
45EB
DEC L
Backspace over the month
45EC
DEC L
Backspace over the day
45ED
INC (HL)
INCrement the year
45EE
RET
RETURN to the interrupt processor

Maximum value table for Periods

45EF
DEFB 3CH
Maximum number of 60 for SECONDS
45F0
DEFB 3CH
Maximum number of 60 for MINUTES
45F1
DEFB 18H
Maximum number of 24 for HOURS
45F2
DEFB 1FH
Maximum number of 31 for JANUARY
45F3
DEFB 1CH
Maximum number of 28 for FEBRUARY
45F4
DEFB 1FH
Maximum number of 31 for MARCH
45F5
DEFB 1EH
Maximum number of 30 for APRIL
45F6
DEFB 1FH
Maximum number of 31 for MAY
45F7
DEFB 1EH
Maximum number of 30 for JUNE
45F8
DEFB 1FH
Maximum number of 31 for JULY
45F9
DEFB 1FH
Maximum number of 31 for AUGUST
45FA
DEFB 1EH
Maximum number of 30 for SEPTEMBER
45FB
DEFB 1FH
Maximum number of 31 for OCTOBER
45FC
DEFB 1EH
Maximum number of 30 for NOVEMBER
45FD
DEFB 1FH
Maximum number of 31 for DECEMBER

4600H – Select Drive Specified in C-Register

4600
PUSH HL
Save the contents of Register Pair HL (the callers HL value) to the stack
4601
LD HL,4308H
Point HL to the memory location holding the address of the last disk drive selected
4604
LD A,(HL)
Put the last disk drive selected held in the memory location pointed to by HL into Register A
4605
CP C
Compare the last disk drive selected with the drive requested
4606
If they are NOT the same then the NZ will be set, so JUMP to 460CH to construct a new drive select mask
4608
INC L
BUMP Register L by 1 to the last drive select mask
4609
LD A,(HL)
Put the last drive select mask pointed to by HL into Register A
460A
Jump to 4624H to select the drive pointed to by Register A
460C
LD (HL),C
Update the last drive selected memory location (held at HL) with the contents of Register C
460D
LD H,43H
Start to set up HL to point to the origin of last track Table
460F
LD L,A
Put the modified Register L (held in A) into Register L to finish forming the origin of current drive
4610
GOSUB to 4398H to wait for the floppy disk controller to report NOT BUSY
4613
LD A,(37EDH)
Put the current track (held at memory location 37EDH) into Register A
4616
LD (HL),A
Save the current track into the last track selected table
4617
LD A,C
Put Register C (the drive number) into Register A for processing/testing
4618
LD L,A
Put the drive number (from Register A) into Register L to reform the last track address for the selected drive
4619
LD A,(HL)
Put the character pointed to by HL (the last track selected this drive) into Register A
461A
LD (37EDH),A
Update the track register to point to the current track number
461D
LD A,C
Put Register C (the drive number) into Register A for processing/testing
461E
GOSUB to 4639H to construct the drive select mask
4621
LD (4309H),A
Save the drive select mask into 4309H
4624
LD L,A
Save Register A into Register L
4625
LD A,(37ECH)
Fetch the disk drive controller status from (37ECH) and put it into Register A
4628
RLCA
RLCA rotates the bits in A left one position, and Bit 7 (which would be rotated off) gets copied to BOTH Bit 0 and the CARRY FLAG. This shifts the NOT READY bit into the CARRY FLAG
4629
LD A,L
Now that we did that, Put Register L into Register A for processing/testing
462A
LD (37E1H),A
Select the drive by sending the drive select mask to (37E1H)
462D
If NC is set then the drive is ready, so JUMP to 4637H
462F
LD HL,0000H
If we are here the drive is NOT ready. Set HL to 0000H which will work out to be a delay of about 1 second.
4632
DEC HL
Decrement HL by 1
4633
4634
LD A,H
OR L
Z-80 trick to check HL against 0
4635
If the counter is not yet zero, LOOP back to 4632H
4637
POP HL
Restore the caller’s HL from the stack into Register Pair HL
4638
RET
RETURN

Construct Unit Select Mask for Value in A-Register

4639
AND 07H
MASK Register A against 0000 0111 to keep only bits 0-2 and isolate to drive number 0-3
463B
463C
463D
RLCA
RLCA
RLCA
RLCA rotates the bits in A (the drive number) left THREE positions, and Bit 7 (which would be rotated off) gets copied to BOTH Bit 0 and the CARRY FLAG. This shifts the NOT READY bit into the CARRY FLAG
463E
OR 0C7H
By ORing A against C7H we actually construct a SET (A),A instruction
4640
LD (4645H),A
Save that constructed OPCODE into 4645H
4643
XOR A,A
Zero out Register A
4644
SET 0,A
Turn on the drive number bit
4646
RET
RETURN

Select Drive and Issue Seek if not on Track

4647
GOSUB to 4600H to select the drive
464A
GOSUB to 4669H to wait for the disk ccontroller to report back NOT BUSY
464D
LD A,(37EDH)
Fetch the last track selected from its holding place of 37EDH and put it into Register A for processing
4650
CP D
Compare the last selected track against the required track held in Register D
4651
LD (37EEH),DE
Update the track/sector registers held in 37EEH with the contents in Register Pair DE
4655
RET Z
If the Z flag is set then we are on the right track, so RETURN to the caller
4656
LD A,1FH
If we are here, then we are at the wrong track. First set Register A to point to the SEEK OPCODE
4658
PUSH AF
Save the contents of Register Pair AF (including the SEEK OPCODE) to the stack
4659
GOSUB to 4669H to wait for the disk ccontroller to report back NOT BUSY
465C
POP AF
Restore the SEEK OPCODE from the stack
465D
LD (37ECH),A
Send the SEEK OPCODE to the floppy disk controller by putting it into 37ECH
4660
RET
RETURN

4661H – Send the Command in Register A to the Floppy Disk Controller. The routine returns when the command is processed.

4661
Send a command to controller.
4664
4665
4666
4667
4668
PUSH AF
POP AF
NOP
NOP
NOP
Stall …
4669
GOSUB to 43ACH to select the disk drive and get the status
466C
BIT 0,A
Test Bit 0 of Register A to see if the controller is busy
466E
RET Z
If the Z flag is set then the controller is NOT BUSY, so RETURN to the caller
466F
LOOP back to 4669 until the controller is NOT BUSY

4671H – Disk Driver. When this routine is called, A has the OPCODE for the controller, C has the drive unit, and DE has the track/sector

4671
LD (4693H),A
Put the callers controller opcode into 4693H. 4692H is LOAD (HL),xxH so this instruction fills the xx.
4674
EX (SP),HL
Swap (SP) and HL so that (SP) now contains the buffer address and HL contains the parameter list address
4675
PUSH BC
Save the contents of Register Pair BC (Register C has the drive unit) to the stack
4676
LD B,(HL)
Fetch retry count and put it into Register B
4677
INC HL
Bump Register Pair HL by 1 to point to the next parameter
4678
LD A,(HL)
Get bias count pointed to at (HL) to add to first ‘ON’ bit in status if error and put it into Register A
4679
INC HL
Bump Register Pair HL by 1 to point to the next parameter
467A
LD (46D3H),A
Put the error bias value into 46D3H
467D
LD A,(HL)
Put the character pointed to by HL (the Get FETCH/STORE instruction to or from the disk controller) into Register A
467E
INC HL
Bump Register Pair HL by 1 to point to the next parameter
467F
LD H,(HL)
Get the FETCH/STORE instruction to or from data buffer and put it into H
4680
LD L,A
Assemble the controller/buffer reference commands into HL by putting A into L
4681
LD (46A7H),HL
Save the controller/buffer reference commands into the main driver loop
4684
POP HL
Restore the callers BC and put it into Register Pair HL
4685
EX (SP),HL
Swap (SP) and HL so that HL now contains the buffer address
4686
GOSUB to 4647H to select the unit and then position to the track and sector held in Register Pair DE
4689
GOSUB to 4669H to delay
468C
PUSH BC
Save the contents of Register Pair BC (retry count/unit number) to the stack
468D
PUSH DE
Save the contents of Register Pair DE (track/sector number) to the stack
468E
PUSH HL
Save the contents of Register Pair HL (buffer address) to the stack
468F
LD HL,37ECH
Set HL to be 37ECH which is the controller command/status register
4692
LD (HL),00H
Issue a read/write command by sending a xxH to the controller command/status register. This is set as 00H here, but 4671H actually loads the value here.
4694
LD DE,37EFH
Set DE to be 37EFH which is the address of the controller data register
4697
POP BC
Need to stall. First, restore the delay and buffer address from the stack into Register Pair BC
4698
PUSH BC
Then put it back into the stack
4699
POP BC
The restore it from the stack again
469A
PUSH BC
Then put it back into the stack
469B
DI
Disable interrupts
469C
JUMP to 46A1H to get the status and begin the transfer
469E
RRCA
Rotate A Right with Carry so that the controller busy bit is put into the CARRY FLAG
469F
If NC is set, then the controller is NOT busy, meaning that the operation is complete, so JUMP to 46ADH
46A1
LD A,(HL)
If we are here, then the controller is busy, so poll the controller status again into Register A
46A2
BIT 1,A
Test Bit 1 of Register A to see if the controller is ready to transfer a byte of data
46A4
If the Z Flag is SET then the controller is NOT ready, so JUMP to 469EH until it is
46A6
DI
If we are here then we assumed the operation completed, so disable interrupts (for no valid reason)
46A7
NOP
NO OPERATION
46A8
NOP
NO OPERATION
46A9
INC BC
Bump BC (the buffer address) to next byte
46AA
LOOP back to 46A1H until the controller is no longer busy

The operation is complete so check the status and test for errors

46AD
EI
Enable interrupts as the transfer is done
46AE
LD A,(HL)
Put the character pointed to by HL (the floppy disk controller status) into Register A
46AF
AND 7CH
AND Register A against 0111 1100 to strip the NOT READY bit off.
46B1
LD (HL),0D0H
Send a 0D0H to the floppy disk controller to force an interrupt to clear controller. 1101 0000 is the command to FORCE INTERRUPT (1101) and TERMINATE WITH NO INTERRUPT (0000). This resets the FDC.
46B3
POP HL
Restore the buffer address from the stack into Register Pair HL
46B4
POP DE
Restore the track/sector address from the stack into Register Pair DE
46B5
POP BC
Restore the retry count/unit number from the stack into Register Pair BC
46B6
If the Z Flag is SET then there were no errors, so JUMP to 46DBH
46B8
BIT 2,A
If we are here then we had an error. First, test Bit 2 of Register A to check for a LOST DATA error
46BA
If there was a LOST DATA error, then JUMP to 46CFH
46BC
CP 20H
Next, check Register A against 20H to check for a WRITE FAULT error
46BE
If the Z Flag is SET then there was a WRITE FAULT error, so JUMP to 46D1H
46C0
PUSH AF
Save the contents of Register Pair AF (the current error status) to the stack
46C1
46C3
LD A,0FFH
LD (4308H),A
Force the disk drive to be selected by sending a 0FFH which modifies the last disk drive selected byte and sending it to 4308H which holds the address of the last disk drive selected
46C6
GOSUB to 4600H to select the disk drive unit
46C9
LD A,0BH
Set Register A with 08B which is the restore head command
46CB
GOSUB to 4658H to issue the restore command
46CE
POP AF
Restore the last disk status from the stack into Register Pair AF
46CF
LOOP back to 4686H until B is zero
46D1
LD B,A
Put the status for the last operation into Register B so as to free up Register A
46D2
LD A,00H
Put a 0 into Register A. This will be the bias value to add to the error number
46D4
RRC B
Rotate Right with Carry on B. This will right shift the error status
46D6
If the Rotate Right on B has a “1”, then JUMP down to 46DBH
46D8
INC A
If we are here, then there still as no CARRY FLAG set so BUMP Register A by 1 for each rotate
46D9
LOOP back to 46D4H until the Carry Flag finally holds a “1”

If the rotating bits finally puts a “1” into the Carry, then we sent here from 46D6H

46DB
POP BC
Restore the retry count/unit from the stack into Register Pair BC
46DC
RET
RETURN

46DD – This is the read Subroutine used for Calling Disk Driver. Driver Returns to Caller of Read Subroutine

46DD
LD A,88H
Put the code for READ into Register A
46DF
GOSUB to 4671H to call the disk driver
46E2
DEFB 0AH
Retry Count for Errors
46E3
DEFB 01H
Bias to be added to the number of the first “1” bit on error
46E4
LD A,(DE)
Read a byte from the floppy disk controller
46E5
LD (BC),A
Put that byte into the caller’s buffer

46E6 – This is the write subroutine used for Calling Disk Driver. Driver Returns to Caller of Write Subroutine

46E6
LD A,0A8H
Set up Register A to hold the WRITE command, with generating ‘FB’ Data Address Mark.
46E8
GOSUB to 4671H to call the disk driver
46EB
DEFB 05H
Retry Count for Errors
46EC
DEFB 09H
Bias to be added to the number of the first “1” bit on error
46ED
LD A,(BC)
Read a byte from the floppy disk controller
46EE
LD (DE),A
Put that byte into the caller’s buffer

46EF – This is the write subroutine used for Calling Disk Driver. Driver Returns to Caller of Write Subroutine

46EF
LD A,0A9H
Set up Register A to hold the WRITE command, with generating ‘FA’ Data Address Mark.
46F1
JUMP to 46E8H to use the normal write disk call

46F3 – This is the read Subroutine used for Calling Disk Driver. Driver Returns to Caller of Read Subroutine

46F3
LD A,88H
Put the code for READ into Register A
46F5
GOSUB to 4671H to call the disk driver
46F8
DEFB 05H
Retry Count for Errors
46F9
DEFB 01H
Bias to be added
46FA
LD A,(DE)
Read a byte from the floppy disk controller
46FB
NOP
NO OPERATION (note: This is where a command put it into a buffer would have been)

4700 – Position File to Specified Record Number

4700
GOSUB to 4892 to set IX to the DCB address, save registers, and ensure file is OPEN
4703
SET 6,(IX+01H)
Set Bit 6 in IX+01 so as to signal a POSITION call
4707
BIT 7,(IX+01H)
Test Bit 7 in IX+01H to see if we are doing physical or logical I/O
470B
If Bit 7 is 0 then we are doing physical I/O, so JUMP to 4717H
470D
470E
LD H,B
LD L,C
If we are here, then we re doing logical I/O, so copy BC (which is the record number) to HL
470F
LD A,(IX+09H)
Fetch the record size from (IX+09H)
4712
GOSUB to 4B6AH to divide the record number by record size
4715
4716
LD B,H
LD C,L
Copy HL (the quotient / relative physical record number) to BC
4717
LD (IX+05H),A
Save record offset within sector to IX+05H
471A
PUSH BC
Save the relative physical record number to the stack
471B
GOSUB to 4878H to flush the sector buffer to disk
471E
POP BC
Restore the relative physical record number from the stack into BC
472F
RET NZ
If NZ is set, then we have an error when writing the sector buffer, so RETURN
4720
4723
LD (IX+0AH),C
LD (IX+0BH),B
Move relative sector to the DCB as next record number
4726
SET 5,(IX+01H)
Force a read on the next file access
472A
GOSUB to 48B9H to test for end of file
472D
RET NZ
If NZ is set, we have an EOF do so an early RETURN
472E
If we are here, then there was no error and no EOF so GOSUB to 48DCH to get the track/sector for the sector number
47C0
RET NZ
If NZ is set, there was an error translating the record number to an actual disk address so RETURN
4732
GOSUB to 4647H to select the drive and find the track
4735
XOR A
Clear all flags so as to signal no error
4736
RET
RETURN

4737 – Backspace Operation

4737
GOSUB to 4892 to set IX to the DCB address, save registers, and ensure file is OPEN
473A
473D
LD C,(IX+0AH)
LD B,(IX+0BH)
Load Register Pair BC with the next sector number
4740
BIT 7,(IX+01H)
Test Bit 7 in IX+01H to see if we are doing physical or logical I/O
4744
If Bit 7 is 0 then we are doing physical I/O, so JUMP to 4753H
4746
LD A,(IX+09H)
Fetch the record size from (IX+09H)
4749
OR A
Test if the record size is 256 bytes
474A
If the record size is 256 bytes, then JUMP to 4753H
474C
NEG
If not, NEGate the record size (i.e., subtract A from zero)
474E
ADD A,(IX+05H)
Add (well, with the NEG its subtract) the record length from the position in sector buffer
4751
If the record crosses the sector line then JUMP to 4717H
4753
DEC BC
If we are here then we are either doing physical I/O or the record didn’t cross the sector line. In those cases, decrement the sector number
4754
Update the DCB, Flush all buffers, and return

4756H – Rewind File – Use POSN Code to Position to Record 0

4756
GOSUB to 4892 to set IX to the DCB address, save registers, and ensure file is OPEN
4759
LD BC,0000H
Load Register BC with the record number for a POSN call
475C
XOR A
Zero out A (i.e. zero out the offset in the sector buffer)
475D
GOSUB to 4717H to use the POSN code to rewind the file

475FH – Skip to EOF

475F
GOSUB to 4892 to set IX to the DCB address, save registers, and ensure file is OPEN
4762
4765
LD C,(IX+0CH)
LD B,(IX+0DH)
Put the number of records in the file into Register Pair BC
4768
LD A,(IX+08H)
Put the EOF of the last sector into Register A
476B
GOSUB to 4717H to use the POSN code to rewind the file

475FH – Read Processing

476D
GOSUB to 4892 to set IX to the DCB address, save registers, and ensure file is OPEN
4770
GOSUB to 4878H to purge the buffer
4773
RET NZ
If there was an error during the purge, RETURN
4774
BIT 7,(IX+01H)
If there was no error during the purge then, test Bit 7 in IX+01H to see if we are doing physical or logical I/O
4778
If Z is set then we must either read a sector, the buffer is not ready, or sector I/O is needed, so jump away to 480EH

477BH – This is the LOGICAL READ routine. It transfers a record from the sector buffer to the user’s record area, byte by byte. The subroutine at 47BBH returns the next byte from the sector buffer. If the buffer is exhaused, the next sector will be read.

477B
LD B,(IX+09H)
IX+09H holds the record size. Put the record size into Register B.
477E
PUSH HL
Save the user’s record area address held in HL to the stack
477F
PUSH BC
Save the byte count held in BC to the stack
4780
GOSUB to 47BBH to get a byte from sector buffer
4783
POP BC
Restore the byte count held in the stack to BC
4784
POP HL
Restore the user’s record area address held in the stack to HL
4785
RET NZ
If NZ is set, then there was an error from the prior call, so EXIT due to error
4786
LD (HL),A
If we are here, then there was no error. Put a byte into the caller’s buffer/record area
4787
INC HL
HL = HL + 1
4788
LOOP back to 477EH until all the bytes read (here) or we error out and return in 4785H
478A
RET
RETURN

478BH – This is the beginning of WRITE Processing.

478B
GOSUB to 4892H to set IX to the DCB address, save registers, and ensure file is OPEN
478E
LD (485EH),A
Initialtze the VERIFY flag
4791
BIT 7,(IX+01H)
Test Bit 7 in IX+01H to see if we are doing physical or logical I/O
4795
If Bit 7 if IX+01H is zero, then we are doing physical I/O, so JUMP to 483EH

If we are here, then we are doing LOGICAL I/O instead of PHYSICAL I/O. This routine will transfer a record from the user’s record area to the file sector buffer byte by byte. The subroutine at 47DAH stores the next byte in the sector buffer. If the buffer is full, it is written to disk.

4798
LD B,(IX+09H)
IX+09H = record length in bytes. Put the record size into Register B. This will be the counter of the number of bytes to loop through.
479B
LD A,(HL)
Top of loop. Fetch one byte from the user’s buffer area
479C
INC HL
HL = HL + 1
479D
PUSH HL
Push the pointer to the current buffer position (held in HL) to the stack
479E
PUSH BC
Push the count of characters moved (held in BC) to the stack
479F
GOSUB to 47DAH to store the character in to the sector buffer
47A2
POP BC
Restore the count of characters moved (held in the stack) to BC
47A3
POP HL
Restore the pointer to the current buffer position (held in the stack) to HL
47A4
RET NZ
If that CALL resulted in an error, NZ will be set, so if NZ is set, exit due to error
47A5
If we are here, then there was no error, so LOOP back to 479BH to keep moving bytes
47A7
RET
RETURN to caller

This routine will process a VERIFY

47A8
GOSUB to 4892 to set IX to the DCB address, save registers, and ensure file is OPEN
47AB
INC A
Put a 1 into A to specify that VERIFY is set
47AC
JUMP to the write routine above at 478EH

47AEH – General Purpose Driver Entry —Called From Driver Entry at 44BB

47AE
GOSUB to 4892 to set IX to the DCB address, save registers, and ensure file is OPEN
47B1
SET 7,(IX+01H)
Set BIT 7 of IX+01 to signal that we are doing a LOGICAL I/O instead of a PHYSICAL I/O
47B5
LD A,B
Put the operation code held in B into Register A for testing.processing
4786
CP 02H
Compare Register A against 02H
4788
LD A,C
Put the data for write op code into Register A
4789
If NC is set then the operation code held in A was equal to or greater than 02H, meaning that this is a WRITE command, so JUMP to 47DAH

If we are here, then we are doing a READ operation

47BB
GOSUB to 48B9H to tests for end of file
47BE
RET NZ
If the NZ flag is set, then we are at EOF so RETURN to the caller
47BF
BIT 5,(IX+01H)
Test Bit 5 of IX+01H to see if we need another sector for buffer
47C3
If the Z Flag is SET then we do NOT need another sector, so we JUMP to 47C9H to get the next byte from the buffer
47C5
If we are here, then we DO need another sector, so GOSUB to 480EH to read the next sector from the file into the buffer
47C8
RET NZ
If the NZ flag is set then the READ produced an error, so RETURN to the caller
47C9
GOSUB to 4883H to put the address of the next record into Register Pair DE. It takes the next record pointer of IX+05H and adds it to the start of the DCB held in IX+03H/IX+04H and puts the result into DE and returns
47CC
XOR A
Clear all flags, Zero A
47CD
LD A,(DE)
Put a byte from the sector buffer into Register A
47CE
PUSH AF
Save the contents of Register Pair AF (the byte just fetched) to the stack
47CF
INC (IX+05H)
Bump to next byte in sector buffer
47D2
If NZ is set then the buffer is not empty, so JUMP forward to 47D8H
47D4
SET 5,(IX+01H)
Set Bit 5 of IX+01H to signal that we need PHYSICAL I/O
47D8
POP AF
Restore the character fetched from the stack into Register Pair AF
47D9
RET
RETURN

If we are here, then we are doing a WRITE operation

47DA
PUSH AF
Save the contents of Register Pair AF (the character to be written) to the stack
47DB
BIT 5,(IX+01H)
Test Bit 5 of IX+01 to see if the sector buffer is full
47DF
If the Z Flag is SET then there is still room in the sector buffer, so JUMP to 47E9H
47E1
If we are here, then the sector buffer was full, so we need to read the next sector from the file into the buffer. GOSUB to 482DH to do that.
47E4
If the Z Flag is SET then there was no error, so JUMP to 47E9H
47E6
EX (SP),HL
If we are here, then there was an error getting the next sector. Swap the contents of the stack pointer into HL and vice versa, which clears the character from the stack
47E7
POP HL
Clear the stack
47E8
RET
RETURN

This is the JUMP point if there was no error reading a new sector into the buffer.

47E9
GOSUB to 4883H to put the address of the next record into Register Pair DE. It takes the next record pointer of IX+05H and adds it to the start of the DCB held in IX+03H/IX+04H and puts the result into DE and returns
47EC
POP AF
Restore the character to be written from the stack into Register Pair AF
47ED
LD (DE),A
Put the value held in Register A into the memory location pointed to by DE
47EE
INC DE
DE = DE + 1
47EF
SET 4,(IX+01H)
Set Bit 4 of IX+01H to signal that the buffer has been updated
47F3
INC (IX+05H)
Bump to next position in buffer
47F6
GOSUB to 48B9H to test for end of file
47F9
If NZ is set then there is an ACCESS ERROR so JUMP to 4801H
47FB
BIT 6,(IX+01H)
Test Bit 6 of IX+01H to set if that the record number must be incremented
47FF
If NZ is set, then the record number does not need to be incremented, so SKIP the next OPCODE
4801
LD (IX+08H),C
IX+08 holds the EOF offset held in the DCB. Update the EOF address in sector buffer to the contents of Register C
4804
LD A,C
Put Register C (the current sector buffer offset) into Register A for processing/testing
4805
OR A
Set status flags for offset
4806
If NZ is set, then the end of the buffer has NOT been reached, so JUMP to 482BH
4808
SET 5,(IX+01H)
If we are here, then the end of the buffer has been reached. We need to force a read on the next access, so set Bit 5 of IX+01H to force that read.
480C
JUMP to 483EH, which is the standard exit for all writes

This routine reads the next sector from the file into the buffer. It is called from a few places. This first part tests to see if there is protection that would otherwise stop us from doing the read.

480E
LD A,(IX+01H)
Fetch access levels/flags and put them into Register A
4811
AND 07H
MASK Register A against 0000 0111 to keep only bits 0-2, isolating the permissions flag
4813
CP 06H
Compare the masked Register A against 06H, which would be READ/WRITE access
4815
If C is set then A is less than the test value of 06H, meaning that there is no protection, JUMP to 481BH
4817
LD A,25H
If we are here, then there is protection, so set up Register A with an ILLEGAL ACCESS ATTEMPTED ON PROTECTED FILE error
4819
OR A
Set the flags for an error
481A
RET
RETURN
481B
If we are here, then there was no protection on the file, so first GOSUB to 48B9H to test for EOF
481E
RET NZ
If the NZ flag is set then we are at the EOF, so RETURN to the caller
481F
If we are here, there is no protection on the file and we are not at EOF, so GOSUB to 482DH to read the next sector from the file
4822
RET NZ
If the NZ flag is set then we had an error on the sector read, so RETURN to the caller
4823
INC (IX+0AH)
IX+0AH is the LSB of the next sector number. Bump the next sector number by one
4826
Since we just bumped the LSB, we need to see if that rolled over and needs to affect the MSB. For that we test for NZ. If NZ is set, there was no overflow, so we can skip over the next 2 instructions which bump the MSB
4828
INC (IX+0BH)
IX+0BH is the LSB of the next sector number. If we are here, the bump in the LSB required a bump in the MSB … so we bump it!
482B
XOR A
Set the flags for NO ERROR
482C
RET
RETURN

This routine reads the next sector from the file into the buffer. It is called from a few places.

482D
GOSUB to 48DCH to get the track/sector for the sector number specified (i.e., the next sector)
4830
RET NZ
If the NZ flag is set, then we have an error so RETURN to the caller
4831
RES 5,(IX+01H)
Reset Bit 5 of IX+01H to clear the force read next sector flag
4835
LD L,(IX+03H)
IX+03 is the LSB of the DCB Buffer Address. Put the LSB into L (which is the LSB of HL)
4838
LD H,(IX+04H)
IX+04 is the MSB of the DCB Buffer Address. Put the MSB into H (which is the MSB of HL)
483B
JUMP to 46DDH to read the sector and return to the caller

This routine continues the WRITE routine. It where the routine jumps when a PHYSICAL I/O is set.

483E
LD A,(IX+01H)
Fetch access levels/flags and put them into Register A
4841
AND 07H
MASK Register A against 0000 0111 to keep only bits 0-2, isolating the permissions flag
4843
CP 05H
Compare the masked Register A against 05H, which would be EXECUTE ONLY or restricted file
4845
If C is set then A is less than the test value of 05H, meaning that the file is not EXECUTE ONLY or other restricted access, so JUMP to 484BH and continue processing
4847
LD A,25H
If we are here, then there is protection, so set up Register A with an ILLEGAL ACCESS ATTEMPTED ON PROTECTED FILE error
4849
OR A
Set the flags for an error
484A
RET
RETURN with error
484B
If we are here, then we didn’t run into a permission error, so GOSUB to 48DCH to get the track/sector for the next sector
484E
RET NZ
If the NZ flag is set then there was an error computing the track/sector address, so RETURN to the caller
484F
LD L,(IX+03H)
IX+03 is the LSB of the DCB Buffer Address. Put the LSB into L (which is the LSB of HL)
4852
LD H,(IX+04H)
IX+04 is the MSB of the DCB Buffer Address. Put the MSB into H (which is the MSB of HL)
4855
GOSUB to 46E6H to write the current sector buffer
4858
RET NZ
If the NZ flag is set then there was an error writing the current sector buffer to disk so RETURN to the caller
4859
RES 4,(IX+01H)
If we are here, there was no error writing the current sector buffer to disk, so RESET Bit 4 of IX+01H to signal that the sector buffer is available
485D
LD A,00H
Put a value into Register A. This memory location (485EH) will be flipped based on whether VERIFY is ON or OFF
485F
OR A
Set the flags
4860
If the NZ Flag is SET then VERIFY is ON, so GOSUB to 46F3H to read the sector back to confirm the write
4863
RET NZ
If the NZ flag is set then there was an error during the re-read, so RETURN to the caller
4864
GOSUB to 48B9H to test for end of file
4867
IF the NZ flag is set, then we have EOF so JUMP to 486FH
4869
BIT 6,(IX+01H)
Test Bit 6 of IX+01H to see if we need to do a POSITION
486D
If NZ is set then we need to POSITION, so JUMP to 4823H (which will bump IX+0A and IX+0B if necessary, and return with NO ERROR)
486F
INC HL
Bump Register Pair HL by 1 to increase the record number
4870
LD (IX+0CH),L
IX+0CH is the LSB of the EOF sector held in the DCB, so put the BUMPed L into the LSB
4873
LD (IX+0DH),H
IX+0DH is the MSB of the EOF sector held in the DCB, so put the BUMPed H into the MSB
4876
JUMP to 4823H to update the record number and exit

This routine purges the sector buffer

4878
LD A,(IX+01H)
Fetch access levels/flags and put them into Register A
487B
AND 90H
Mask the access level/flags against 1001 0000 to isolate only Logical I/O / Write Access
487D
CP 90H
Compare the masked access level/flags with 90H
487F
If we have Logical I/O / Write Access then JUMP to 483EH
4881
XOR A
Clear all flags to signal good status after Physical I/O
4882
RET
RETURN to caller

This routine gets the address of the next record in the sector buffer pointed to by DE and computes the address for the next byte to be stored/fetched in current logical record

4883
LD A,(IX+05H)
Put the current byte offset in sector buffer into Register A
4886
LD C,A
Store the byte offset into Register C
4887
ADD A,(IX+03H)
IX+03H = the LSB of the DCB Sector Buffer Address. So add the current byte offset to the start of the DCB sector buffer
488A
LD E,A
Put Register A (the LSB of physical buffer addr) into Register E
488B
LD A,(IX+04H)
Fetch the MSB of DCB Sector Buffer Address
488E
ADC A,00H
Add any carry from the LSB addition
4890
LD D,A
Put the modified MSB of the SCB Sector buffer Address into D, so that DE now holds it
4891
RET
RETURN

4892H – Save the Registers

4892
LD A,(DE)
Put the current byte (pointed to by DE) into Register A
4893
RLCA
Rotate Register A so that the OPEN bit is in the CARRY FLAG
4894
If NC is set, then the file is not open do JUMP to 48EAH
4896
POP AF
Restore the routine return address from the stack into Register Pair AF
4897
EX (SP),HL
Swap the buffer address for the contents of the stack pointer
4898
LD (430CH),HL
Put the buffer address into 430CH
489B
LD (430AH),DE
Put the DCB address into 430AH
489F
EX (SP),HL
Restore the buffer address into HL and the return address to the stack
48A0
PUSH DE
Save the contents of Register Pair DE (the DCB address) to the stack
48A1
EX (SP),IX
Save the original IX (the DCB Address) into (SP)
48A3
PUSH HL
Save the contents of Register Pair HL to the stack
48A4
PUSH DE
Save the contents of Register Pair DE to the stack
48A5
PUSH BC
Save the contents of Register Pair BC to the stack
48A6
PUSH HL
Save the contents of Register Pair HL to the stack
48A7
LD HL,48B3H
Put the address of the routine to restore the registers into HL
48AA
EX (SP),HL
Set that address as the exit address
48AB
PUSH AF
Save the contents of Register Pair AF (the return address for caller to 4892H) to the stack
48AC
XOR A
Clear all flags to signal NO ERROR
48AD
RET
RETURN
48AE
POP AF
Clear return addr to 4892
48AF
LD A,26H
Set Register A for a FILE NOT OPEN error
48B1
OR A
Set flags for an error
48B2
RET
RETURN

48B3H – Restore Context After I/O Operation

48B3
POP BC
Restore the contents of Register BC from the Stack
48B4
POP DE
Restore the contents of Register DE from the Stack
48B5
POP HL
Restore the contents of Register HL from the Stack
48B6
POP IX
Restore the contents of Register IX from the Stack
48B8
RET
Return

48B9H – Test for EOF. This routine returns A with: 0=NOT EOF, 1C=EOF, 1D=Beyond EOF

48B9
LD L,(IX+0AH)
IX+0AH = the LSB of the next sector number. Sent to L
48BC
LD H,(IX+0BH)
IX+0BH = the MSB of the next sector number. Sent to H
48BF
LD C,(IX+05H)
IX+05H = byte offset in sector buffer to the current record. Sent to C
48C2
LD A,H
Put Register H (the MSB of the next sector number) into Register A for processing/testing
48C3
CP (IX+0DH)
IX+0DH = the MSB of the EOF sector held in the DCB. Compare the MSB of the next sector number with the MSB of the EOF sector
48C6
If NZ is set then they are not the same, so JUMP to 48D7H
48C8
LD A,L
If we are here, then the MSB of the next sector = the MSB of the EOF sector, so we need to test the LSB’s. First Put Register L (the LSB of the next sector number) into Register A for processing/testing
48C9
CP (IX+0CH)
IX+0CH = the LSB of the EOF sector held in the DCB. Compare the LSB of the next sector number with the LSB of the EOF sector
48CC
If NZ is set then they are not the same, so JUMP to 48D7H
48CE
DEC C
If we are here then the next sector number = the EOF sector number. DECrement Register C by 1 to adjust the offset to the current record
48CF
GOSUB to 4B97H to test for EOF in the sector buffer
48D2
If NZ is set, then it was not the EOF in the sector buffer, so SKIP the next 2 OPCODES
48D4
OR 1CH
Set A to 1CH to signal an EOF Flag
48D6
RET
RETURN
48D7
LD A,1DH
Set A to 1DH to signal a PAST EOF error
48D9
RET NC
If NC is set then return with error
48DA
XOR A
Clear all flags to signal NO EOF
48DB
RET
RETURN

48DCH – Locate the GAP for the Current Record Number

48DC
LD L,(IX+0AH)
IX+0AH = the LSB of the next sector number. Sent to L
48DF
LD H,(IX+0BH)
IX+0BH = the MSB of the next sector number. Sent to H
48E2
LD A,05H
Put a 05H into Register A. 05H will be the modulo number because we either need a 0 or a 5 to get to the relative gran
48E4
GOSUB to 4B84 to divide by Register A. This will give the relative granule number in HL and the sector number in the granule in A
48E7
LD (49AAH),A
Put the sector number in granule into 49AAH
48EA
PUSH IX
Store the Index Register IX (the DCB) to the Stack
48EC
EX (SP),HL
Swap the contents of the Stack Pointer with Register Pair HL so that HL will have the DCB address (formerly of IX) and the stack will have the relative granule number sought
48ED
LD BC,000FH
Put a 15, which is the offset of the DCB start to the first GAP, into Register Pair BC
48F0
ADD HL,BC
Add 15 to Register Pair HL, so that Register Pair HL now points to the first GAP in the DCB
48F1
POP BC
Restore the relative granule number for the record from the stack into Register Pair BC
48F2
LD A,05H
Put a 05H, which is the number of GAP pairs to search, into Register A.
48F4
LD DE,0000H
Set up DE with 0000H for math
48F7
PUSH AF
Save the contents of Register Pair AF (the number of GAP’s tested) to the stack
48F8
DEC HL
HL=HL-1 to backspace to the first byte of the GAP
48F9
LD A,(HL)
Put the character pointed to by HL into Register A. On the first run, this would be byte l of first extent pair (track no.)
48FA
INC HL
Bump Register Pair HL by 1 to now point to byte 2 current extent pair / number of consecutive granules assigned
48FB
INC A
BUMP Register A by 1 to set up to test for the end of the GAP
48FC
If the Z Flag is SET then we are at the end of an extant (with a 255) so JUMP down to 4909H
48FE
PUSH HL
Save the contents of Register Pair HL to the stack. At this point in the loop HL contains DCB + 15 + x, which is the current GAP address. X can be 0-4.
48FF
4900
LD H,D
LD L,E
HL = DE. Puts the cumulative granule total into HL
4901
XOR A
Clear all flags in preparation for a CARRY operation
4902
SBC HL,BC
SUBTRACT WITH CARRY to subtract DE (requested granule number) and the carry flag from HL (cumulative granule total found so far)
4904
If the CARRY FLAG is set then the requested relative granule number is greater than the total granules counted thus far, so JUMP down to 4915H
4906
POP HL
Restore the DCB address from the stack into Register Pair HL.
4907
If the Z Flag is SET, then JUMP to 496AH to compute the absolute track and sector number if the requested granule is in range of of the current GAP
4909
INC HL
Bump Register Pair HL by 1 to byte 3 of current extent pair (track no.)
490A
POP AF
Restore the count of extent sets already examined from the stack into Register Pair AF
490B
DEC A
DECrement Register A by 1 for purposes of this loop
490C
If the Z Flag is SET then all sets in this directory entry were tested but the GAP for this granule was not found, so JUMP down to to 4928H
490E
LD E,(HL)
Put the byte pointed to by HL into Register E. This will put the cumulative granule count up through this GAP in DE
490F
INC HL
Bump Register Pair HL by 1 to move to the next GAP
4910
LD D,(HL)
Put the byte pointed to by HL (MSB of the granule count) into Register D
4911
INC HL
Bump Register Pair HL by 1 to point to the first byte of the next GAP
4912
INC HL
Bump Register Pair HL by 1 to point to the second byte of the next GAP
4913
LOOP back to 48F7H

We are here if the relative granule number is greater than the sum of the granules allocated.

4915
INC H
BUMP Register H by 1. This would give us a 1 if the difference greater than 256
4916
LD A,L
Put Register L (the negative of the requested relative granule number) into Register A for processing/testing
4917
POP HL
Restore the address of the second byte of the extent from the stack into Register Pair HL
4918
If the difference between the requested and cumulative granule is greater than 256 then NZ will be set and the granule can’t be in this GAP, so JUMP back to 4909H

If the difference is less than 256, compare it to the number of granules assigned to the current GAP because the relative granule may fall within the next GAP.

491A
PUSH DE
Save the contents of Register Pair DE (granule total) to the stack
491B
LD E,A
Put Register A (negative of requested granule) into Register E
491C
LD A,(HL)
Put the total numnber of of granules -1 starting with this track into A
491D
AND 1FH
MASK Register A against 0001 1111 to keep only bits 0-4. This will isolate the granule count.
491F
ADD A,E
Add E to A, which basically compares the requested granule to the number in this GAP. This will trigger a CARRY
4920
LD A,E
Restore the negative of the requested granule number to A in case we found the needed GAP
4921
POP DE
Restore the number of of granules passed so far from the stack into Register Pair DE
4922
If NC is set then the requested granule is not in this GAP, so JUMP back to to 4909H to get the next GAP
4924
NEG
NEGATE Register A (the same as subtracting A from 0). This will convert A (the relative granule number) to positive.
4926
Jump to 496AH to compute the absolute track and sector number

If we are here, then the elative granule is not in the current GAP so we need to search directory entries belonging to the file for a GAP containing the relative granule number being sought.

4928
GOSUB to 49B5H to search the directory for the GAP owning relative granule. On return from routine, HL = first byte of the GAP owning the relative granule
492B
RET NZ
If the NZ flag is set then there was a disk error while doing that search, so RETURN to the caller
492C
PUSH HL
Save the contents of Register Pair HL (track number for the requested granule) to the stack
492D
492E
LD H,B
LD L,C
HL = BC (the requested relative granule number)
492F
XOR A,A
Clear status in preparation for math
4930
SBC HL,DE
Subtract DE (total granules addressable through this GAP) and the carry flag from HL (requested relative granule number). The result of this operation gives the number of granules in current GAP which will be translated into numbers of tracks that will be added to the starting track number for GAP giving track number for relative granule number.
4932
LD A,L
Put Register L into Register A for processing/testing
4933
LD (499AH),A
Put A into 499AH to update the track address
4936
POP HL
Restore the count of granules/starting track for this granule from the stack into Register Pair HL
4937
PUSH DE
Save the contents of Register Pair DE (cumulative granule count) to the stack
4938
PUSH IX
Save the DCB address to the Stack
493A
EX (SP),HL
Exchange the contents of the stack with HL, so that HL now holds the DCB address and the stack now holds the granule number for this
493B
LD DE,000EH
Put a 14 into DE for the start of the index for the GAP
493E
ADD HL,DE
Add the DE offset to the start of the DCB
493F
POP DE
Restore the count/track number from the stack into Register Pair DE

Search the DCB for the GAP matching the number returned From 49B5H. When this routine finishes, HL will hold the track and granule count owning the relative granule. DE will hold the cumulative granule count addressable through this track.

4940
LD B,05H
Set Register B to 5, which is the maximum nunber of GAPs
4942
LD A,(HL)
Put the first byte of a GAP into Register A. We are searching GAPs looking for one that matches the one in DE
4943
INC HL
Bump Register Pair HL by 1
4944
CP E
Does the first byte in the GAP (i.e., the track number) match Register E?
4945
If there is no match, then JUMP down to 494DH
4947
LD A,(HL)
Put the second byte of a GAP (pointed to by HL) into Register A
4948
XOR A,D
Does the second byte in the GAP match Register D?
4949
AND 0E0H
MASK Register A against 1110 0000 to keep only bits 5-7
494B
If the Z Flag is SET then A matched D so JUMP to 4965H
494D
DEC B
If we are here, we did not get a match against DE so we need to keep going. First, though, we need to make sure we didn’t run out of GAPs so DECrement Register B by 1
494E
If the Z Flag is SET then we have tested all the GAPs, then JUMP to 4955H to go move the GAP’s in the DCB down and move track/count and cumulative total returned by 4985 to the first GAP entry in the DCB
4950
4951
4952
INC HL
INC HL
If we are here then there are more GAPS to search, so Bump Register Pair HL by 3 to get to the next GAP and skip over the two byte totals that follow each GAP
4953
Now that HL points to the next GAP, LOOP back to 4942H and test that one

Move GAP’s in DCB down one entry

4955
PUSH DE
Save the contents of Register Pair DE (cummulative granule count) to the stack
4956
EX DE,HL
SWAP Register Pair DE with Register Pair HL so that DE holds the end of the GAP’s in the DCB. We don’t care about HL in this swap.
4957
LD HL,0FFFCH
HL = -4
495A
ADD HL,DE
Add -4 to the end of the GAP’s in the DCB so that HL now holds the ending address of the GAP’s in the DCB
495B
LD BC,000CH
Set up a LDDR by loading BC to hold 12. 12 is 3 sets of 4 bytes. DE is the address of the 5th GAP entry in DCB
495E
LDDR
Move all GAP entrys down one. LDDR Transfers data from the memory location pointed to by HL to the memory location pointed to by DE. Then HL, DE, and BC are decremented.
4960
EX DE,HL
SWAP Register Pair DE with Register Pair HL. HL will now hold the DCB address of the GAP for this granule
4961
POP BC
Restore count/track numnber for the requested granule from the stack into Register Pair BC
4962
POP DE
Restore requested granule no from the stack into Register Pair DE
4963
If we are here, then we found the matching granule so JUMP to 498AH to update the DCB and compute the absolute track and sector number
4965
LD (HL),D
Save the granule track count to the DCB
4966
EX DE,HL
SWAP Register Pair DE with Register Pair HL so that DE holds the DCB address and HL holds the GAP entry
4967
POP AF
Clear the stack
4968
JUMP to 4993H to compute the absolute disk address.

496AH – This routine converts the granule number and GAP into an absolute track/sector number

496A
LD (499AH),A
Store the number of granules in the GAP in memory location 499AH
496D
LD B,(HL)
Put the second byte of the extent pair (number of consecutive sectors) into Register B
496E
DEC HL
Backspace the DCB to point to the track number
496F
LD C,(HL)
Put the first byte of the extent pair (track number) into Register C
4970
INC HL
Bump Register Pair HL by 1 (to point to the second byte again)
4971
POP AF
Restore the count of extent pairs examined from the stack into Register Pair AF
4972
CPL
Make Register A negative. The CPL instruction rotates the contents of A right one bit position. The contents of bit 0 are copied to the carry flag and the previous contents of the carry flag are copied to bit 7.
4973
ADD A,04H
Subtract 4 from Register A
4975
If the first or second extents give a 1-4 then JUMP to 4991H
4977
INC A
BUMP Register A by 1
4978
4979
RLCA
RLCA
Multiply A by 4 so we will have 16/12/8/4 (if it was 4/3/2/1) which will be the number of bytes to transfer
497A
PUSH BC
Save the contents of Register Pair BC (target GAP track and count) to the stack
497B
PUSH DE
Save the contents of Register Pair DE (cumulative granule total) to the stack
497C
EX DE,HL
SWAP Register Pair DE with Register Pair HL so that DE holds the address of the GAP owning the relative gran. We don’t care about HL
497D
LD HL,0FFFCH
HL = -4
4980
ADD HL,DE
Subtract 4 from the address of the GAP owning the relative granule and put that into HL, so HL now holds the address of the second byte of the previous extent pair
4981
LD B,00H
Prepare for a LDDR by setting B to the byte count of GAPs to move down
4983
LD C,A
Prepare for a LDDR by putting the modified A (16/12/8/4) into Register C
4984
LDDR
Move all GAPs after the target GAP down one. LDDR Transfers data from the memory location pointed to by HL to the memory location pointed to by DE. Then HL, DE, and BC are decremented.
4986
EX DE,HL
SWAP Register Pair DE with Register Pair HL so that HL holds the ending address for target GAP. DE is not relevant.
4987
POP DE
Restore the cumulative granule count from the stack into Register Pair DE
4988
POP BC
Restore the target GAP (track/count) from the stack into Register Pair BC
4989
NOP
NO OPERATION
498A
LD (HL),B
Put the granule count for target GAP into the DCB
498B
DEC HL
HL = HL -1 to move back 1 entry in the DCB
498C
LD (HL),C
Put the track number for target GAP into the DCB
498D
DEC HL
HL = HL -1 to move back 1 entry in the DCB
498E
LD (HL),D
Put the MSB of the cumulative granule count into the DCB
498F
DEC HL
HL = HL -1 to move back 1 entry in the DCB
4990
LD (HL),E
Put the LSB of the cumulative granule count into the DCB
4991
LD H,B
Put the cumulative granule count for this GAP into Register H
4992
LD L,C
Put the track number for this GAP into Register H

Translate disk address

4993
LD A,H
Put Register H (cumulative granule count for this GAP) into Register A for processing/testing
4994
4995
4996
RLCA
RLCA
RLCA
RLCA rotates the bits in A (cumulative granule count for this GAP) left THREE positions, which is the same multiplying by 2, three times
4997
AND 07H
MASK Register A against 0000 0111 to keep only bits 0-2 to isolate the offset to first granule
4999
ADD A,00H
Add the number of granules past the first one to the target granule. Note, the value which is being added is modified in 496A and is the number of granules in the GAP
499B
LD E,A
Put Register A (which now holds the relative granule number within the GAP) into Register E
499C
AND 0FEH
MASK Register A against 11111110 to turn off bit 0 to control for overflow
499E
RRCA
Rotate the masked Register A right by 1 bit to isolate and right justify overflow for track increment
499F
LD (49AEH),A
Put Register A into (49AEH). This will be used later to add to the starting track number
49A2
LD A,E
Refetch relative granule number within GAP
49A3
AND 01H
Mask Register A against 00000001 to keep only the even/odd bit. We will then be able to compute the starting track offset (0 if even, -4 if odd)
49A5
LD E,A
Put Register A into Register E to help with math
49A6
49A7
49A8
RLCA
RLCA
ADD A,E
Multiply Register A by 5
49A9
ADD A,00H
And then add the sector number within the granule to get the absolute sector number within track. Note: 48E7H put the byte into 49AA instead of it being 00H
49AB
LD E,A
Put Register A (starting sector number on track) into Register E
49AC
LD A,L
Put Register L (lst byte of extent pair, the track) into Register A for processing/testing
49AD
ADD A,00H
Add the track increment for granule number within GAP to Register A. Note: 499FH put the byte into 49AE instead of it being 00H
49AF
LD D,A
Store the track number into Register D
49B0
LD C,(IX+06H)
Fetch the drive number for file from the DCB into Register C
49B3
XOR A
Clear all flags / good finish
49B4
RET
RETURN

49B5H – Finds the GAP for Relative Granule number held in BC by searching the directory entries belonging to a file. If there are overflow entries, read them as well. On Entry: BC=Granule Number. On Exit: L=Track, H=Count of Granules assigned to the track, and DE=cumulative number of granules thru this GAP.

49B5
PUSH BC
Save the contents of Register Pair BC (the requested granule number) to the stack
49B6
LD DE,0000H
Initialize the count of granules skipped
49B9
LD B,(IX+07H)
IX+07H = the sector number for the directory entry and offset. Fetch it into B
49BC
LD A,B
Put Register B (the sector number for the directory entry and offset) into Register A for processing/testing
49BD
LD (4ABEH),A
Store it into (4ABEH)
49C0
LD C,(IX+06H)
IX+06H = the drive number. Fetch it into C
49C3
GOSUB to 4AC1H to read the directory sector pointed to by BC into the buffer
49C6
LD BC,0016H
Set up BC to the offset to the extent pairs
49C9
ADD HL,BC
Add the offset to the extent pairs (held in BC) to the address of entry (held in HL). HL will then hold the address of the GAP extents
49CA
EX DE,HL
SWAP Register Pair DE with Register Pair HL so that DE holds the address of extent pairs
49CB
POP BC
Restore the requested granule number from the stack into Register Pair BC
49CC
RET NZ
Rmemeber back in 49C3 we read the directory? Well NOW we test for error. If the NZ flag is set there was an error, so RETURN to the caller
49CD
LD A,(DE)
Fetch byte one of the GAP from the directory sector and put it into Register A
49CE
CP 0FEH
Test that byte against 0FEH to test for overflow extent pair
49D0
If NC is set, then A is equal to or greater than the test value of 0FEH, meaning 0FE or 0FF. If so, either the end of extents or overflow GAP was found. JUMP to 49F1H
49D2
INC DE
If we are here, then we didn’t error out with END OF EXTENTS or OVERFLOW so Bump DE to point to the next byte in the GAP
49D3
LD A,(DE)
Fetch the contents being pointed to by DE; in this case the count of granules
49D4
PUSH HL
Save the contents of Register Pair HL (count of granules skipped) to the stack
49D5
AND 1FH
MASK Register A against 0001 1111 to keep only bits 0-4. This leaves A holding the granule count -1
49D7
INC A
BUMP Register A by 1 so that A holds the granule count
49D8
ADD A,L
Add the number granules skipped (held in L) to the current granule count (held in A)
49D9
LD L,A
Put the modified Register L (held in A) back into Register L
49DA
Did that math overflow L (which is the LSB), requiring us to update the MSB? If NC then no, so skip over the next instruction which bumps H
49DC
INC H
BUMP Register H by 1
49DD
PUSH HL
Save the contents of Register Pair HL (updated count of granules skipped) to the stack
49DE
DEC HL
Adjust granules skipped for comparison with requested granule number
49DF
XOR A
Clear carry flag for upcoming math
49E0
SBC HL,BC
Compare reguested granule (BC) with granule count thru this GAP (HL) by using subtraction so we can just check the CARRY FLAG
49E2
POP HL
Restore the true granule count from the stack into Register Pair HL
49E3
If NC is set from the subtraction, then the requested granule exists within this GAP range, so JUMP to 49E9H
49E5
INC DE
If we are here then the requested granule wasn’t found in this GAP so time to move to another GAP. Bump DE by 1 to point to the second byte of the next GAP
49E6
POP AF
Clear the Stack
49E7
LOOP back to 49CDH until we exit either due to 0FEH (49D0H exits), 0FFH (49D0H exits), or found GAP (49E3H exits)

If we are here, the requested granule exists in the GAP range we just found

49E9
POP HL
Restore the requested granule number from the stack into Register Pair HL
49EA
EX DE,HL
SWAP Register Pair DE with Register Pair HL so that DE holds the accumulated gran count and HL holds the address of the 2nd byte for GAP entry wanted
49EB
LD A,(HL)
Fetch the byte pointed to by HL (the count of granules assigned from this track) into Register A
49EC
DEC HL
Backspace to the first byte of GAP
49ED
LD L,(HL)
Fetch the track number from (HL)
49EE
LD H,A
Put Register A (count of granules assigned) into Register H
49EF
XOR A,A
Clear all flags to signal no error
49F0
RET
RETURN

Jumped to by 49D0H if we had either an end of extents or overflow was found.

49F1
PUSH BC
Save the contents of Register Pair BC (requested granule number) to the stack
49F2
EX DE,HL
SWAP Register Pair DE with Register Pair HL so that DE holds count of granules skipped and HL holds the address of the directory sector entry
49F3
If NZ is set then we are here because of 0FFH and the GAP is assigned. JUMP to 49F9H
49F5
INC HL
If we are here then an overflow was indicated, so Bump Register Pair HL by 1 which will point HL to the offset to sector containing overflow entry
49F6
LD B,(HL)
Fetch the offset to sector containing overflow entry into Register B
49F7
JUMP to 49BCH to locate the overflow GAP’s
49F9
If we are here, then the GAP was assigned, so GOSUB to 4A00H to assign the next GAP
49FC
POP BC
Restore requested granule number from the stack into Register Pair BC
49FD
RET NZ
If the NZ flag is set then we had an error from the assignment call, so RETURN to the caller
49FE
This restarts the whole scan. Since we have now just assigned the GAP it wants it should exit out in short order at 49F0H

4A00H – Granule Assignment Routine.

On entry: BC = requested granule number (target granule), DE = Number of granules presently assigned, and HL = Address of file entry in directory sector.

4A00
PUSH BC
Save the contents of Register Pair BC (target granule number) to the stack
4A01
LD B,(IX+07H)
Fetch the overflow pointer to directory entry from IX+07H
4A04
LD C,(IX+06H)
Fetch the the drive number from IX+06H
4A07
GOSUB to 4AF0H to read the GAT sector for the specified drive into 4D00H
4A0A
POP BC
Restore target granule number from the stack into Register Pair BC
4A0B
RET NZ
If the NZ flag is set then there was an error reading the GAT sector, so RETURN to the caller
4A0C
PUSH HL
Save the contents of Register Pair HL (Address of file entry in directory sector) to the stack
4A0D
LD H,B
Put the MSB of target granule into Register H
4A0E
LD L,C
Put the LSB of target granule into Register L
4A0F
XOR A
Clear all flags, including the CARRY FLAG, for math
4A10
SBC HL,DE
Subtract DE (Number of granules presently assigned) and the carry flag from HL (the target granule). The result is stored in HL
4A12
4A13
LD B,H
LD C,L
BC = HL (the difference between requested and current granule number)
4A14
INC BC
BC = BC + 1. BC should now be the relative granule in last GAP and the number of granules to allocate
4A15
POP DE
Restore the directory sector entry from the stack into Register Pair DE
4A16
INC DE
Bump DE to point to the second byte of the last GAP entry
4A17
INC BC
Bump BC to the number granules to allocate
4A18
LD A,(IX+07H)
Fetch the sector number for the directory entry from IX+07H

The next bit of math is to isolate and then calculate the advanced starting position in the GAT

4A1B
AND 07H
MASK Register A against 0000 0111 to keep only bits 0-2
4A1D
RLCA
Multiply by 2
4A1E
RLCA
Multiply that by 2
4A1F
LD L,A
Put the modified Register L (held in A) back into Register L. This will be the start of forming the beginning GAT search address
4A20
LD H,4DH
Put 4DH as the MSB into Register L to finish forming the GAT search address in a buffer starting from 4D00H
4A22
PUSH BC
Save the contents of Register Pair BC (number of granules to allocate and relative granule within GAP) to the stack
4A23
LD A,E
Put the LSB of the current GAP address into Register A
4A24
AND 1EH
MASK the LSB of the current GAP address against 00011110 to isolate the first GAP word address
4A26
CP 16H
Compare the masked value against 16H
4A28
If the masked value was 16H the Z Flag will be SET and the first GAP assignment was found, so JUMP to 4A6EH
4A2A
4A2B
DEC E
DEC E
Otherwise, we need to backspace two bytes to the previous GAP, so DECrement Register E twice
4A2C
LD A,(DE)
Fetch the number of consecutive granules -1 into Register A
4A2D
AND 1FH
MASK Register A against 0001 1111 to keep only bits 0-4 and therefore isolating the granule count
4A2F
INC A
BUMP Register A by 1 so that A points to the true number of consecutive granules
4A30
LD C,A
Store the true number of consecutive granules into Register C
4A31
CP 20H
Compare the true number of consecutive granules in Register A against 20H (which would mean that the granule count field is full)
4A33
If the Z Flag is SET then the GAP is full and we need to assign a new GAP, so JUMP to 4A54H to assign a new GAP
4A35
LD A,(DE)
If we are here then the GAP was not full, so we need to try to assign a granule. First, refetch the second byte – count of consecut1ve granules already assigned
4A36
AND 0E0H
MASK Register A against 1110 0000 to keep only bits 5-7 to isolate the offset to first granule in the GAP
4A38
4A39
4A3A
RLCA
RLCA
RLCA
Three rotates will leave Register A holding only a 0 or a 1 depending on the starting sector offset
4A3B
ADD A,C
Add the number of consecutive grans to get the total granules assigned to the GAP
4A3C
LD B,A
We need to convert granules to tracks. First, copy the total granules assigned to the GAP from Register A into Register B
4A3D
SRL B
Then divide them by 2
4A3F
AND 01H
The mask Register A to isolate the bit that tells us which granule must be assigned next to be consecutive with current GAP
4A41
LD C,A
Save that mask for the specific required granule into Register C
4A42
DEC DE
Backspace DE to point to the first byte of the GAP (which holds the starting track number)
4A43
LD A,(DE)
Fetch the starting track number from (DE)
4A44
INC DE
Bump DE to point to the second byte of the GAP
4A45
ADD A,B
Now we need to figure out the track number to use as an index into the GAP. We add the number of of consecutive tracks now assigned to the GAP to get next track number to assign.
4A46
LD L,A
Put the modified Register L (held in A) back into Register L to start forming the GAT address for the needed track
4A47
LD H,4DH
Put 4DH as the MSB into Register L to finish forming the GAT search address in a buffer starting from 4D00H. At this point HL will be set to point to the last track assigned in current the GAP
4A49
CP 23H
Test against 23H to see if the end of the GAT was reached
4A4B
If NC is set then we have reached the end of the GAT, so we need to start another GAP – so JUMP to 4A54H.
4A4D
LD A,C
Put Register C (the bit needed in the GAT entry for consecutive assignment of this track) into Register A for processing/testing
4A4E
LD B,(HL)
Fetch the track available word from (HL)
4A4F
GOSUB to 4B5DH to test if the needed granule is actually available
4A52
If the Z Flag is SET then the neeed granule is actually available, so JUMP to 4A9CH
4A54
4A55
INC E
INC E
If we are here, the granule we needed wasn’t available, so we need to assign a new GAP. First, BUMP Register E by 2 to point to the second byte of the next GAP
4A56
LD A,E
We need to test for the end of GAP’s in this directory entry, so copy the pointer to the second byte of the next GAP into Register A
4A57
AND 1EH
MASK Register A against 00011110 to isolate the LSB of the address for the next GAP
4A59
CP 1EH
Compare the MASKED Register A to 1EH
4A5B
If NZ is set then this is not last pair of GAP for this entry, so JUMP to 4A6EH. Otherwise, we have to create a new GAP entry
4A5D
GOSUB to 4AB6H to rewrite the GAT and the directory entry
4A60
POP BC
Clear stack in case of error
4A61
RET NZ
If the NZ flag is set then there was an error rewriting the GAT and/or directory entry so RETURN to the caller
4A62
PUSH BC
Save the contents of Register Pair BC (target granule number) to the stack
4A63
GOSUB to 4A6BH to build an overflow directory entry. This routine simply calls SYS2/SYS with option 3.
4A66
POP BC
Restore target granule number from the stack into Register Pair BC
4A67
RET NZ
If the NZ flag is set then there was an error while creating the overflow directory entry, so RETURN to the caller
4A68
JUMP to 49B5H to restart the whole GAP assignment routine, which will quickly exit now that we are here

This routine calls on SYS2/SYS to build an overlow entry.

4A6B
LD A,0B4H
Setup Register A to accomplish a call to SYS2/SYS with option 3 selected
4A6D
RST 28H
Load and Execute the overlay selected by Register A

This routine assigns the granule for a new GAP

4A6E
LD B,02H
Set up B for a DJNZ loop. It is set to two to represent the number of times to scan the allocation table
4A70
LD A,L
Put Register L (current track number) into Register A for processing/testing
4A71
CP 23H
Compare Register A (current track number) against 23H to see if we have reached the end of the GAT
4A73
If NC is set then Register A is equal to or greater than the test value of 23H, so the end of the GAT was reached and testing is over, so JUMP out to 4A7CH
4A75
LD A,(HL)
If we are here, then we are not at the end of the GAT, so fetch the GAT entry (pointed to by HL) into Register A
4A76
INC A
BUMP Register A by 1 to see if there are still granules available
4A77
If the BUMP results in NZ then there are still granules available, so JUMP to 4A89H
4A79
INC L
If we are here, then there are no more granules available, so BUMP Register L by 1 to point to the next entry
4A7A
LOOP back to 4A70H to keep checking until the table is exhausted
4A7C
LD L,00H
Reset L to facilitate restarting the scan from the GAT table beginninq
4A7E
LOOP back to 4A70H until B is zero (i.e., twice)
4A80
POP BC
If we are here, we have looped and looped, so we need to write GAT table and updated directory entry back to disk. First Register Pair BC
4A81
GOSUB to 4AB6H to do that
4A84
RET NZ
If the NZ flag is set then that update to disk produced an error, so RETURN to the caller
4A85
LD A,1BH
If we are here then the disk space was full! Set Register A to 1BH to signal DISK SPACE FULL error status
4A87
OR A
Set the flags for an error
4A88
RET
RETURN

This routine makes the initial GAP Byte 2 Assignment the granule for a new GAP

4A89
LD A,0FFH
Set the number of consecutive granules assigned to -1
4A8B
LD (DE),A
Put the value held in Register A into the 2nd byte of current GAP memory location (pointed to by DE)
4A8C
LD C,00H
Set C to start an OPCODE for BIT 0,B
4A8E
LD B,(HL)
Refetch the GAT entry to test BIT 0 if
4A8F
LD A,C
Put Register C into Register A in preparation of a BIT X,B OPCODE
4A90
GOSUB to 4B5DH to test for 1st or 2nd granule in track being assigned
4A93
If the Z Flag is SET then the starting sector number is zero meaning that the first granule is already assigned, so JUMP to 4A9CH
4A95
LD A,(DE)
if we are here then the second granule in track is assigned, so fetch the second byte of the GAP
4A96
ADD A,20H
Set Bit 5
4A98
LD (DE),A
Put the it back
4A99
INC C
BUMP Register C by 1 so that BC points to the next bit in the GAT entry, which surely should be the availible granule
4A9A
Rerun the test by jumping back to 4A8F. With the work just done, it should exit quickly and branch to 4A90H
4A9C
LD A,C
Put Register C (the value for the bit to be turned on … 0=Bit 0, 1= Bit 1) into Register A for processing/testing
4A9D
GOSUB to 4639H to turn on that Bit in Register A
4AA0
OR A,(HL)
Combine the bit-modified Register A with the rest of the GAT entry
4AA1
LD (HL),A
Save that bit-modified GAT entry back to the GAT
4AA2
DEC E
DECrement Register E by 1 to point to the first entry of the GAP
4AA3
LD A,(DE)
Fetch the first entry of the GAP (i.e. the track number or FFH)
4AA4
INC A
BUMP Register A by 1. This tests for new track assignment or extension to existing track via a call from 4A52H
4AA5
If NZ is set, then it is an EXTENSION to existing track, so JUMP to 4AA9H
4AA7
LD A,L
If we are here, then i tis a NEW TRACK ASSIGNMENT, so put Register L (LSB of the current GAT address, i.e. the track number for this GAP) into Register A for processing/testing
4AA8
LD (DE),A
Put the value held in Register A into the memory location pointed to by DE
4AA9
INC E
BUMP Register E by 1 to point to the second byte of the GAP
4AAA
LD A,(DE)
Fetch the second byte of the GAP
4AAB
INC A
BUMP Register A by 1 to denote an increase in the number of assigned granules
4AAC
LD (DE),A
Put the that value back into DE.
4AAD
POP BC
Restore the count of grans to allocate from the stack into Register Pair BC
4AAE
DEC BC
Decrement and test to see if we are through
4AAF
PUSH BC
Save the contents of Register Pair BC (the reduced count of granules left to allocate) to the stack
4AB0
4AB1
LD A,B
OR C
The Z-80 cannot test BC for Z or NZ so this trick is commonly used … A will be 0 only if both B and C were zero
4AB2
If NZ is set then we are not finished, so JUMP back to 4A2CH to reallocate another
4AB5
POP BC
If we’re here we’re done, so first clear the stack
4AB6
LD C,(IX+06H)
IX+06H = the drive number. Fetch the Drive Number into Register C
4AB9
GOSUB to 4B03H to write the GAT table (Track 11, Sector 0) from the memory buffer
4ABC
RET NZ
If the NZ flag is set then there was an error writing the GAT table, so RETURN to the caller
4ABD
LD B,00H
Put the sector number and offset into Register B. Note: This value is changed by the instruction at 49BDH
4ABF
GOSUB to 4AD6H to write the updated directory entry and return to caller

4AC1H – READ the Directory Entry for Directory Sector in B.

4AC1
PUSH BC
Save the contents of Register Pair BC (directory sector number/drive number) to the stack
4AC2
PUSH DE
Save the contents of Register Pair DE (caller’s DE) to the stack
4AC3
GOSUB to 4B1EH to get directory track/sector number (specified in DE)
4AC6
If that routine returns NZ then the sector number was out of range, so JUMP to 4AD1H
4AC8
PUSH HL
Save the contents of Register Pair HL (the caller’s HL) to the stack
4AC9
LD L,00H
Force buffer addr to page boundry
4ACB
Read the sector held in Register E of the track held in D on the drive held in C into the buffer pointed to by HL
4ACE
POP HL
Restore the caller’s HL from the stack into Register Pair HL
4ACF
If the Z Flag is SET then there was no error in the read so, then JUMP to 4AD3H
4AD1
LD A,11H
If we’re here there was an error, so load up Register A with a DIRECTORY READ ERROR
4AD3
POP DE
Restore caller’s DE from the stack into Register Pair DE
4AD4
POP BC
Restore sector number/drive number from the stack into Register Pair BC
4AD5
RET
RETURN

4AD6H – WRITE the Directory Entry for Directory Sector in B.

4AD6
PUSH BC
Save the contents of Register Pair BC (directory sector number/drive number) to the stack
4AD7
PUSH DE
Save the contents of Register Pair DE (caller’s DE) to the stack
4AD8
GOSUB to 4B1EH to get directory track/sector number (specified in DE)
4ADB
If that routine returns NZ then the sector number was out of range, so JUMP to 4AEDH
4ADD
LD L,00H
Force buffer addr to page boundry
4ADF
GOSUB to 46EFH to write the directory sector
4AE2
If NZ is set, there was a write error, so JUMP to 4AEBH
4AE4
GOSUB to 46F3H to read the sector back to confirm the write
4AE7
CP 06H
That routine will return 06H for a good read, so test Register A against 06H
4AE9
If the Z Flag is SET the readback matched, so skip the next instruction which would set an error code
4AEB
LD A,12H
Set Register A with a directory write error
4AED
POP DE
Restore caller’s DE from the stack into Register Pair DE
4AEE
POP BC
Restore sector number/drive number from the stack into Register Pair BC
4AEF
RET
RETURN

4AF0H – READ the GAT sector for the drive pointed to by Register C

4AF0
PUSH DE
Save the contents of Register Pair DE (caller’s DE) to the stack
4AF1
PUSH HL
Save the contents of Register Pair HL (caller’s HL) to the stack
4AF2
GOSUB to 4B55H to get the directory track number for the disk drive pointed to by Register C
4AF5
LD E,00H
Set the sector number to 0 (as the GAT sector is sector 0)
4AF7
LD HL,4D00H
Point HL to a buffer starting at 4D00H
4AFA
Read the sector held in Register E of the track held in D on the drive held in C into the buffer pointed to by HL
4AFD
POP HL
Restore caller’s HL from the stack into Register Pair HL
4AFE
POP DE
Restore caller’s DE from the stack into Register Pair DE
4AFF
RET Z
If the Z flag is set then there was no error so RETURN to the caller
4B00
LD A,14H
Set Register A to a GAT READ error
4B02
RET
RETURN

4B03H – WRITE the GAT sector for the drive pointed to by Register C

4B03
PUSH DE
Save the contents of Register Pair DE (caller’s DE) to the stack
4B04
PUSH HL
Save the contents of Register Pair HL (caller’s HL) to the stack
4B05
GOSUB to 4B55H to get the directory track number for the disk drive pointed to by Register C
4B08
LD E,00H
Set the sector number to 0 (as the GAT sector is sector 0)
4B0A
LD HL,4D00H
Point HL to a buffer starting at 4D00H
4B0D
Write the sector held in Register E of track 11 into the buffer pointed to by HL
4B10
IF NZ is set then there was an error writing the GAT so JUMP to 4B19H
4B12
GOSUB to 46F3H to read the sector back to confirm the write
4B15
CP 06H
That routine will return 06H for a good read, so test Register A against 06H
4B17
If the Z Flag is SET the readback matched, so skip the next instruction which would set an error code
4B19
LD A,15H
Set Register A with a GAT write error
4B1B
POP HL
Restore caller’s HL from the stack into Register Pair HL
4B1C
POP DE
Restore caller’s DE from the stack into Register Pair DE
4B1D
RET
RETURN

4B1EH – Prepare Registers for Directory I/O

Entry: B = the system sector number, C = disk drive number

Exit: DE = Track/Sector number, HL = Buffer Address for the file entry, AF = Non-Zero is sector out of range

4B1E
GOSUB to 4B55H to get the directory track number for the disk drive pointed to by Register C
4B21
LD A,B
Put Register B (offset for the file entry in directory) into Register A for processing/testing
4B22
AND 0E0H
MASK Register A against 1110 0000 to keep only bits 5-7 to isolate the file entry number
4B24
LD L,A
Put the LSB of buffer address for the file (held in A) into Register L
4B25
LD H,42H
Set the MSB of the buffer address to 42H
4B27
XOR B
XOR A against B to isolate the sector number (bits 0-4)
4B28
CP 08H
Compare A against 08 to test for sector out of range
4B2A
If NC is set, A was greater than or equal to the comparison value of 08 so the sector is out of range. JUMP to 4B31H
4B2C
ADD A,02H
Form a true sector number by adding 2 to the prior masked Register A
4B2E
LD E,A
Put Register A (true sector number) into Register E
4B2F
XOR A
Clear all flags to signal no error
4B30
RET
RETURN
4B31
LD A,10H
Set up Register A to a SECTOR NUMBER OUT OF RANGE error
4B33
OR A
Set the flags for an error
4B34
RET
RETURN

4B35H – Routine to Call the Disk Driver

Entry: DE = Track/Sector number, HL = Buffer Address, C = Drive number

4B35
GOSUB to 46DDH to call the disk driver for read the track/sector pointed to by DE from the drive pointed to by C into the buffer at HL
4B38
SUB A,06H
Subtract 06H from A to get either a Z or NZ to test for normal operation
4B3A
RET Z
If the Z flag is set there was no error so RETURN to the caller
4B3B
ADD A,06H
Restore A by adding 06H back
4B3D
RET NZ
If the NZ flag is set, then RETURN to the caller
4B3E
PUSH DE
Save the contents of Register Pair DE (disk address where the error occurred) to the stack
4B3F
LD DE,0000H
Point DE to Track 0, Sector 0
4B42
GOSUB to 46DDH to call the disk driver for read the track/sector pointed to by DE from the drive pointed to by C into the buffer at HL
4B45
POP DE
Restore the address of the disk error from the stack into Register Pair DE
4B46
RET NZ
If the NZ flag is set there was an error reading the boot sector, so RETURN to the caller with a bad status
4B47
PUSH HL
Save the contents of Register Pair HL (original buffer address) to the stack
4B48
4B49
INC HL
INC HL
Bump Register Pair HL by 2 to get to byte 2 of the buffer
4B4A
LD D,(HL)
Fetch the directory track number (2nd byte of the sector) from boot loader and put it into Register D
4B4B
LD A,04H
Put an 04 into A to prepare for math. In this case, it is the offset to the directory table.
4B4D
ADD A,C
Add the drive number to the directory offset
4B4E
LD L,A
Put that value into the LSB of the buffer to form the location of the directory table address for the drive
4B4F
LD H,43H
Set the MSB of the buffer to 43H
4B51
LD (HL),D
Save the directory track number into the buffer
4B52
POP HL
Restore the callers buffer address from the stack into Register Pair HL
4B53
Jump back to the top of this routine

4B55H – Routine to Return Directory Track Nwnber for Unit C

4B55
LD A,04H
Load Register A with the offset to the directory table (i.e., 4)
4B57
ADD A,C
Add the offset to the directory table to the drive number held in C
4B58
LD L,A
Put the modified Register A into Register L to form the LSB of the buffer
4B59
LD H,43H
Set the MSB of the HL buffer to 43H
4B5B
LD D,(HL)
Fetch the directory track numnber for the drive number held in Register C
4B5C
RET
RETURN

4B5DH – Build and execute an instruction to test the bit held in Register A for the quantity held in Register B.

4B5D
AND 07H
MASK Register A against 0000 0111 to keep only bits 0-2, which as the bits to be tested
4B5F
4B60
4B61
RLCA
RLCA
RLCA
Rotate the bits 3 times to the left to form the first part of the OPCODE
4B62
OR 40H
Mask Register A against 40H to build the rest of the OPCODE. The BIT opcodes start at 40H.
4B64
LD (4B68H),A
Put the calculted opcode into 4B68, which is the 2nd byte of the immediately following OPCODE
4B67
BIT 0,B
Execute the bit test
4B69
RET
RETURN

4B5DH – Compute the sector number for a record number held in DE with a record length held in A.

The math is RECORD SIZE x RECORD NUMBER / 256.

4B6A
PUSH BC
Save the callers BC to the stack
486B
EX DE,HL
Swap DE and HL so that HL now holds the record number
486C
LD C,A
Copy the record length to Register C
486D
LD HL,0000H
Set HL to 0000 for math. In this case, it will be multiplying the record length by the record size by using the “Shift Add” method
4B70
LD A,L
Initialize the product as zero
4B71
LD B,08H
Set Register B to the maximum shift count of 8
4B73
ADD HL,HL
HL = HL + 1. This shifts the product left once for each bit tested
4B74
RLA
RLA rotates the contents of A left one bit position. The contents of bit 7 are copied to the carry flag and the previous contents of the carry flag are copied to bit 0
4B75
RLC C
RLC C rotates the contents of C left one bit position. The contents of bit 7 are copied to the carry flag and bit 0. This will test for the ON bit in the record length
4B77
If NC is set, then the ON bit wasn’t found, so jump forward a few instructions
4B79
ADD HL,DE
If we are here, the ON Bit was found, so add the record number to the product
4B7A
ADC A,00H
Pick up any possible CARRY from L
4B7C
Loop back t0 4B73H until 8 record length bits tested
4B7E
LD C,A
Put the MSB of the product into Register C
4B7F
LD A,L
Put the Remainder (which is the offset in sector of record) into Register A
4B80
LD L,H
Put the LSB of the product into Register L
4B81
LD H,C
Put the MSB of the product into Register H
4B82
POP BC
Restore the caller’s BC from the stack
4B83
RET
RETURN

4884H – This routine performs MODULO division by shifting the most significant bit out of the dividend (held in HL) into Register A one bit at a time. Whenever Register A is greater than or equal to the modulus or is full, decrement it by the modulo value and bump the quotient.

Entry: HL = dividend, A = divisor

Exit: HL = quotient, A= remainder

4B84
PUSH DE
Save the contents of Register Pair DE (caller’s DE) to the stack
4B85
LD D,A
Put the divisor (held in A) into Register D
4B86
LD E,10H
Put the maximum number of bits to test (i.e., 16) into Register E
4B88
XOR A
Zero Register A and clear all flags
4B89
ADD HL,HL
HL = HL + 1. This also has the effect of shifting the dividend left 1 bit with the overflow to CARRY
4B8A
RLA
Rotate the bits of Register A left so that CARRY goes to Bit 0 and Bit 7 goes to CARRY
4B8B
If the remainder is large, then CARRY will be set, so JUMP to 4B90H
4B8D
CP D
Compare the remainder to the divisor
4B8E
If the remainder is less than the divisor, JUMP to 4B92H
4B90
SUB D
Subtract the divisor from A to adjust the remainder
4B91
INC L
BUMP Register L by 1 to adjust the remainder
4B92
DEC E
DECrement Register E (the number of bits to process) by 1
4B93
If we still have bits to process, LOOP back to 4B89H
4B95
POP DE
Restore the caller’s DE from the stack into Register Pair DE
4B96
RET
RETURN

4B97H – test for EOF in the sector buffer

4B97
LD A,(IX+08H)
IX+08H = EOF offset held in the DCB. Fetch its value into Register A
4B9A
DEC A
DECrement Register A by 1
4B9B
SUB C
Subtract C (the current record offset) from the decremented EOF offset
4B9C
CCF
Invert the CARRY FLAG to test if we are beyond the EOF
4B9D
INC BC
Bump BC to readjust the offset
4B9E
RET
RETURN
4B9F
NOP
Need a blank instruction so 4BA0H would be the next instruction

4BA0H – Return with zero status. The 4312H vector points here.

4BA0
XOR A
Zero Register A and clear all flags. This will signal a GOOD status
4BA1
RET
RETURN

4BA2H – RST 28 Processing. Load System Overlay. Entry: A holds the overlay request

4BA2
EX (SP),HL
Save the return address for RST 28 in HL
4BA3
POP HL
Restore HL, return address lost
4BA4
OR A
Test overlay call request
4BA5
If A is positive, it is an invalid request, so JUMP to 4312H (which JUMPs to 4BA0H to return with zero status)
4BA8
PUSH HL
Save the caller’s HL
4BA9
LD H,A
Save the overlay request into Register H
4BAA
LD A,(430EH)
Fetch the last overlay request code
4BAD
XOR H
Compare the last overlay request code against A via XOR
4BAE
AND 9FH
Mask the XOR’d results against 1001 1111
4BB0
LD A,H
Restore the current request from H
4BB1
LD (430EH),A
And the current request as the previous request
4BB4
If Z is set, then the overlay is currently in memory, so JUMP to 4BEDH
4B86
PUSH DE
Save caller’s DE to the Stack
4BB7
PUSH BC
Save caller’s BC to the Stack
4BB8
AND 9FH
Mask the XOR’d results against 1001 1111 to isolate the directory sector number
4BBA
CP 08H
Test the isolated directory sector number against 08H to test for 1st or 2nd entry in sector
4BBC
If the isolated directory sector is less than 8 then the 2nd entry is to be used, JUMP to 4BC0H
4BBE
ADD A,l8H
Otherwise, add 24 to the isolated directory sector to get the new sector number file offset
4BC0
LD B,A
Copy Register A to B for safekeeping and use for computing buffer address by 4B1EH
4BC1
LD (44A7H),A
Save in DCB
4BC4
XOR A
Clear CARRY and Register A
4BC5
LD (44A1H),A
Now that A is zero, load it into (44A1H) to grant all permissions
4BC8
SBC HL,HL
Generate 16 Bits of zero
4BCA
LD (44AAH),HL
And save as next record number
4BCD
LD C,A
Set drive number to zero
4BCE
GOSUB to 4AC1H to read the directory sector pointed to by BC into the buffer
4BD1
If NZ is set, there was a read error, so JUMP to 4409H
4BD4
LD A,L
Put the LSB of the file entry address in buffer into Register A
4BD5
ADD A,16H
Add an offset of 25 bytes to position to first GAP
4BD7
LD L,A
Form GAP addressess in HL
4BD8
LD A,(HL)
Fetch track number for the overlay
4BD9
INC L
Bump to 2nd GAP byte
4BDA
LD H,(HL)
Fetch sector offset/granules assigned
4BDB
LD L,A
HL=1st GAP for overlay
4BDC
LD (44AEH),HL
Save as 1st GAP in short system DCB
4BDF
LD DE,44A0H
Set DE to point to the addressess of the short system DCB
4BE2
Go load file from system DCB
4BE5
Jump to 4409H if any error during load
4BE8
LD (4BFCH),HL
Save transfer addressess of overlay
4BEB
POP BC
Restore caller’s BC
4BEC
POP DE
Restore caller’s DE
4BED
POP HL
Restore caller’s HL
4BEE
LD A,(4315H)
Fetch DEBUG flag and save it
4BF1
LD (4C00H),A
for recall after overlay execution
4BF4
XOR A
Zero Register A and Flags
4BF5
LD (4315H),A
Clear the DEBUG flag
4BF8
LD A,(430EH)
Load overlay request code (it was put there at 4BB1)
4BFB
Enter overlay. Note: The value was put here by 4BE8H
4BFE
PUSH AF
Save Register Pair AF to the Stack so we can use Register A
4BFF
LD A,09H
Set Register A to restore DEBUG
4C01
LD (4315H),A
Set the DEBUG glag to 4315
4C04
POP AF
Restore overlay exit status
4C05
RET
Return to overlay caller

4C96H – Load File From System DCB

4C96
PUSH HL
Save caller’s HL to the stack
4C97
GOSUB to 4439H to load the file specified by the system DCB
4C9A
If there was an error during the load, JUMP to 44B9H
4C0D
EX (SP),HL
Restore HL. Save stack contents
4C9E
LD A,(439FH)
Fetch the DEBUG flag
4C11
OR A
Set status flags for DEBUG
4C12
If DEBUG active, go load it
4C15
RET
Return to caller

4C16H – Load File From DCB Specified in DE

4C16
LD B,00H
Specify physical I/O
4C18
LD HL,4D00H
Set HL to point to a buffer at 4D00H
4C1B
GOSUB to 4424H to open a file
4C1E
if Z is set, there was an error, so JUMP to 4C29H
4C20
SET 06H,A
Convert possible error code of 18-58
4C22
CP 58H
Was the error code 18
4C24
RET NZ
Return if no
4C25
LD A,1FH
Yes, replace it with a 1FH
4C27
OR A
Set status flags
4C26
RET
And return to caller

4C29H – General Purpose File Loader

4C29
INC DE
Bump DCB pointer to access flags
4C2A
LD A,(DE)
Fetch file status flags
4C2B
DEC DE
Backspace DCB pointer
4C2C
AND 07H
MASK the index address held in Register A against 0000 0111 to keep only bits 0-2, so that it isolates the access permission
4C2E
CP 06H
Test for restricted file
4C30
Jump if file not restricted
4C32
LD A,(430FH)
Fetch DEBUG flag
4C35
RLCA
DEBUG active bit to CARRY
4C36
LD A,25H
Error code if DEBUG active
4C38
RET C
Return with error if DEBUG active

4C39H – System Loader

4C39
LD BC,4DFFH
Set BC to empty buffer status
4C3C
GOSUB to 4C8CH to get a byte from the load file
4C3F
CP 01H
Test to see if that byte was a control code
4C41
Jump if load data follows
4C43
CP 02H
Test for control code 2
4C45
Jump if transfer address follows
4C47
CP 20H
Test for illegal control code
4C49
Jump if illegal control code, skip to next field
4C4B
LD A,22H
Else ret LOAD FILE FORMAT error
4C4D
OR A
Set status for error code
4C4E
RET
Return to file load caller

4C4FH – Skip to Next Control Byte

4C4F
Get byte count for this control code
4C52
LD B,A
Put the byte count into Register B for a DJNZ Loop
4C53
GOSUB to 4C8CH to get a byte from the load file
4C56
LOOp till all bytes for current control code skipped
4C58
Go fetch next control code

4C5AH – Get Transfer Address Into HL

4C5A
Get byte count for this control code
4C5D
Get byte count for this control code
4C60
LD L,A
Put the byte count into the LSB of HL
4C61
Get byte count for this control code
4C64
LD H,A
Put the byte count into the MSB of HL
4C65
XOR A
Clear all flags and signal a good load
4C66
RET
RETURN

4C67H – Load Data Into Memory

4C67
Get a byte (byte count for this control code)
4C6A
LD B,A
Put the byte count into Register B for a DJNZ Loop
4C6B
Get a byte (this will be the LSB of the LOAD address)
4C6E
LD L,A
Put the byte count into the LSB of HL
4C6F
DEC B
Count 1 byte as having been read
4C70
Get a byte (this will be the MSB of the LOAD address)
4C73
LD H,A
Put the byte count into the MSB of HL
4C74
DEC B
Count 1 byte as having been read
4C75
Get a byte (this will be a byte of LOAD data)
4C78
LD (HL),A
Save it in memory
4C79
CP (HL)
Verify that it was properly stored
4C7A
IF NZ is set, it wasn’t properly stored so JUMP to 4C81H to determine why and set the error code
4C7C
INC HL
Bump HL to the next location in memory
4C7D
LOOP back to 4C75H until all bytes in the block have been moved
4C7F
JUMP to 4C3CH to get the get next control byte

If we are here, the byte didn’t get written, so we need to see why.


4C81H – Determine Cause of Storage Failure

4C81
LD A,(HL)
Fetch the original contents of the memory location
4C82
CPL
Complement it
4C82
LD (HL),A
Put it back into the memory location
4C82
CP (HL)
Compare the memory location with what was attempted to be put there (held in Register A)
4C82
LD A,63H
Prepare to return a MEMORY FAULT error, so load that error into Register A
4C82
RET NZ
Exit with MEMORY FAULT if the comparison was still not a match
4C82
LD A,64H
Otherwise, prepare to return an ATTEMPTED TO LOAD ROM error, so load that error into Register A
4C82
OR A
Set the flags to an error position
4C82
RET
RETURN

4C67H – Get the Next Byte of Load Data from the Sector Buffer

4C8C
INC C
Bump to to the next byte in sector buffer
4C8D
Jump to 4CA3H if the buffer is not empty
4C8F
PUSH DE
Push the current DCB address to the stack
4C90
EX (SP),IX
Then move it to IX for the upcomming read routine
4C92
PUSH HL
Save caller’s HL
4C93
PUSH DE
Save caller’s DE
4C94
PUSH BC
Save the buffer address
4C95
Go read next sector from file
4C98
POP BC
Restore buffer address
4C99
POP DE
And DE
4C9A
POP HL
And HL
4C9B
POP IX
Clear stack
4C9D
Jump to 4CA3H if there was no error during read
4C9F
POP BC
Clear 4C8CH (returne address) from stack
4CA0
OR 40H
Signal error during load
4CA2
RET
Return to file load caller

4CA3H – Re-initialize BC to 4D00 and fetch the first byte of data from the buffer.

4CA3
PUSH BC
Remove buffer address of 4D00 (unnecessary)
4CA4
LD B,4DH
Force address to 4D0B (unnecessary)
4CA6
LD A,(BC)
Fetch 1st data byte
4CA7
POP BC
Restore 4D00
4CA6
RET
Return to caller

4CA9 – CLOCK Display Routine

4CA9
DEFW 4CAC5
4CAB
DEFB 5
200 millisecond counter
4CAC
DEC (IX+02H)
Count a 200 MS interval
4CAF
RET NZ
Exit if 1 second not elapsed
4CB0
LD (IX+02H) ,05H
Reset millisec counter to 1 second
4CB4
LD HL,3C35H
Video display address for time
4CB7
LD DE,4043H
End address of max time values
4CBA
LD C,3AH
Hex value for : (field separator)
4CBC
LD B,03H
No. of values to convert to ASCII
4CBE
LD A,(DE)
Get a binary time value starting with hour, minute, second
4CBF
DEC DE
Backspace to next time value
4CC0
LD (HL),2FH
Convert binary to ASCII by compound subtraction. First, begin with quotient of 2F
4CC2
INC (HL)
Increment to 30, 31, …
4CC3
SUB 0AH
Divide value by 10 using subtraction
4CC5
LOOP back to 4CC2H until division is complete
4CC7
ADD A,3AH
Force the remainder to be positive
4CC9
INC HL
Bump to next location in display
4CCA
LD (HL),A
Save 2nd digit (remainder)
4CCB
INC HL
Bump to next position in display
4CCC
DEC B
Have 3 digits been displayed
4CCD
RET Z
Return if yes
4CCE
LD (HL),C
No, display “:” following value
4CCF
INC HL
Bump to next location in display
4CD0
LOOP till 3 values displayed
4CD2
LD DE,4046H
Ending address of binary date
4CD5
LD C,2FH
ASCII value for I (field separator)
4CD7
Use time conversion routine

4CD9H – TRACE Display Routine

4CD9
DEFW 4CDBH
4CDB
LD HL,0012H
Offset to F-Reg in stack frame
4CDE
ADD HL,SP
Backspace stack pointer
4CDF
LD E,(HL)
Fetch the LSB of F-reg at interrupt time
4CE0
INC HL
Backspace one more byte
4CE1
LD D,(HL)
Fetch the MSB of F-register at interrupt time
4CE2
LD HL,3C2EH
Set HL to point to the display address for TRACE display
4CE5
LD A,D
Put the MSB of the P-reg into Register A in preparation for display
4CE6
Convert to ASCII and display
4CE9
LD A,E
Put the LSB of the P-reg into Register A
4CEA
PUSH AF
Save value to be converted
4CEB
RRA
RRA
RRA
RRA
Right shift it 4 times to isolate the upper digit of the lower nybble
4CEF
Convert upper digit of nibble to ASCII and display
4CF2
POP AF
Restore the original nibble
4CF3
AND 0FH
MASK Register A against 00001111 to isolate the lower digit
4CF5
ADD A,30H
Convert binary to ASCII
4CF7
CP 3AH
Test for hex values A-F
4CF9
If C is set, then A was less than the test value of 3AH, so C set means it is not A-F, so skip then next instruction
4CFB
ADD A,07H
Else convert to proper ASCII value
4CFD
LD (HL),A
Send ASCII digit to video
4CFE
INC HL
Bump to next video address
4CFF
RET
Return to caller

4D00H – System Sector Buffer

4D00
DEFS 256
256 System sector buffer area

4E00H – System Initialization Code

4E00
DI
Disable interrupts
4E01
IM 1
Set the INTERRUPT MODE to 1
4E03
LD SP,41FCH
Set the Stack Pointer to 41FCH
4E06
LD HL,5200H
Set HL to point to the utility load addrress of 5200H
4E09
LD (4047H),HL
Store the 5200H Utility Load Address into (4047H)
4E0C
LD HL,0FFFFH
Set the start address for the memory test into HL
4E0F
LD A,(HL)
Fetch the memory contents of (HL) into Register A
4E10
LD B,A
Save a copy of the memory contents into Register B
4E11
CPL
Complement Register A
4E12
LD (HL),A
Put the complemented memory contents back into (HL)
4E13
CP (HL)
Check to see if the complemented memory contents are actually in (HL), setting the flags accordingly
4E14
LD (HL),B
Put the actual memory contents back into (HL)
4E15
If the complemented memory read back OK then the memory address must exist, so JUMP to down to 4E1DH
4E17
LD A,H
If we are here, then we didn’t get a match, so put the MSB of the failed memory address into Register A
4E18
SUB A,04H
Reduce the MSB of the failed memory address by 04H, which drops it 1024 bytes
4E1A
LD H,A
Put the reduced MSB back into Register H
4E1B
LOOP back to 4E0FH to test (HL)
4E1D
LD (4049H),HL
If we are here, then HL points to the highest memory address, so put that into (4049H) which stores such things
4E20
JUMP to 4F20H to change the keyboard driver address
4E23
LD (43B8H),HL
Save the new keyboard driver address into 43B8H …
4E26
LD (43C2H),HL
… and 43C2H (the driver address table)
4E29
LD HL,(401BH)
Fetch the address of the keyboard buffer
4E2C
LD (43C0H),HL
Put the address of the keyboard buffer into 43C0H (the device table)
4E2F
LD HL,(401EH)
Fetch the address of the video driver
4E32
LD (43BAH),HL
Put the address of the video driver into 43BAH (the driver table) …
4E35
LD (43C6H),HL
… and 43C6H (the driver address table)
4E38
LD HL,(4023H)
Fetch the address of the video buffer
4E3B
LD (43C4H),HL
Put the address of the video buffer into 43C4H (the device table)
4E3E
LD HL,(4026H)
Fetch the address of the printer driver
4E41
LD (43BCH),HL
Put the address of the printer driver into 43BCH (the driver table) …
4E44
LD (43CAH),HL
… and 43CAH (the driver address table)
4E47
LD HL,(402BH)
Fetch the address of the printer buffer
4E4A
LD (43C8H),HL
Put the address of the printer buffer into 43C8H (the device table)
4E4D
LD HL,43CCH
Put the END ADDRESS of the device table into Register HL
4E50
LD (HL),00H
Put a 00H at the end of the device table to signify a terminator
4E52
INC L
Bump the LSB by 1
4E53
If the NZ flag is set, JUMP to the next instruction (= meaningless instruction)
4E55
LD A,03H
Set Register A to 03H
4E57
LD HL,4015H
Setting up for a LDIR. Set HL to point to the address of the keyboard DCB
4E5A
LD DE,4358H
Set DE to point to the address of the alternate DCB area
4E5D
PUSH AF
Save Register Pair AF to the stack
4E5E
LD BC,0008H
Set BC to a counter of 8 bytes to move
4E61
LDIR
Move the 8 bytes of the keyboard DCB into the alternative DCB
4E63
XOR A,A
Zero out Register A and clear all flags
4E64
LD B,08H
Setting up for a DJNZ. Set B to move 8 bytes
4E66
LD (DE),A
Put a ZERO into the bytes right after the alternate DCB area
4E67
INC DE
Bump DC
4E68
LOOP to fill all 8 bytes
4E6A
LD A,0FFH
Put FF (a data storage pattern) into Register A
4E6C
LD B,10H
Setting up for DJNZ. Set the counter to 16 bytes
4E6E
LD (DE),A
Fill (DE) with a FF. This fills 4368-4377 with FF
4E6F
INC DE
Bump DE
4E70
LOOP back to 4E6EH until all 16 bytes are filled with FF
4E72
POP AF
Restore Register pair AF (pushed in 4E5D)
4E73
DEC A
Reduce Register A by 1 (=meaningless instruction, Register A is filled in 2 instructions)
4E74
If Register A has still not hit ZERO, JUMP to the next instruction (=meaningless instruction)
4E76
LD A,(37ECH)
Get the controller status from (37ECH) and put it into Register A for testing/processing
4E79
XOR A
Clear A and all flags. This will be the NOP OPCODE since its being put into program memory
4E7A
LD (4315H),A
Store a NOP at the DEBUG switch memory location
4E7D
LD HL,404CH
Put the address of the interrupt mask byte into Register HL
4E80
LD (HL),A
Put a 0 into the interrupt mask byte. This will set to IGNORE ALL INTERRUPTS
4E81
LD A,0C3H
Put the OPCODE for a JP command into Register A
4E83
LD DE,4518H
Put 4518H, which is the address of the interrupt routine, into DE
4E86
LD (4013H),DE
Put the address of the interrupt routine into 4013H as the destination for a JUMP instruction for a call to 4012H
4E8A
LD (4012H),A
Put a JUMP instruction into 4012H
4E8D
LD DE,4560H
Put 4560H, the address of the of clock interrupt, into DE (actually, the Address of interrupt service routine for interrupt bit 7)
4E90
LD (405BH),DE
Put the clock interrupt into the Interrupt Task List
4E94
SET 7,(HL)
Set the mask bit for clock interrupt to ON
4E96
LD DE,4669H
Put 4669H, the address of the disk interrupt routine, into DE (actually, the address of interrupt service routine for interrupt bit 6)
4E99
LD (4059H),DE
Put the disk interrupt routine into the Interrupt Task List
4E9D
SET 6,(HL)
Set the mask bit for disk interrupt to ON
4E9F
LD A,01H
Set A to be 01H
4EA1
LD (430FH),A
Send 01H to the DEBUG LOAD FLAG (held at 430FH)
4EA4
LD DE,45AFH
Put 45AFH, which is the routine to maintain the clock, into Register DE
4EA7
LD A,07H
Put an 07H Into Register A to indicate which index into the Interrupt Task List is desired
4EA9
GOSUB to 4410H to add the clock maintenance routine to the interrupt task list
4EAC
LD HL,4EEDH
Point HL to the TRSDOS SYSTEM message
4EAF
LD A,(HL)
Fetch a character from that message
4EB0
NEG
NEGATE it
4EB2
GOSUB to 0033H in the ROM to send the character held in Register A to the current device
4EB5
INC HL
BUMP HL to point to the next character in the message
4EB6
CP 0DH
Check to see if the CURRENT character is a CARRIAGE RETURN/MESSAGE TERMINATOR
4EB8
If that character is NOT a CARRIAGE RETURN/MESSAGE TERMINATOR JUMP back to 4EAF to keep displaying the message
4EBA
GOSUB to 002BH in the ROM to fetch a character from the keyboard
4EBD
CP 0DH
Test to see if that keyboard character was a CARRIAGE RETURN
4EBF
If it was a carriage return, then JUMP to 4400H to SKIP the AUTO routine
4EC2
LD C,00H
If we are here, then we need to run the AUTO. First, put a 00 into Register C as the GAT sector pointer
4EC4
LD D,11H
Put an 11H into Register D to set the track for the directory
4EC6
LD E,00H
Put a 00H into Register E to set the sector number for the directory
4EC8
LD HL,4200H
Set up HL to point to a buffer address
4ECB
Read the sector held in Register E of the track held in D on the drive held in C into the buffer pointed to by HL
4ECE
If NZ is set, there was an error reading the GAT Sector, so JUMP back to 4409H to deal with the error
4ED1
LD A,(42E0H)
If we are here, then the GAT was properly read, so Fetch the first byte of the AUTO command from 42E0H into Register A
4ED4
CP 0DH
Is that first character a blank? If so, then there is no AUTO command
4ED6
If so, then there is no AUTO command so JUMP to 4400H to SKIP the AUTO routine
4ED9
LD HL,42E0H
If we are here, we need to deal with AUTO, so set up for a LDIR. First set HL to point to the AUTO file specification
4EDC
LD DE,4318H
Set DE to point to the command line buffer
4EDF
LD BC,0020H
Set BC to 32, which is the maximum length of the AUTO command
4EE2
LDIR
Move the auto command from the GAT sector into the command line buffer
4EE4
LD HL,4318H
Point HL to the command line buffer
4EE7
GOSUB to 4467H to display the AUTO message
4EEA
JUMP to 4405H to load up SYS1/SYS and run the AUTO command

4EEDH – System Header Message

4EED
DEFM ‘TRSOOS- DISK OPERATING SYSTEM- VER 2.3’
4F17
DEFB 0F3H
Message terminator (carriage return)

4F20H – Change the keyboard driver address. Jumped to by 4E20H

4F20
LD HL,43DBH
Point HL to the address of the replacement keyboard driver
4F23
LD (4016H),HL
Send the replacement keyboard driver to 4016H which otherwise holds the keyboard DCB
4F26
Rejoin main code