FARGOS Source Code

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.

Bootstrap Loader

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

Disk I/O Layer

The disk I/O layer included routines to read/write sectors of the disk, maintain a queue of pending I/O operations, and execute jobs.
;* * * * * * * * * * * * * * * * * * * * * * * * * * *
;               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