Equates for Network 4 (Omninet) Ports

OMSB
EQU 0D3H
; MSB of Omninet Address Pointer
OLSB
EQU 0D1H
; LSB of Omninet Adddress Pointer
OSTROB
EQU 0D2H
; Omninet Strobe Port
OMOUT
EQU 0D0H
; Output with Auto Increment
OMIN
EQU 0D2H
; Input without Auto Increment
OMINPL
EQU 0D0H
; Input With Auto Increment
OMSTAT
EQU 0D3H
; Transporter Status

Equates for Hard Disk Ports

HSTAT
EQU 0CFH
; Status Register
HCOMND
EQU 0CFH
; Command Register
HDRIVE
EQU 0CEH
; Size/Drive/Head Register
HCYLHI
EQU 0CDH
; Cylinder High Byte
HCYLLO
EQU 0CCH
; Cylinder Low Byte
HSECT
EQU 0CBH
; Sector Number
HDATA
EQU 0C8H
; Data Register
HCNTL
EQU 0C1H
; Control Register

Equates for Misc Other Items

DISPIT
EQU 021BH
; ROM Display line routine
OMBUFF
EQU 7A00H
; Read Buffer
BUFMSB
EQU 7AH
; MSB of the OMBUFF Address

Begin …

 
ORG 7000H
START
LD HL,WELCOM
 
LD HL,(6FF0H)
;Get Special Flag
 
LD DE,5041H
 
OR A
 
SBC HL,DE
 
;FLAG ON, MUST DELAY
 
LD (6FF0H),DE
 
LD A,(4210H)
 
OR 10H
; I/O Ports On!
 
OUT (0ECH),A
 
LD (4210H),A
 
XOR A
; Start by Resetting Transporter
 
OUT (OLSB),A
; Point at Zero – LSB First
 
OUT (OMSB),A
; Point at Zero – then MSB
 
DEC A
; Make A = FFH
 
OUT (OMOUT),A
; Response Code
 
LD A,20H
; Reset Command (i.e., a space)
 
OUT (OMOUT),A
; Put into Transporter
 
XOR A
; Zero A Again
 
OUT (OMOUT),A
; Reponse Address = Zero
 
OUT (OMOUT),A
 
OUT (OMOUT),A
 
INC A
; Address 1
 
; Kick It
 
XOR A
; Address 0
 
; Register A Now contains our Transporter Address
 
CP 63H
; Are we the server? (Check for “?”)
 
; If so, then the hard disk is directly attached to the computer making this system the server. With this, jump to the hard drive to load the server code.

If we are here, then the station address is not 63 and this is not the server. With this, we must read (across the network) the bootstrap pointer sector (sector 0 of drive 1 at the server) to get the boot code volume for THIS station, then get the location of the boot volume from the volume table, and then load the object code found at that location and jump to it. So let’s start …

 
EX AF,AF'
; Save address until later
 
LD DE,20H
; Logical Record Number of Boot Pointer
 
LD BC,OMBUFF
; Point at our budffer
 
; Read the Boot Record
 
EX AF,AF'
; Get our address back
 
LD H,BUFMSB
; MSB of our Buffer Address
 
ADD A,A
; Double our Address
 
LD L,A
; HL is our Boot Volume Number
 
LD E,(HL)
; Get LSB
 
INC HL
; Bump HL to point to the MSB
 
LD D,(HL)
; Get MSB

Now have Volume Number of our Boot Code. Next we must get the Volume Entry for Start Address …

 
LD BC,10H
; Size of volume entry (Binary = 16)
 
LD HL,0
; Start at Zero offset
GETV1
LD A,E
; Check remaining count
 
OR D
; Down to Zero yet?
 
; Yes, go handle it!
 
DEC DE
; Count down volume numbers
 
ADD HL,BC
; Bump sector and offswt
 
; Loop

“GETV2” – At this point H = the Sector Offset and L = Buffer offset.

GETV2
LD E,H
; Sector offset into E
 
LD H,BUFMSB
; MSB of Buffer
 
PUSH HL
; Save Buffer Offset for a Moment
 
LD HL,15H
; Sart of Volume Table (15)
 
ADD HL,DE
; Adjust offset
 
EX DE,HL
; Put adjusted sector into DE
 
LD BC,OMBUFF
; Point at Buffer
 
; Read the entry table
 
POP HL
; HL now points at the type code
 
INC HL
; Point at the server number
 
LD A,(HL)
; Get server number
 
LD (SERV1),A
; Put into the command area
 
INC HL
; Point at the drive number
 
LD A,(HL)
; Get it
 
LD (RDDRV),A
; Put into read command
 
INC HL
; Point at the LSB of the Starting Sector
 
LD E,(HL)
; Put into E
 
INC HL
; Point at the MSB of the Starting Sector
 
LD D,(HL)
; Put Into D
 
LD BC,OMBUFF+255
; Point at End of Buffer
 
EXX
; Change Sides!

“BOOT0” – We know where the Boot Code is so BOOT!

BOOT0
; Get the next byte
 
CP 02H
; Is it a start record?
 
; Yes, go get start addres
 
CP 01H
; Is it object code?
 
; Go load it
 
CP 20H
; Is it an error?
 
; No, comment, go ignore it

Load file format error!

 
LD HL,FMTERR
; Point at error message
FATAL
EQU $
; Any error comes here
 
; Display error message
STOP
; Hang the system in an infinite loop
BOOT1
; Get count to ignore
 
LD B,A
; Put it into B
BOOT2
; Ignore loop
 
DJNZ BOOT2
; Spin our wheels
 
; Loop back to BOOT0 to get next byte
BOOT3
; Get the size of the record
 
LD B,A
; Put into B
 
DEC B
; Remove the address part of the size
 
DEC B
 
; Get the LSB of the Load Address
 
LD L,A
; Put it into L
 
; Get the MSB of the Load Address
 
LD H,A
; Put it into H
BOOT4
; Get Next Byte of Object Code
 
LD (HL),A
; Put it into memory
 
INC HL
; Bump the memory pointer
 
DJNZ BOOT4
; Go get the next byte
 
; Go get the next block
BOOTED
; Get the “2” Size
 
; Get the LSB of the Start Address
 
LD L,A
; Put it into L
 
; Get the MSB of the Start Address
 
LD H,A
; Put it into H
 
LD (6FF0H),HL
; Wipe out the flag
 
; Go to it!

GETBYT” – Returns the next byte from the disk drive reading the buffer when necessary

GETBYT
EXX
; Change sides
 
INC C
; Bump the pointer
 
; Read the sector, if necessary
 
LD A,(BC)
; Fetch the byte.
 
EXX
; Back again
 
RET
; Return
GREAD
PUSH BC
; Save buffer
 
PUSH DE
; Save sector
SWITCH
; Self modifying code switch
 
POP DE
; Restore sector
 
INC DE
; Point to the next one
 
POP BC
; Restore buffer
 
RET
; Return

“OREAD” – Omninet Read Routine: BC = Buffer Address; DE = Record Number Wanted.

OREAD
LD A,08H
; Retry count
 
LD (RETRYS),A
; Put into field
 
LD (RDSECT),DE
; Put sector into read buffer
 
PUSH BC
; Save buffer address for a moment
OREAD1
XOR A
; Zero A
 
OUT (OLSB),A
; Point Pointer At 0
 
OUT (OMSB),A
 
LD C,OMOUT
; Point C at the Data Port
 
LD B,CMDEND-CMDS
; Get length of area
 
LD HL,CMDS
; Point to command area
 
OTIR
; Move it to the transporter

Transporter is now formatted the way we want it, so issue the read. Remember that A is still zero.

 
; Issue the receive
 
LD A,RES1-CMDS
; Point at the result
 
; Wait for it
 
CP 0FEH
; = Successful set up?
 
; No, it is an error
 
INC A
; Move A back to 255
 
OUT (OMOUT),A
; Put back into the transporter
 
LD A,SEND1-CMDS
; Point at send command
 
; Kick it
 
LD A,RES2-CMDS
; Point at result to send
 
; Wait for it
 
CP 80H
; Check for 80H as a fatal error
 
; Go handle it
 
LD A,RES1-CMDS
; Point at RES1 again
 
; Now wait for the server
 
CP 80H
; Check for 80H as a fatal error
 
; Go handle it
 
LD A,STAT-CMDS
; Point at disk status
 
OUT (OLSB),A
; Do the point
 
IN A,(OMIN)
; Fetch it
 
CP 80H
; Check for 80H as a fatal error
 
; Go handle it

Now retrieve the read record.

 
LD A,04H
 
OUT (OMSB),A
; Point at the data area
 
XOR A
; Zero out A
 
OUT (OLSB),A
; Start of it
 
LD BC,OMINPL
; B = 0, C = Input data port
 
POP HL
; Retrieve the buffer
 
INIR
; Bring it in
 
RET
; Return to the routine calling this one

Let’s just stick a random variable right in the middle of the code for some reason.

RETRYS
DEFB 8

Reset the transporter socket

OERROR
XOR A
; Zero A
 
OUT (OLSB),A
; Point at Address 0 – LSB First
 
OUT (OMSB),A
; Point at Address 0 – then MSB
 
DEC A
; Make A = FFH
 
OUT (OMOUT),A
; Response Code
 
LD A,10H
; End receive command
 
OUT (OMOUT),A
; Send the end receive command
 
XOR A
; Zero A
 
OUT (OMOUT),A
; Response address = 0
 
OUT (OMOUT),A
 
OUT (OMOUT),A
 
LD A,0B0H
; Load the socket address into A
 
OUT (OMOUT),A
 
LD A,01H
; Point at 1.
 
; Kick it.
 
XOR A
; Point at response.
 
; Wait for it

Transporter now reset, but before continuing, Let’s wait for a random amount of time.

 
LD A,R
; Get random count
 
LD B,A
OERR1
DJNZ OERR1
 
LD A,(RETRYS)
 
DEC A
 
LD (RETRYS),A
 
; Go retry the read.
 
LD HL,OMERR

“STROBE” – Issue an omninet command in zero page with Register A containing the address within the page.

STROBE
PUSH AF
; Save the LSB of strobe
 
XOR A
; Zero A
 
; And one …
 
; And two …
 
POP AF
; Restore the LSB of Strobe
OSTRB
PUSH AF
; Save it again
OSTRB1
IN A,(OMSTAT)
; Ready?
 
RLA
; Move the bit to carry
 
; NC = Not available yet
 
POP AF
; Restore it.
 
OUT (OSTROB),A
; Stroke it.
 
RET

“OMWAIT” – Look at an address in zero page (specified in “A”) and waits for it to become something other than 255.

OMWAIT
OUT (OLSB),A
; Point at it
OMW1
IN A,(OMIN)
; Look at it
 
CP 0FFH
; Check for 255 to see if its done
 
; If not, look again
 
EX (SP),HL
 
EX (SP),HL
; Delay in case it is in the
 
EX (SP),HL
; process of changing
 
EX (SP),HL
 
IN A,(OMIN)
; It is ready!
 
RET

“WAITR” – This routine delays APX 4 seconds to protect against rapid resets. This routine counts from 1 to 65536 32 times.

WAITR
LD B,32H
; Outer Loop – Set to 32 count (DJNZ Command)
 
LD HL,0
; Inner Loop – Set HL to 0
WAITR1
INC HL
; Inner Loop – Bump HL
 
LD A,H
 
OR L
 
; Inner Loop – If HL is NOT Zero, bump again
 
DJNZ WAITR1
; Outer Loop – Decrement B and run the loop again until B is 0.
 
RET

“SERVR” – This is the Hard Drive Server Boot Routine, to run on systems which ARE the server.

SERVR
LD A,10H
; First, reset the hard disk
 
OUT (HCNTL),A
; Wake up the hard disk
 
LD B,40H
; Give it some time
 
CALL 60H
; Delay
 
LD A,0CH
; Set the software reset bit
 
OUT (HCNTL),A
; Issue it
 
EX (SP),HL
; Wait
 
EX (SP),HL
; Wait
 
IN A,(HSTAT)
; Get Status
 
RLA
; Move busy into carry
 
; Awake yet? No, go redo it.
 
XOR A
; Zero A
 
OUT (HDRIVE),A
; Point to drive 0
 
LD A,16H
; Restore command slow mode
 
OUT (HCOMND),A
; Issue it
 
EX (SP),HL
; Wait
 
EX (SP),HL
; Wait
SRV1
IN A,(HSTAT)
; Get status
 
RLA
; Put busy into carry
 
; Ready yet?
 
LD A,10H
; Restore command fast mode
 
OUT (HCOMND),A
; Issue it
 
EX (SP),HL
; Wait
 
EX (SP),HL
; Wait
SRV2
IN A,(HSTAT)
; Get status
 
RLA
; Put busy into carry
 
; Ready yet?
 
RRA
; Move error flag
 
RRA
; into carry
 
; Do it again if error

The ROM author comments the new 6 instructions as “Now do some nice self-modifying code”.

 
LD HL,HDREAD
; Load HL with the Hard Disk Read Routine
 
LD (SWITCH+1),HL
; Put into the read vector
 
LD DE,82H
; Start of server code volume
 
LD BC,OMBUFF+255
; Point at the end of the buffer
 
EXX
; Flip to the other side
 
; Go do bootstrap

“HDREAD” – Now let’s read drive 0, head 0, of the hard drive. BC is the BUFFER and DE is the Logical Sector

HDREAD
LD HL,05H
;True sector offset
 
ADD HL,DE
; Adjust
 
LD A,L
; Move LSB into A
 
AND 1FH
; Get Mod 32
 
OUT (HSECT),A
; Put into cylinder register

This code divides HL by 32 which will then be the cylinder

 
SRL H
; Shift the contents of Register H right one bit. Bit 0 is copied to the CARRY FLAG, and a zero is put into BIT 7.
 
RR L
; Shift the contents of Register L right one bit. Bit 0 is copied to the CARRY FLAG, and the old CARRY FLAG are copied to BIT 7.
 
SRL H
 
RR L
 
SRL H
 
RR L
 
SRL H
 
RR L
 
SRL H
 
RR L

Bow that HL has the cylinder, we need the track.

 
LD A,L
; Get low cylinder byte
 
OUT (HCYLLO),A
; Put into the CYLINDER LOW register
 
LD A,H
; Get high cylinder byte
 
OUT (HCYLHI),A
; Put into the CYLINDER HIGH register

Logical seek now done! Do the read.

 
LD H,B
; Put the buffer address from BC into HL. First H ..
 
LD L,C
; Then L
 
LD BC,HDATA
; B = 0, C = Data Port
 
LD A,20H
; Read command itself
 
OUT (HCOMND),A
; Issue the read command
 
EX (SP),HL
; Wait
 
EX (SP),HL
; Wait
HDR1
IN A,(HSTAT)
; Get the status
 
RLA
; Put the busy into carry
 
; Wait for it
 
INIR
; Get the sector
 
IN A,(HSTAT)
; Get the status again
 
RRA
; Put the error into carry
 
RET NC
; Return if there is no error
HERROR
LD HL,HDERR
; Point at the error message
 
; Go do it.

Data and constants.

WELCOM
DEFB 1CH
; Home Cursor
 
DEFB 1FH
; Clear screen
 
DEFM 'Network 4 Model III Transporter '
 
DEFM 'ROM Version 01.01.00'
 
DEFB 0AH
 
DEFB 0AH
 
DEFM 'Initializing, please wait.'
 
DEFB 0DH
; Carriage return
FMTERR
DEFB 0AH
 
DEFB 0AH
 
DEFM 'LOAD FILE FORMAT ERROR'
 
DEFB 0DH
; Carriage return
HDERR
DEFB 0AH
 
DEFB 0AH
 
DEFM 'HARD DISK ERROR'
 
DEFB 0DH
; Carriage return
OMERR
DEFB 0AH
 
DEFB 0AH
 
DEFM 'NETWORK 4 CODE X1'
 
DEFB 0AH
 
DEFM 'Please try again.'
 
DEFB 0DH
; Carriage return

Data and constants – Transporter control block.

CMDS
DEFB 0F0H
; Receive Command
 
DEFW 0
 
DEFB RES1-CMDS
 
DEFB 0B0H
 
DEFB 0
 
DEFB 4
; Response record is at 0400H
 
DEFB 0
 
DEFB 4
; Record is up to 400H (1K)
 
DEFB 0
 
DEFB 3
; Control length is 3
RES1
DEFB 255
 
DEFW 0
 
DEFW 0
 
DEFB 0
STAT
DEFB 0
; Disk status byte
SEND1
DEFB 40H
 
DEFW 0
 
DEFB RES2-CMDS
 
DEFB 0B0H
 
DEFW 0
 
DEFB RDCMD-CMDS
 
DEFB 0
 
DEFB 4
; Send 4 bytes
 
DEFB 4
; 4 bytes of control data
SERV1
DEFB 3FH
; Send to server
RES2
DEFB 0FFH
 
DEFB 0
 
DEFB 0
 
DEFB 0
 
DEFB 0
 
DEFB 4
 
DEFB 1
 
DEFB 0
RDCMD
DEFB 22H
; Read Command
RDDRV
DEFB 01H
RDSECT
DEFW 0
CMDEND
EQU $
 
END START