Network 4 Transporter ROM Explained

This is a disassembly of Network 4 Transporter ROM Explained

Interview with Tony Peppin - January 31, 2025. Provided by vtgearhead.

It all starts with Michael Freeman.

I first met Michael at a local Radio Shack store. I was acquainted with the store manager. I don't know if you remember it, but I had a KIM 1, computer on a board. It had a four character display and a hex keypad built on the board with several ports on the side, including an RS-232 port and a teletype milliamp loop port. From a salvage yard, I bought a Navy surplus Friden Flexowriter, with a built in paper tape reader/punch. I was attempting to build an interface between my KIM 1 and this teletype want-to-be. (The Flexowriter was 90 volt DC parallel and I was trying to talk to it through a 5 volt parallel port in the KIM. I actually succeeded, sort of. I would output the first character to type, the Flexowriter would type it, the Flexowriter would pull so much power out of the household current that it would reset the KIM.) While I was getting the parts I needed, the store manager spotted Michael and called him over. Michael was trying to learn about computers and the manager figured that I could help him. He was right. Michael and I hit it off really well.

Shortly after that, Radio Shack introduced their TRS-80 model I. Both Michael and I put in orders for one. There was a demo model of the unit that was making its way up and down the Pacific coast. It was going to be at the Tacoma Mall for two days before heading south to Portland. The manager arranged for Michael and I to play with it. And play with it, we did. The Mall Radio Shack manager let us stay in the store long after the Mall closed. (It was at that time that I created a five line program that would shift the display mode from 64x16 to 32x16 creating double sized letters, display the word "DAMN!" on each line, and then freeze the computer so that it had to be powered cycled to recover. Ah, good times...)

When the TRS-80 model I expansion interface went on the market, I received serial number 78. Michael discovered the joy of having a pink pearl eraser when you had an expansion interface. (The connector would corrode if exposed to higher humidity. A pink pearl would clean off the corrosion and allow the expansion interface to actually work again.) Eventually, floppy disks became available for the TRS-80 model I. We of course got some. I had one, and Michael got two. He could easily copy diskettes, I had to flip back and forth between diskettes. Eventually, the TRS-80 model III came out, and of course, we acquired some of those too.

It begins.....

Michael worked for his father in the family owned business. He was a gofer/troubleshooter/jack of all trades. In his spare time, he consulted small businesses about computer issues and needs, pushing of course TRS-80s. As a troubleshooter, he traveled far and near. Somehow he was contacted, or he contacted someone at Corvus. They had built their Corvus drives for Apple and were wanting to expand their market to TRS-80s. They had come up with an interface for a TRS-80, but they were having problems with software. They were not TRSDOS people and did not really have an understanding of how it worked. And let's face it, TRSDOS, with all its overlays and secret handshakes, was really an operating system for a minicomputer, not a microcomputer. Michael told them that he could not really help them, but his friend could do it easily. (Did I ever mention that Michael could lie with a straight face?) Corvus "loaned" Michael two 20 meg hard drives and gave him the instructions needed to access the drive through the interface. It wasn't easy, but I was able to cram the disk I/O routines into available memory space and patch the object code. Imagine the difference between a 55K floppy and a 20 megabyte hard drive. A floppy drive was still needed to bootstrap the hard drive since we could not change the TRS-80 boot ROM. Corvus ended up with the software to interface to a TRS-80 for the simple cost of two hard drives.

It grows.....

Corvus had this big idea: Let us share a single hard drive with multiple computers! We will simply expand the number of stations on this flat cable! It worked with the software. They started marketing the idea. The FCC then told Corvus, uh-uhh. You may not sell that big antenna that interferes with radios, even if it does allow a bunch of computers to talk to each other. That's when Corvus came up with Omninet. A networking idea based upon RS-422. It wasn't long until Corvus came a knocking at Michael's door asking for a little favor. The actual coding necessary for using Omninet was very similar to direct accessing the hard drive, so it was just the work of a couple of hours to modify my changes to work with the networking hardware. Michael began to expand his consulting work to include the sharing of a hard drive with several different computers.

As I mentioned before, Michael traveled far and near as a trouble shooter for his family's company. On some of these trips he would mix in a consulting gig as well as trouble shooting the family's business' products. In one case, (I think it was in California) working with a local Radio Shack Store, he demonstrated to a school district a ten computer and one Corvus drive Omninet setup. Tandy's Education Division was also demonstrating to that school district an example of Network 3. I don't know if you have ever seen a Network 4 network up and running, but from what you said in your last email, you have seen how "speedy" a Network 3 network is. The RS-422 runs at 4 meg, so it is slightly slower than half a standard ethernet. Network 3 runs at 9600 bps. Michael's demo of 10 computers was up and running before Network 3 finished booting the first workstation. The actual accessing of data from the Corvus drive was comparable to the speed of accessing the data from an attached floppy disk.

Two weeks later, Tandy's Education Division contacted Michael to ask if he could help them develop what became Network 4. Michael and I became partners in a business he called FPS, Freeman Pepin Services. Tandy gave us a printout of the source code of TRSDOS and supplied us with some hardware. When they found out that we had plans to burn EPROMs that would allow direct booting off of the Corvus, they were shocked and became very accommodating. Are you familiar with TRS-80 Model IV? It looks almost identical to the TRS-80 Model III. However, the newer versions have a slightly different ROM from the older models. If the Model IV has the Omninet module installed, if you power up or reset the computer while holding down the '4' key on the attached 10-key pad, the Model IV will attempt to use the server (which can be either a Corvus drive or a TRS-80 with an attached hard drive running my custom server code) to boot. The software will allow 64 stations (including the server) to co-exist on a single network. The server is address zero. During the final debugging session in Fort Worth before Tandy started selling Network 4, we actually had 64 computers running in this large room. It took less than 5 minutes to boot all of them, and a lot of that time was us running around resetting the computers.

Network 4 was only marketed by the Education Division and not the rest of Tandy. It never got the traction it deserved. Tandy moved away from the TRS-80 design and migrated to the IBM PC types such as the Tandy 1000 or the Tandy 3000. What no one seemed to notice is that the Tandy 1000 also had an Omninet module available and had drivers to access (or to be) the server. If Corvus had only gone to ethernet instead of RS-422 ... *sigh*

I remember having a long discussion with one of the Education Division engineers, educating him on what a server-based network could do. We talked about a modem server as well as many other server oriented modules. Alas, while he got excited by the possibilities, he was never able to get any of his management to understand.

Equates

Network 4 (Omninet) Ports

OMSBEQU 0D3H; MSB of Omninet Address Pointer
OLSBEQU 0D1H; LSB of Omninet Adddress Pointer
OSTROBEQU 0D2H; Omninet Strobe Port
OMOUTEQU 0D0H; Output with Auto Increment
OMINEQU 0D2H; Input without Auto Increment
OMINPLEQU 0D0H; Input With Auto Increment
OMSTATEQU 0D3H; Transporter Status

Equates for Hard Disk Ports

HSTATEQU 0CFH; Status Register
HCOMNDEQU 0CFH; Command Register
HDRIVEEQU 0CEH; Size/Drive/Head Register
HCYLHIEQU 0CDH; Cylinder High Byte
HCYLLOEQU 0CCH; Cylinder Low Byte
HSECTEQU 0CBH; Sector Number
HDATAEQU 0C8H; Data Register
HCNTLEQU 0C1H; Control Register

Equates for Misc Other Items

DISPITEQU 021BH; ROM Display line routine
OMBUFFEQU 7A00H; Read Buffer
BUFMSBEQU 7AH; MSB of the OMBUFF Address

Disassembly

 
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

Now 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