FARGOS was a multi-tasking operating system for the Zilog Z-80 that ran natively on TRS-80 Model I computers and dates back to 1985 (FARGOS is actually an acronym for Fantastic And Really Great Operating System). The operating system was origined in the upper reaches of the 64K address space supported by the Z-80. As the other operating systems for the TRS-80 (e.g., TRS-DOS, NewDos/80, LDOS) utilized the lowermost portion of the address space, this choice permitted post-mortem debugging: a reset of the machine still preserved the contents of memory and it could be viewed by a debugger running under one of the commercial operating systems.
It was subsequently extended into a version (FARGOS/MP, where MP stands for Multiple Processors) that ran on a hypothetical multiprocessor version of the hardware. As no such hardware actually existed, this was tested and run using a simulator written in C and hosted on Xenix 3.0, but it would still run on the real single processor hardware.
The ROM in the TRS-80 Model I had enough capability to retrieve a single sector from drive 0, place it into memory and transfer control. To do something useful, a bootstrap loader needs to be written that is small enough to fit in that one sector and capable enough to retrieve enough of the operating system to continue the boot process. The FARGOS bootstrap loader appears below:
;* * * * * * * * * * * * * * * * * * * * * * * * * * *
; FARGOS/MP
; Fantastic And Really Great Operating System /
; Multiple Processors
; Version 1.1
; Author: Geoffrey C. Carpenter
;This code is the sole property of the author and
;all rights are reserved.
;* * * * * * * * * * * * * * * * * * * * * * * * * * *
;* * * * * * * * * * * * * * * * * * * * * * *
; FARGOS/MP BOOTSTRAP LOADER VERSION 1.0
;THIS IS VERY MACHINE SPECIFIC -- WORKS ONLY
;ON TRS-80 MODEL I'S. THIS LOADER ACCEPTS
;A LIMITED SUBSET OF THE FARGOS LOADER FILES.
;SPEFICICALLY, STORAGE ALLOCATION/RELOCATABLE
;SEGMENTS ARE NOT SUPPORTED
;* * * * * * * * * * * * * * * * * * * * * * *
;THIS CODE IS PLACED IN TRACK 0, SECTOR 0 OF
;A DISKETTE TO BE USED FOR A BOOT. THE FARGOS
;CODE FOLLOWS IN TRACK 0, SECTOR 1 UNTIL EOF.
;* * * * * * * * * * * * * * * * * * * * * * *
DRIVE EQU 37E1H ;DRIVE SELECT LATCH
FDCCMD EQU 37ECH ;FDC COMMAND REGISTER
FDCTRK EQU 37EDH ;FDC TRACK REGISTER
FDCSEC EQU 37EEH ;FDC SECTOR REGISTER
FDCDAT EQU 37EFH ;FDC DATA REGISTER
BUFFER EQU 7500H
STACK EQU 7400H
ORG 4200H
LD HL,FDCCMD ;POINT TO FDC CMD REG.
LD (HL),0FEH ;DON'T KNOW WHAT THIS DOES
LD (HL),0D0H ;CANCEL ANY COMMAND
LDR000 LD SP,STACK ;SET STACK POINTER
LD C,0 ;ZERO BUFFER COUNT
LDR010 CALL LDR500 ;GET NEXT BYTE IN FILE
LD A,(HL) ;GET BYTE IN A
CP 1 ;LOAD BLOCK CODE?
JR Z,LDR100 ;YES, LOAD BLOCK
CP 2 ;TRANSFER ADDR. CODE?
JR Z,LDR200 ;YES, GET ADDR. AND EXIT
AND 4 ;SKIP CODE?
JR NZ,LDR400 ;YES, SKIP BLOCK
JP LDRERR ;LOAD FILE FORMAT ERROR
LDR100 CALL LDR500 ;GET NEXT BYTE
LD B,(HL) ;GET LENGTH OF BLOCK
DEC B ;OFFSET = # BYTES + 2
DEC B
CALL LDR500 ;GET LSB OF BLOCK ADDR.
LD E,(HL) ;SAVE LSB IN E
CALL LDR500 ;GET MSB OF BLOCK ADDR.
LD D,(HL) ;DE HOLDS START ADDR.
LDR110 CALL LDR500 ;GET NEXT BYTE
LD A,(HL) ;PLACE IN A FOR TRANSFER
LD (DE),A ;SAVE IN STORAGE
INC DE ;POINT TO NEXT STORAGE LOCATION
DJNZ LDR110 ;PROCESS BLOCK
JR LDR010 ;PROCESS NEXT LOADER CODE
LDR200 CALL LDR500 ;GET NEXT BYTE, IGNORE
CALL LDR500 ;GET LSB TRANSFER ADDR.
LD E,(HL) ;SAVE IN E
CALL LDR500 ;GET MSB TRANSFER ADDR.
LD D,(HL) ;DE HOLDS TRANSFER ADDR.
PUSH DE ;TRANSFER ADDR.
POP HL ; TO HL
JP (HL) ;GO TO START
LDR400 CALL LDR500 ;GET SKIP COUNT
LD B,(HL) ;SAVE COUNT IN B
LDR410 CALL LDR500 ;GET NEXT BYTE
DJNZ LDR410 ;SKIP ENTIRE BLOCK
JR LDR010 ;PROCESS NEXT LOADER CODE
LDR500 LD A,C ;GET BUFFER COUNT
CP 0 ;NEED TO READ NEW SECTOR?
JR Z,LDR510 ;YES, GO READ SECTOR
INC HL ;POINT TO NEXT BYTE
INC C ;INCREMENT BUFFER COUNT
RET ;RETURN TO CALLER
LDR510 PUSH BC ;SAVE REGISTERS
PUSH DE
LD HL,FDCCMD ;HL POINTS TO FDCCMD
LD DE,FDCDAT ;DE POINTS TO DATA
LD BC,BUFFER ;BC POINTS TO DEST.
LDR515 BIT 0,(HL) ;FDC BUSY?
JR NZ,LDR515 ;YES, TRY AGAIN
LD A,1 ;DRIVE MASK
LD (DRIVE),A ;SELECT DRIVE
LD A,(SECTOR) ;GET CURRENT SECTOR
INC A ;ADD 1
CP 10 ;END OF TRACK?
JR NZ,LDR520 ;NO, SAVE NEW SECTOR
LD A,(TRACK) ;GET TRACK #
INC A ;MOVE TO NEXT TRACK
LD (TRACK),A ;SAVE NEW TRACK
LD (FDCDAT),A ;SAVE NEW TRACK
LD (HL),1BH ;DO SEEK COMMAND
PUSH BC ;DELAY, SO CONTROLLER CAN
LD B,6 ; DIGEST COMMAND
DJNZ $
POP BC
LDR517 BIT 0,(HL) ;FDC BUSY?
JR NZ,LDR517 ;YES, TRY AGAIN
XOR A ;ZERO A
LDR520 LD (FDCSEC),A ;SAVE NEW SECTOR
LD (SECTOR),A ;SAVE NEW SECTOR
LD (HL),88H ;READ COMMAND
PUSH BC ;DELAY, SO CONTROLLER
LD B,6 ; CAN DIGEST COMMAND
DJNZ $
POP BC
LDR525 LD A,(HL) ;GET CONTROLLER STATUS
BIT 1,A ;DATA REQUEST?
JR Z,LDR525 ;NO, TRY AGAIN
LDR530 LD A,(DE) ;GET DATA BYTE
LD (BC),A ;SAVE IN BUFFER
INC C ;INCREMENT ADDRESS
JR NZ,LDR525 ;GET NEXT BYTE IF NOT 256
LD HL,BUFFER ;HL POINTS TO BUFFER
POP DE ;RESTORE REGISTERS
POP BC
LD C,1 ;BUFFER COUNT = 1
RET ;RETURN TO CALLER
LDRERR LD HL,ERRMES ;HL POINTS TO ERROR MESS.
LD DE,3C00H ;TOP OF SCREEN
LD BC,LENMES ;LENGTH OF MESSAGE
LDIR ;DISPLAY
JR $ ;LOOP FOREVER
SECTOR DEFB 0
TRACK DEFB 0
ERRMES DEFM 'FARGOS/MP BOOT ERROR'
LENMES EQU $-ERRMES
ZEND EQU $
END
;* * * * * * * * * * * * * * * * * * * * * * * * * * *
; FARGOS/MP
; Fantastic And Really Great Operating System /
; Multiple Processors
; Version 1.1
; Author: Geoffrey C. Carpenter
;This code is the sole property of the author and
;all rights are reserved.
;* * * * * * * * * * * * * * * * * * * * * * * * * * *
SPRVSR EQU 0FF80H ;SUPERVISOR
GETBFR EQU 0FFADH ;GET A BUFFER FROM POOL
LDPROG EQU 0FFB3H ;LOAD PROGRAM INTO CORE
INSJOB EQU 0FFB9H ;INSERT JOB INTO XEQTBL
SLEEP EQU 0FFBCH ;PUT A PROGRAM TO SLEEP
WAKEUP EQU 0FFBFH ;WAKEUP A SLEEPING PROGRAM
CURENT EQU 0FFD0H ;ADDRESS USER XEQTBL ENTRY
IOHENT EQU 0FFD2H ;ADDRESS I/O HANDLER ENTRY
MEMTBL EQU 0FFD4H ;ADDRESS STORAGE ALLOCATION TABLE
DRIVE EQU 37E1H ;DRIVE SELECT LATCH
FDCCMD EQU 37ECH ;FDC COMMAND REGISTER
FDCTRK EQU 37EDH ;FDC TRACK REGISTER
FDCSEC EQU 37EEH ;FDC SECTOR REGISTER
FDCDAT EQU 37EFH ;FDC DATA REGISTER
NLOCKS EQU 10 ;MAX. 10 LOCKED SECTORS
;NOTE: CHANGE IN NLOCKS REQUIRES CHANGE IN LCKTBL SIZE
ORG 0FA00H
;* * * * * * * * * * * * * * * * * * * * * * * * *
;* MODULE: RDSKSC -- READ DISK SECTOR *
;* * * * * * * * * * * * * * * * * * * * * * * * *
RDSKSC LD A,(DE) ;GET BYTE 0 OF FCB
AND 127 ;RESET BIT 7
LD (DE),A ;REPLACE IN FCB
PUSH IX ;SAVE REGISTERS
PUSH BC
LD IX,RDQUE ;ADDR. READ QUEUE
AND 4 ;TEST NON-BLOCKING READ
LD C,A ;SAVE RESULT IN C
JR DSKIO ;QUEUE READ REQUEST
;* * * * * * * * * * * * * * * * * * * * * * * * *
; MODULE: WDSKSC -- WRITE DISK SECTOR *
;* * * * * * * * * * * * * * * * * * * * * * * * *
WDSKSC LD A,(DE) ;GET BYTE 0 OF FCB
OR 128 ;SET BIT 7
LD (DE),A ;REPLACE IN FCB
PUSH IX ;SAVE REGISTERS
PUSH BC
LD IX,WRQUE ;ADDR. WRITE QUEUE
AND 2 ;NON-BLOCKING WRITE
LD C,A ;SAVE RESULT IN C
;DSKIO QUEUES I/O REQUESTS FROM RDSKSC & WDSKSC
DSKIO PUSH HL ;SAVE REGISTERS
PUSH IY
LD HL,IOSEM ;GET ADDR. I.O SEMAPHORE
CALL SETSEM ;SET IT
LD A,(IX+1) ;GET MSB START-OF-QUEUE
CP 0FFH ;EMPTY QUEUE?
JR NZ,DSK005 ;GO IF NON-EMPTY
;ADD ENTRY TO BEGINNING OF QUEUE
LD (IX+0),E ;SAVE LSB OF NEW ENTRY
LD (IX+1),D ;SAVE MSB
JR DSK007 ;COMPLETE INSERTION
DSK005 LD L,(IX+2) ;GET ADDR. LAST QUEUE
LD H,(IX+3) ; ENTRY
LD (IX+2),E ;SAVE ADDR. OF NEW
LD (IX+3),D ; ENTRY AS LAST
PUSH HL ;ADDR. OLD LAST QUEUE
POP IX ; ENTRY TO IX
LD (IX+4),E ;SAVE POINTER TO NEW ENTRY
LD (IX+5),D ;SAVE MSB
DSK007 PUSH DE ;COPY FDB ADDR.
POP IY ; TO IY
LD (IY+4),0FFH ;MARK END OF QUEUE
LD (IY+5),0FFH
LD (IY+15),0 ;MARK AS BLOCKING
CALL CURENT ;GET XEQTBL ENTRY IN IX
PUSH IX ;MOVE TO
POP HL ; HL
LD (IY+1),L ;SAVE XEQTBL ENTRY ADDR.
LD (IY+2),H
LD A,(DE) ;GET I/O MODE STATUS
AND 1 ;FLUSH WRITE QUEUE?
JR Z,DSK010 ;NO, GO ON
LD (FLSHIO),A ;SET FLUSH QUEUE FLAG
DSK010 LD HL,IOSEM ;GET ADDR. I/O SEMAPHORE
CALL RESSEM ;REQUEST COMPLETE
LD IX,(IOHENT) ;GET XEQTBL ENTRY OF I/O HANDLER
LD A,(IX+1) ;GET PROCESS STATUS
AND 64 ;IS IT ASLEEP?
JR Z,DSK020 ;NO, GO ON
PUSH IX ;TRANSFER ADDR. TO
POP HL ; FOR WAKEUP
CALL WAKEUP ;WAKEUP THE I/O HANDLER
DSK020 LD A,C ;GET NON-BLOCKING STATUS
OR A ;SET FLAGS
JR NZ,DSK085 ;GO IF NON-BLOCKING REQUEST
CALL SLEEP ;WAIT FOR I/O HANDLER TO
;PROCESS REQUEST AND WAKE US UP...
LD (IY+15),A ;GET STATUS
JR DSK090 ;EXIT
DSK085 LD A,254 ;STATUS = I/O IN PROGRESS
LD (IY+15),A ;STATUS = I/O IN PROGRESS
DSK090 POP IY ;RESTORE REGISTERS
POP HL
POP BC
POP IX
OR A ;SET FLAGS
RET ;RETURN TO CALLER
;* * * * * * * * * * * * * * * * * * * * * * * * *
;* MODULE: IOHAND -- DISK I/O HANDLER *
;* * * * * * * * * * * * * * * * * * * * * * * * *
IOHAND LD A,(FLSHIO) ;GET FLUSH I/O FLAG
OR A ;SET FLAGS
JR NZ,IOH001 ;GO IF FLUSH REQUESTED
LD HL,(RDQUE) ;GET 1ST ENTRY OF READ QUEUE
LD IY,RDQUE ;QUEUE IS READ
LD A,H ;GET MSB OF ADDR.
CP 0FFH ;EMPTY?
JR NZ,IOH005 ;NO, GO ON
LD HL,(WRQUE) ;GET 1ST ENTRY OF WRITE QUEUE
LD IY,WRQUE ;QUEUE IS WRITE
JR IOH005
IOH001 LD HL,(WRQUE) ;GET 1ST ENTRY OF WRITE QUEUE
LD IY,WRQUE ;QUEUE IS WRITE
LD A,H ;GET MSB OF ADDR.
SUB 0FFH ;EMPTY?
JR NZ,IOH005 ;NO, GO ON
LD (FLSHIO),A ;CLEAR FLUSH WRITE QUEUE REQUEST
LD HL,(RDQUE) ;GET 1ST ENTRY OF READ QUEUE
LD IY,RDQUE ;QUEUE IS READ
IOH005 LD A,H ;GET MSB OF QUEUE ADDR.
CP 0FFH ;EMPTY QUEUE?
JR NZ,IOH007 ;GO IF NOT EMPTY
;NO WORK TO DO, GO TO SLEEP UNTIL ANOTHER REQUEST MADE
CALL SLEEP ;GO TO SLEEP UNTIL WOKEN UP
JR IOHAND ;CHECK NEW QUEUE STATUS
IOH007 PUSH HL ;TRANSFER REQUEST ADDR.
POP IX ; TO IX
LD (REQPTR),IY ;SAVE ADDR. PTR. TO REQUEST
;IX POINTS TO REQUEST (FDB), REQPTR POINTS TO POINTER TO REQUEST
LD A,(IX+0) ;GET I/O REQUEST TYPE
AND 128 ;DETERMINE READ/WRITE
JR Z,IOREAD ;GO IF READ OPERATION
;DISK SECTOR WRITE HERE
LD A,(IX+0) ;GET MODE
AND 8 ;RELEASE LOCK ON WRITE?
JR Z,IOW010 ;NO, GO ON
LD A,(IX+8) ;GET SECTOR
LD (LCKKEY),A
LD A,(IX+7) ;GET TRACK
LD (LCKKEY+1),A
LD A,(IX+16) ;GET DRIVE
LD (LCKKEY+2),A
;KEY = SECTOR:TRACK:DRIVE
CALL FINDLK ;SEARCH FOR ENTRY
JR NZ,IOW010 ;GO IF NOT FOUND
LD (HL),0 ;CLEAR LOCK (3 BYTES)
INC HL
LD (HL),0
INC HL
LD (HL),0
IOW010 CALL SEEK ;SEEK TRACK & SECTOR
JR NZ,IOHERR ;GO IF ERROR DURING SEEK
;CHECK FOR WRITE PROTECTED DISKETTE, HL SET IN SEEK
BIT 6,(HL) ;WRITE PROTECTED DISKETTE?
JR Z,IOW015 ;NO, GO ON
LD A,15 ;RETURN CODE = 15
JR IOHERR ;EXIT
IOW015 LD A,(FDCWR) ;GET WRITE COMMAND
CALL IOINIT ;OUTPUT COMMAND
;DO WRITE FROM BUFFER POINTED TO BY DE
IOW025 LD A,(HL) ;GET STATUS
AND 83H ;CHECK BUSY, DRQ, DRIVE
JP PO,IOW025 ;ONLY 1 BIT SET, TRY AGAIN
LD A,(DE) ;GET BYTE FROM BUFFER
LD (FDCDAT),A ;GIVE FDC DATA BYTE
INC DE ;POINT TO NEXT BYTE
BIT 7,(HL) ;DRIVE STILL READY?
JR NZ,IOW027 ;GO IF DRIVE OFF
DJNZ IOW025 ;SEND 256 BYTES
IOW027 LD A,(HL) ;GET STATUS OF COMMAND
AND 60 ;ANY ERRORS?
JR Z,IOHERR ;NO, EXIT, A=0
BIT 5,A ;WRITE FAULT?
JR Z,IOW030 ;NO, CHECK NEXT ERROR
LD A,14 ;RETURN CODE = 14
JR IOHERR ;EXIT
IOW030 BIT 4,A ;RECORD NOT FOUND?
JR Z,IOW031 ;NO, CHECK NEXT ERROR
LD A,13 ;RETURN CODE = 13
JR IOHERR ;EXIT
IOW031 BIT 3,A ;CRC ERROR?
JR Z,IOW032 ;NO, DO NEXT ERROR
LD A,1 ;RETURN CODE = 1
JR IOHERR ;EXIT
IOW032 LD A,11 ;RETURN CODE = 11
JR IOHERR ;EXIT
;I/O READ HERE
IOREAD LD A,(IX+8) ;GET SECTOR
LD (LCKKEY),A
LD A,(IX+7) ;GET TRACK
LD (LCKKEY+1),A
LD A,(IX+16) ;GET DRIVE
LD (LCKKEY+2),A
;KEY = SECTOR:TRACK:DRIVE
CALL FINDLK ;LOOK FOR LOCKED SECTOR
JR NZ,IOR006 ;GO IF NOT LOCKED
LD A,1 ;FLAG FOR FLUSH
LD (FLSHIO),A ;FORCE FLUSH OF WRITE QUEUE
JR IOH100 ;NOW SKIP THIS REQUEST
IOR006 LD A,(IX+0) ;GET I/O MODE
AND 16 ;SET LOCK ON READ?
JR Z,IOR010 ;NO, GO ON
;FIND FREE LOCK
XOR A ;ZERO A
LD (LCKKEY),A ;ZERO KEY
LD (LCKKEY+1),A
LD (LCKKEY+2),A
CALL FINDLK ;FIND FREE ENTRY
JR Z,IOR007 ;GO IF FOUND
LD A,4 ;TOO MANY LOCKS
JR IOHERR ;EXIT
;SET LOCK FLAG
IOR007 LD A,(IX+8) ;GET SECTOR
LD (HL),A
INC HL
LD A,(IX+7) ;GET TRACK
LD (HL),A
INC HL
LD A,(IX+16) ;GET DRIVE
LD (HL),A
INC HL
IOR010 CALL SEEK ;SEEK TRACK & SECTOR
JR NZ,IOHERR ;GO IF ERROR DURING SEEK
LD A,(FDCRD) ;GET READ COMMAND
CALL IOINIT ;OUTPUT COMMAND AND INITIALIZE
;DO READ INTO BUFFER POINTED TO BY DE
IOR050 LD A,(HL) ;GET FDC STATUS
AND 83H ;CHECK BUSY, DRQ, DRIVE
JP PO,IOR050 ;ONLY 1 BIT SET, TRY AGAIN
LD A,(FDCDAT) ;GET BYTE FROM FDC
LD (DE),A ;SAVE IN BUFFER
INC DE ;POINT TO NEXT ENTRY
BIT 7,(HL) ;DRIVE STILL READY?
JR NZ,IOR052 ;GO IF ERROR BIT SET
DJNZ IOR050 ;GET 256 BYTES
IOR052 LD A,(HL) ;GET COMMAND STATUS
AND 28 ;ANY ERRORS?
JR Z,IOHERR ;NO, EXIT, A=0
BIT 4,A ;RECORD NOT FOUND?
JR Z,IOR053 ;NO, CHECK NEXT ERROR
LD A,5 ;RETURN CODE = 5
JR IOHERR ;EXIT
IOR053 BIT 3,A ;CRC ERROR?
JR Z,IOR055 ;NO, DO NEXT ERROR
LD A,1 ;RETURN CODE = 1
JR IOHERR ;EXIT
IOR055 LD A,3 ;RETURN CODE = 3
;UNLINK I/O REQUEST AND RETURN STATUS TO USER TASK
IOHERR EI ;END TIME CRITICAL SECTION
LD HL,IOSEM ;ADDR. I/O SEMAPHORE
CALL SETSEM ;SET THE SEMAPHORE
LD IY,(REQPTR) ;POINTER TO QUEUE
LD L,(IX+5) ;GET PTR. TO NEXT
LD H,(IX+6) ; I/O REQUEST
LD (IY+0),L ;SAVE AS START OF QUEUE
LD (IY+1),H
PUSH HL ;SAVE FOR NEXT PASS
LD B,(IX+15) ;GET BLOCKED STATUS
LD (IX+15),A ;SAVE RESULT
LD A,B ;GET BLOCKED STATUS
CP 0 ;BLOCKED?
JR NZ,IOHE10 ;NO, GO ON
;WAKE UP THE SLEEPING TASK....
LD L,(IX+1) ;GET ADDR. XEQTBL ENTRY
LD H,(IX+2)
CALL WAKEUP ;WAKE THE USER TASK
IOHE10 LD HL,IOSEM ;GET ADDR. I/O SEMAPHORE
CALL RESSEM ;RESET IT
POP HL ;RESTORE NEXT QUEUE ENTRY
;GIVE UP TIME SLICE TO COMPENSATE FOR USEAGE OF DI
CALL SPRVSR ;RELEASE TIME SLICE
IOHE20 LD A,H ;GET MSB. OF ADDR.
CP 0FFH ;END OF QUEUE?
JP NZ,IOH007 ;NO, DO NEXT REQUEST
JP IOHAND ;RESTART CHECKING QUEUES
;SKIP READ REQUEST BECAUSE OF READ-LOCKED SECTOR
IOH100 LD HL,5 ;OFFSET OF NEXT REQUEST POINTER
PUSH IX ;CURRENT REQUEST POINTER
POP IY ; TO IY
ADD IY,HL ;POINT TO NEXT-REQUEST-PTR
LD L,(IX+5) ;GET NEXT REQUEST
LD H,(IX+6) ; ADDR. IN HL
JR IOHE20 ;PROCESS NEXT REQUEST
;SEEK TRACK & SECTOR
SEEK LD HL,FDCCMD ;HL = FDC COMMAND/STATUS REGISTER
SEEK05 BIT 0,(HL) ;FDC BUSY?
JR Z,SEEK10 ;NO, GO ON
CALL SPRVSR ;RELEASE TIME SLICE
JR SEEK05 ;TRY AGAIN
SEEK10 LD (HL),0D0H ;FORCE INTERRUPT--CLEAR STATUS
LD B,(IX+16) ;GET NEW DRIVE MASK
LD A,(LDRIVE) ;GET MASK LAST DRIVE ACCESSED
CP B ;NEW DRIVE = OLD DRIVE?
JR Z,SEEK11 ;YES, DON'T BOTHER UPDATING TBL.
PUSH IX ;SAVE IX
LD A,B ;NEW DRIVE MASK TO A
LD (LDRIVE),A ;SAVE AS LAST DRIVE ACCESSED
CALL GETDRV ;GET ADDR. TRACK TBL. ENTRY
LD A,(IX+3) ;GET TRACK POS. THIS DRIVE
LD (FDCTRK),A ;SAVE AS CURRENT TRACK
POP IX ;RESTORE IX
SEEK11 LD A,(IX+16) ;GET DRIVE MASK
LD (DRIVE),A ;SELECT DRIVE
CALL DSKCHK ;CHECK FOR DISKETTE IN DRIVE
RET NZ ;RETURN IF ERROR
PUSH IX ;SAVE IX
LD A,(IX+16) ;GET DRIVE MASK
LD B,(IX+7) ;GET TRACK NUMBER
CALL GETDRV ;GET ADDR. TRACK TBL. ENTRY
LD (IX+3),A ;SAVE NEW TRACK IN TBL.
LD (FDCDAT),A ;SELECT TRACK
LD A,(IX+0) ;GET STEPPING RATE
AND 3 ;MASK STEP RATE
LD B,A ;SAVE STEP RATE MASK IN B
LD A,(FDCSK) ;GET SEEK COMMAND
OR B ;OR IN STEP RATE
LD (FDCCMD),A ;PERFORM SEEK
POP IX ;RESTORE IX
SEEK15 CALL SPRVSR ;RELEASE TIME SLICE
BIT 0,(HL) ;IS FDC BUSY?
JR NZ,SEEK15 ;YES, TRY AGAIN
BIT 4,(HL) ;SEEK ERROR?
JR Z,SEEK16 ;NO, GO ON
LD A,(FDCRST) ;GET RESTORE COMMAND
OR B ;OR IN STEP RATE
LD (HL),A ;DO RESTORE
LD B,6 ;DELAY SO THAT
DJNZ $ ;FDC CAN SWALLOW CMD.
JR SEEK05 ;RETRY OPERATION FROM TOP
SEEK16 BIT 3,(HL) ;CRC ERROR?
JR Z,SEEK17 ;NO, GO ON
LD A,1 ;RETURN CODE = 1
RET ;RETURN WITH ERROR
SEEK17 LD A,(IX+10) ;GET SECTOR REQUESTED
LD (FDCSEC),A ;SELECT SECTOR
XOR A ;ZERO A & SET FLAGS
RET
;END OF SEEK SUBROUTINE
;OUTPUT I/O COMMAND, INITIALIZE REGISTERS
IOINIT DI ;TIME CRITICAL SECTION
LD (HL),A ;PERFORM READ/WRITE
LD B,6 ;DELAY SO THAT
DJNZ $ ; FDC CAN SWALLOW CMD
LD E,(IX+3) ;GET ADDRESS
LD D,(IX+4) ; OF BUFFER
LD B,0 ;B=256 BYTES TO MOVE
RET ;COMMAND/INITIALIZATIN DONE
;CHECK FOR DISKETTE MOUNTED IN DRIVE. EXPECT TO SEE IT "WHIRLING"
;AND STROBING THE INDEX PULSE.
DSKCHK DI ;TIME CRITICAL SECTION
;BECAUSE OF INDEX PULSE WIDTH
LD BC,0 ;DELAY = FFFFH+1
DSKC05 BIT 1,(HL) ;TEST FOR INDEX PULSE
JR Z,DSKC10 ;GO IF HOLE COVERED
DEC BC ;DECREMENT COUNT
LD B,A ;GET MSB COUNT IN A
OR C ;IS COUNT=0?
JR NZ,DSKC05 ;NO, TRY AGAIN
JR DSKC90 ;NO DISK IN DRIVE
DSKC10 LD BC,0 ;DELAY = FFFFH+1
DSKC11 BIT 1,(HL) ;TEST FOR INDEX PULSE
JR NZ,DSKC95 ;GO IF PULSE DETECTED
DEC BC ;DECREMENT COUNT
LD A,B ;GET MSB OF COUNT
OR C ;IS COUNT=0?
JR NZ,DSKC11 ;NO, TRY AGAIN
DSKC90 LD A,8 ;RETURN CODE = 8
OR A ;SET FLAGS
JR DSKC96 ;EXIT
DSKC95 XOR A ;RETURN CODE = 0
DSKC96 EI ;END TIME CRITICAL SECTION
RET ;RETURN TO CALLER
;SHOULD ADD CHECK FOR ILLEGAL DRIVE NUMBER
GETDRV PUSH BC ;SAVE REGISTERS
LD IX,DRVTBL ;GET ADDR. DRIVE TABLE
LD BC,16 ;16 BYTE ENTRIES
GETD05 RRA ;SHIFT A RIGHT 1
JR C,GETD10 ;C=1, DONE SHIFTING
ADD IX,BC ;POINT TO NEXT TABLE ENTRY
JR GETD05 ;KEEP SHIFTING
GETD10 POP BC ;RESTORE BC
RET ;RETURN WITH IX POINTING TO ENTRY
;FIND LOCK ENTRY
FINDLK PUSH BC ;SAVE REGISTERS
PUSH DE
LD HL,LCKTBL ;GET START OF LOCK TABLE
LD B,NLOCKS ;GET NUMBER OF LOCKS
FIND05 PUSH BC ;SAVE COUNT
LD DE,LCKKEY ;GET LOCK KEY
LD B,3 ;ENTRY LENGTH
PUSH HL ;PUSH START ADDR.
FIND10 LD A,(HL) ;GET TABLE ENTRY
CP (DE) ;COMPARE TO KEY
JR NZ,FIND20 ;GO IF NOT EQUAL
INC HL ;POINT TO NEXT CHAR
INC DE
DJNZ FIND10 ;CHECK REST.
POP HL ;RESTORE START
POP BC ;RESTORE COUNT
XOR A ;ZERO A
JR FIND90 ;EXIT
FIND20 POP HL ;RESTORE START
LD BC,3 ;OFFSET = 3
ADD HL,BC ;POINT TO NEXT ENTRY
POP BC ;RESTORE COUNT
DJNZ FIND05 ;CHECK NEXT ENTRY
LD A,4 ;NOT FOUND
FIND90 POP DE ;RESTORE REGISTERS
POP BC
OR A ;SET FLAGS
RET
RDQUE DEFW 0FFFFH ;1ST ENTRY IN READ QUEUE
DEFW 0 ;LAST ENTRY IN READ QUEUE
WRQUE DEFW 0FFFFH ;1ST ENTRY IN WRITE QUEUE
DEFW 0 ;LAST ENTRY IN WRITE QUEUE
LDRIVE DEFB 8 ;MASK OF DRIVE LAST ACCESSED
;NOTE SEEK RATES ARE 0--RATES ARE OR'ED IN FROM DRIVE TABLE ENTRIES
FDCSK DEFB 1CH ;FDC SEEK COMMAND
FDCRST DEFB 8 ;FDC RESTORE COMMAND
FDCRD DEFB 88H ;FDC READ SECTOR COMMAND
FDCWR DEFB 0A8H ;FDC WRITE SECTOR COMMAND
;NOTE: RESTRICTED SUPPORT FOR 2 DRIVES ONLY!
;FULL IMPLEMENTATION MUST HAVE ANOTHER 32 BYTES ALLOCATED.
NUMDRV DEFB 2 ;MAXIMUM 2 DRIVES
DRVTBL DEFB 3 ;SINGLE DENSITY, 40MS SEEK
DEFB 39 ;40 TRACKS/DRIVE
DEFB 9 ;10 SECTORS/TRACK
DEFB 1 ;HEAD IS AT POSITION 1 AFTER BOOT
DEFB 17 ;DIRECTORY ON TRACK 17
DEFW 0FFFFH ;NO VSAM READ
DEFB 0 ;NO I/O REQUESTS PENDING
DEFS 8 ;SKIP REST OF ENTRY
;DRIVE 1 ENTRY
DEFB 3 ;SINGLE DENSITY, 40MS SEEK
DEFB 39 ;40 TRACKS/DRIVE
DEFB 9 ;10 SECTORS/TRACK
DEFB 1 ;HEAD IS AT POSITION 1 AFTER BOOT
DEFB 17 ;DIRECTORY ON TRACK 17
DEFW 0FFFFH ;NO VSAM READ
DEFB 0 ;NO I/O REQUESTS PENDING
DEFS 8 ;SKIP REST OF ENTRY
;SECTOR READ LOCK TABLE
LCKTBL DEFW 0 ;10 3-BYTE ENTRIES, INITIALLY ZERO
DEFW 0 ; SO 15 2-BYTE ZERO-DEFINES
DEFW 0
DEFW 0
DEFW 0
DEFW 0
DEFW 0
DEFW 0
DEFW 0
DEFW 0
DEFW 0
DEFW 0
DEFW 0
DEFW 0
DEFW 0
LCKKEY DEFS 3 ;LOCK SEARCH KEY
;* * * * * * * * * * * * * * * * * * * * * * * * *
; MODULE: XEQJOB -- LOAD AND EXECUTE A PROGRAM
;ENTRY: HL POINTS TO FILENAME/ENVIRONMENT BLOCK
;EXIT: IX POINTS TO EXECUTION TABLE ENTRY
;TASK STARTED, ENVIRONMENT BLOCK CHANGES OWNERSHIP
;* * * * * * * * * * * * * * * * * * * * * * * * *
XEQJOB PUSH BC ;SAVE REGISTERS
PUSH DE
PUSH HL
PUSH IY
PUSH HL ;SAVE ENVIRONMENT ADDR.
POP IY ; IN IY
CALL GETFCB
JR NZ,XEQXIT ;GO IF NO FCB AVAILABLE
CALL LDPROG ;LOAD PROGRAM
JR NZ,XEQXIT ;NO, RETURN ERROR CODE
CALL INSJOB ;ADD JOB TO XEQTBL
JR NZ,XEQXIT ;QUIT IF ERROR
PUSH IY ;RESTORE ENVIRONMENT ADDR.
POP HL ; TO HL
;ENVIRONMENT REQUIRED IN CURRENT RELEASE
; LD A,H ;GET MSB OF ENVIRONMENT ADDR.
; SUB 0FFH ;NO ENVIRONMENT?
; JR Z,XEQ090 ;GO IF NO ENVIRONMENT
;TRANSFER OWNERSHIP OF ENVIRONMENT BLOCK
LD L,H ;SAVE BASE OF ENVIRONMENT IN L
LD H,(IX+4) ;GET BASE OF TDB/SAM
INC H ;POINT TO SAM
;L ALREADY HOLDS DESIRED OFFSET, HL=ENTRY IN CHILD'S SAM
PUSH IX ;SAVE FOR START UP OF TASK
CALL CURENT ;GET PARENT'S XEQTBL ENTRY
LD E,L ;COPY OFFSET TO E
LD D,(IX+4) ;GET BASE OF TDB/SAM
POP IX ;RESTORE CHILD'S XEQTBL ENTRY
INC D ;POINT TO PARENT'S SAM
LD A,(DE) ;GET PARENT'S ALLOCATION ENTRY
LD (HL),A ;COPY INTO CHILD'S SAM
XOR A ;ZERO A
LD (DE),A ;CLEAR PARENT'S ENTRY
XEQ090 LD (IX+1),A ;LET THE TASK START RUNNING
XEQXIT POP IY ;RESTORE REGISTERS
POP HL
POP DE
POP BC
OR A ;SET FLAGS
RET ;RETURN TO CALLER
;* * * * * * * * * * * * * * * * * * * * * * * * *
; MODULE: GETFCB -- GET AND PREPARE AN FCB
;ENTRY: HL = FILESPEC
;EXIT: DE = PREPARED FCB (FOR FOPEN)
;* * * * * * * * * * * * * * * * * * * * * * * * *
GETFCB PUSH HL ;SAVE REGISTERS
PUSH BC
PUSH IX
CALL CURENT ;GET XEQTBL ENTRY ADDR.
LD D,(IX+5) ;GET ADDR. TDB
LD E,128 ;OFFSET OF TDB IS 128
PUSH HL ;SAVE FILESPEC ADDR.
PUSH DE ;PUT IN
POP IX ; IX
LD DE,16 ;FCB ENTRY IS 16 BYTES LONG
LD B,8 ;MAX. 8 FCB ENTRIES
GETF10 LD A,(IX+14) ;MSB OF FDCB POINTER
CP 0 ;IN USE?
JR Z,GETF20 ;NO, FOUND IT
ADD IX,DE ;GET ADDR. NEXT ENTRY
DJNZ GETF10 ;KEEP SEARCHING
LD A,68 ;NO FREE FCB
POP HL ;RESTORE FILESPEC ADDR.
JR GETF90 ;EXIT WITH ERROR
GETF20 POP HL ;RESTORE FILESPEC ADDR.
PUSH IX ;TRANSFER FCB ADDR.
POP DE ;INTO DE
PUSH DE ;SAVE IT
LD BC,14 ;MAXIMUM FILE LENGTH
GETF01 LD A,(HL) ;GET CHARACTER
CP ' ' ;A SPACE?
JR Z,GETF02 ;YES, END OF FILESPEC
LDI ;COPY (HL) TO (DE)
JR NZ,GETF01 ;KEEP COPYING
JR GETF60 ;DONE
GETF02 LD A,C ;GET COUNT
CP 0 ;ALL DONE?
JR Z,GETF60 ;YES, GO ON
LD B,A ;COUNT TO B
GETF03 LD (HL),20H ;PAD OUT FILESPEC
INC HL
DJNZ GETF03 ;FILL OUT REST WITH BLANKS
GETF60 POP DE ;RESTORE FCB ADDR.
XOR A ;ZERO A
GETF90 POP IX ;RESTORE REGISTERS
POP BC
POP HL
OR A ;SET FLAGS
RET ;RETURN TO CALLER
ZEND EQU $
ORG 0FF8FH ;RDSKSC JP VECTOR
JP RDSKSC
ORG 0FF92H ;WDSKSC JP VECTOR
JP WDSKSC
ORG 0FFB6H ;XEQJOB JP VECTOR
JP XEQJOB
END