              IF      IODRIVERBODY
*             PHYSICAL DISK DRIVERS STORAGE "DEFS"

::            SET     *
              ORG     DSKINFO:SIZE

*             TACKS ON TO BOTTOM OF DISK INFO TABLE

FDREADWRITE   RMB     1                 0 IS READ, <>0 IS WRITE
FDDSTATEJ     RMB     1                 JMP instruction
FDDSTATE      RMB     2                 address for JMP instruction
FDSEEKRETRY   RMB     1                 NUMBER OF RE-SEEKS
FDRETRY       RMB     1                 READ/WRITE RETRY COUNT
FDDRIVE       RMB     1                 DRIVE NUMBER TO USE
FDCYL         RMB     1                 what track we're on (-1 if lost)
FDSECTOR      RMB     1                 GIMME THIS ONE
FDCOMPLEMENT  RMB     1                 COMPLEMENT DATA
FDFIRSTSEC    RMB     1                 FIRST SECTOR ON TRACK
FDHEADCHAIN   RMB     2                 head of shared-head queue
FDNEXTCHAIN   RMB     2                 next on shared-head queue
FDCCB         RMB     2                 address of controller table
FDMAPALG      RMB     2                 current map algorithm
FDK1MODNSPT   RMB     1                 SPIRALING CONSTANT MOD NSPT
FDK2MODNSPT   RMB     1                 2*SC MOD NSPT
FDK4MODNSPT   RMB     1                 4*SC MOD NSPT
FDK8MODNSPT   RMB     1                 8*SC MOD NSPT
FDK16MODNSPT  RMB     1                 16*SC MOD NSPT
FDK32MODNSPT  RMB     1                 32*SC MOD NSPT
FDMAP         EQU     *                 MAP FOR MAPPING
; virtual floppy dcb allocates room needed for FDMAP
FDSIZE        EQU     *
                  PAGE
*                 Controller Tables

                  ORG     0

CCB:BUSY          RMB     1              controller is busy
CCB:ADDR          RMB     2              controller address
CCB:TIMEOUT       RMB     1              seconds before controller times out
CCB:DRIVE         RMB     1              drive to access
CCB:CYL           RMB     1              cylinder to access on that drive
CCB:LASTCYL       RMB     1              last cylinder accessed, that drive
CCB:STARTIO       RMB     2              address of STARTIO routine
CCB:STATUS        RMB     3              call for status
CCB:RESET         RMB     3              call to abort and interrupt
CCB:ABORT         RMB     3              call to abort
CCB:RESTORE       RMB     3              call to restore drive
CCB:SETSEEK       RMB     3              call to set desired drive and track
CCB:SEEK          RMB     3              call to initiate seek
CCB:READSECTOR    RMB     3              call to read sector
CCB:WRITESECTOR   RMB     3              call to write sector
CCB:VERIFYSECTOR  RMB     3              call to verify sector just written
CCB:TIMEOUTBLK    RMB     TIMEOUT:SIZE   timeout block for controller
CCB:CURRENTDCB    RMB     2              address of current DCB
CCB:SIZE          EQU     *
                  ORG     ::
                  FIN     IODRIVERBODY
                  IF      IODRIVERRAM
DISKINTDCB        RMB     2              address of DCB for interrupt service
DISKINTCCB        RMB     2              address of CCB for interrupt service
           PAGE
*                 Controller Definitions

                  IF      PERSCI
*                 PerSci Controller

CCB:PERSCI
                  FCB     1              controller busy: 0 = yes, <>0 = no
                  FDB     $FFA0          address
                  FCB     0              timeout counter
                  FCB     $FF            drive to access
                  FCB     $FF            cylinder to access
                  FCB     $FF            last cylinder accessed
                  FDB     DISKINTSTARTPERSCI
                  JMP     PERSCI:STATUS
                  JMP     PERSCI:RESET
                  JMP     PERSCI:ABORT
                  JMP     PERSCI:RESTORE
                  JMP     PERSCI:SETSEEK
                  JMP     PERSCI:SEEK
                  JMP     PERSCI:READSECTOR
                  JMP     PERSCI:WRITESECTOR
                  JMP     PERSCI:VERIFYSECTOR
FDTIMEOUTBLOCK    SET     *
                  FDB     NEXTTIMEOUT    timeout block for PerSci floppies
                  FDB     0              fuse length
                  FDB     PERSCI:TIMEOUT
NTIMEOUTS         SET     NTIMEOUTS+1
NEXTTIMEOUT       SET     FDTIMEOUTBLOCK
                  ORG     FDTIMEOUTBLOCK+TIMEOUT:SIZE
                  FDB     0              current DCB
                  IF      DAMFLOPPY
                  PAGE
                  FIN     DAMFLOPPY
                  FIN     PERSCI
                  IF      DAMFLOPPY
*                 DAM Floppy Controller

CCB:DAMFLOPPY
                  FCB     1              controller busy: 0 = yes, <>0 = no
                  FDB     $FF80          address
                  FCB     0              timeout counter
                  FCB     $FF            drive to access
                  FCB     $FF            cylinder to access
                  FCB     $FF            last cylinder accessed
                  FDB     DISKINTSTARTDAMFLOPPY
                  JMP     DAMFLOPPY:STATUS
                  JMP     DAMFLOPPY:RESET
                  JMP     DAMFLOPPY:ABORT
                  JMP     DAMFLOPPY:RESTORE
                  JMP     DAMFLOPPY:SETSEEK
                  JMP     DAMFLOPPY:SEEK
                  JMP     DAMFLOPPY:READSECTOR
                  JMP     DAMFLOPPY:WRITESECTOR
                  JMP     DAMFLOPPY:VERIFYSECTOR
FDTIMEOUTBLOCK    SET     *
                  FDB     NEXTTIMEOUT    timeout block for PerSci floppies
                  FDB     0              fuse length
                  FDB     DAMFLOPPY:TIMEOUT
NTIMEOUTS         SET     NTIMEOUTS+1
NEXTTIMEOUT       SET     FDTIMEOUTBLOCK
                  ORG     FDTIMEOUTBLOCK+TIMEOUT:SIZE
                  FDB     0              current DCB
                  FIN     DAMFLOPPY
           PAGE
           INCLUDE IOVFDDCBS.ASM
           FIN     IODRIVERRAM
              IF      IODRIVERINIT
FDRESTORE
              LDX     #OKRTS            do the following once only
              STX     FDRESTORE
              LDX     #0                reset the PIA(s)
              IF      PERSCI
              STX     PERSCI:PIACA
              FIN     PERSCI
              IF      DAMFLOPPY
              STX     DAMFLOPPY:PIACA
              FIN     DAMFLOPPY
              LDX     #$FFFF
              IF      PERSCI
              STX     PERSCI:PIADA
              LDAA    #$A5             see if Persci controller exists
              STAA    PERSCI:PIADA
              NEGA
              ADDA    PERSCI:PIADA     gives zero IFF Persci exists
              NEGA                     gives carry IFF Persci exists
              LDAA    #0               form Persci Interrupt Test mask
              RORA                     (A)=$80 --> Persci exists
              STAA    PERSCIINTERRUPTMASK
              FIN     PERSCI
              IF      DAMFLOPPY
              STX     DAMFLOPPY:PIADA
              LDAA    #$A5             see if DAM Floppy controller exists
              STAA    DAMFLOPPY:PIADA
              NEGA
              ADDA    DAMFLOPPY:PIADA  gives zero IFF DAM Floppy exists
              NEGA                     gives carry IFF DAM Floppy exists
              LDAA    #0               form DAM Floppy Interrupt Test mask
              RORA                     (A)=$80 --> DAM Floppy exists
              STAA    DAMFLOPPYINTERRUPTMASK
              FIN     DAMFLOPPY
              LDX     #%0010110000000111
              IF      PERSCI
              STX     PERSCI:PIACA
              LDAA    PERSCI:PIADB      clear possible interrupt
              FIN     PERSCI
              IF      DAMFLOPPY
              STX     DAMFLOPPY:PIACA
              LDAA    DAMFLOPPY:PIADB   clear possible interrupt
              FIN     DAMFLOPPY
              OKRTS
              FIN     IODRIVERINIT
           IF      IODRIVERBODY
           PAGE
FDDRIVER   FDB     FDRESTORE
           FDB     FDREAD
           FDB     FDWRITE
           FDB     FDWAITDONE
           FDB     FDSTATUS
           FDB     FDCONTROL


*          FDCONTROL -- CONTROL OPERATION ENTRY POINT FOR SECTOR I/O DRIVER

FDCONTROL  CMPA    #CC:DISMOUNTDISK      SINCE SDOS PASSES THIS THRU
           BEQ     FDDISMOUNT            B/ ITS A DISMOUNT!
           JMP     ILLDEVICEOP           NOT A LEGAL CONTROL CALL

FDDISMOUNT OKRTS                         I'M HAPPY...

*          FDSTATUS-- HANDLE STATUS REQUEST

FDSTATUS
           JMP      ILLDEVICEOP          ; NO SUCH STATUS AVALIABLE

          PAGE
*         FDREAD/WRITE -- START SINGLE SECTOR TRANSFER

FDWRITE   LDAA     #1
          BRA      FDREAD.1
FDREAD    CLRA
FDREAD.1  JSR      FDSETUPDRIVE       GO SET UP ALL THE PARAMETERS IN THE DCB
          LDX      FDCCB,X            see if controller is busy
          TST      CCB:BUSY,X
          BNE      FDSTARTIO
          JSR      SDOS+SDOS:WAITEVENT
FDSTARTIO LDX      DCBPOINTER         NOW NOBODY'S USING DRIVE
          CLR      DCB:DONEFLAG,X     KICK INTERRUPT ROUTINE INTO MOTION
          LDX      FDCCB,X            point controller table at this DCB
          LDAA     DCBPOINTER
          LDAB     DCBPOINTER+1
          STAA     CCB:CURRENTDCB,X
          STAB     CCB:CURRENTDCB+1,X
          LDX      CCB:STARTIO,X
          JSR      SDOS+SDOS:STARTIO
          OKRTS

FDWAITDONE
          LDAA     DCB:DONEFLAG,X     IS IT DONE?
          BNE      FDWAIT1            B/ YES
          JSR      SDOS+SDOS:WAITEVENT
          LDX      DCBPOINTER
FDWAIT1   LDX      DCB:LASTERROR,X
          BEQ      FDWAIT2            B/ NO ERRORS
          JMP      ERRETX
FDWAIT2   OKRTS
          PAGE
MODULONSPTB
          SUBB     DSKINFO:NSPT+1,X
          BCC      MODULONSPTB
          ADDB     DSKINFO:NSPT+1,X
          RTS

MODULONSPT 
          SUBA     DSKINFO:NSPT+1,X
          BCC      MODULONSPT
          ADDA     DSKINFO:NSPT+1,X
          RTS

*         FDSETUPDRIVE -- SETS UP FDDRIVE TABLE FOR INTERRUPT DRIVEN TRANSFER

FDSETUPDRIVE 
          LDX      DCBPOINTER
          STAA     FDREADWRITE,X      SAVE THE READ/WRITE FLAG
          LDAB     #10
          STAB     FDRETRY,X          SAVE THE READ/WRITE RETRY COUNT
          LDAA     #4                 SETUP SEEK RETRY COUNT
          STAA     FDSEEKRETRY,X
          CLR      DCB:LASTERROR,X    "NO ERRORS"
          CLR      DCB:LASTERROR+1,X
          LDAB     DSKINFO:MAPALGORITHM,X
          LDAA     DSKINFO:MAPALGORITHM+1,X
          CMPB     FDMAPALG,X
          BNE      FDSETUP1           B/ MAP HAS CHANGED
          CMPA     FDMAPALG+1,X
          BEQ      FDSETUP2           B/ MAP HAS NOT CHANGED
FDSETUP1  STAB     FDMAPALG,X
          STAA     FDMAPALG+1,X

BUILDMAP  STX      TEMPX
          NEGA
          LDAB     DSKINFO:NSPT+1,X   NUMBER OF TIMES TO DO THIS
BUILDMAP1 LDX      DCBPOINTER
          ADDA     DSKINFO:MAPALGORITHM+1,X
BUILDMAP2 BSR      MODULONSPT
          LDX      DCBPOINTER         SEE IF THIS SECTOR NUMBER
          DEX                           ALREADY USED
BUILDMAP3 INX
          CPX      TEMPX
          BEQ      BUILDMAP4          B/ NOT USED, USE IT
          CMPA     FDMAP,X
          BNE      BUILDMAP3          B/ IT'S NOT THIS ONE, KEEP LOOKING
          INCA                        OH WELL, LET'S BUMP IT AND TRY AGAIN
          BRA      BUILDMAP2
BUILDMAP4 STAA     FDMAP,X
          INX
          STX      TEMPX
          DECB                        ARE WE DONE BUILDING THE MAP?
          BNE      BUILDMAP1          B/ NOPE

*         BUILD UP THE SPIRALING INFO

          LDX      DCBPOINTER
          LDAA     FDMAPALG,X
          BSR      MODULONSPT
          STAA     FDK1MODNSPT,X
          ASLA
          BSR      MODULONSPT
          STAA     FDK2MODNSPT,X
          ASLA
          BSR      MODULONSPT
          STAA     FDK4MODNSPT,X
          ASLA
          BSR      MODULONSPT
          STAA     FDK8MODNSPT,X
          ASLA
          BSR      MODULONSPT
          STAA     FDK16MODNSPT,X
          PAGE
FDSETUP2 ; COMPUTE TARGET CYLINDER AND SECTOR
          LDX      DSKINFO:SECTORDB,X
          LDAA     RDSI:LSN+1,X       GET LSN
          LDAB     RDSI:LSN+2,X
          LDX      DCBPOINTER         SO WE CAN POKE AT DCB AGAIN
;
;  Now generate 8 quotient bits (enough for 255 tracks!)
;
          ASLB                        it takes 8 ASLD's to shift sector
          ROLA                          number into upper byte
          SUBA     DSKINFO:NSPT+1,X   Compute quotient bit
          BCC      *+4                b/ did go in, quotient bit is 1
          ADDA     DSKINFO:NSPT+1,X   didn't go in, quotient bit is zero
          ROL      FDSECTOR,X         save complement of quotient bit
          ASLB                        double the dividend
          ROLA

          SUBA     DSKINFO:NSPT+1,X   Compute quotient bit
          BCC      *+4                b/ did go in, quotient bit is 1
          ADDA     DSKINFO:NSPT+1,X   didn't go in, quotient bit is zero
          ROL      FDSECTOR,X         save complement of quotient bit
          ASLB                        double the dividend
          ROLA

          SUBA     DSKINFO:NSPT+1,X   Compute quotient bit
          BCC      *+4                b/ did go in, quotient bit is 1
          ADDA     DSKINFO:NSPT+1,X   didn't go in, quotient bit is zero
          ROL      FDSECTOR,X         save complement of quotient bit
          ASLB                        double the dividend
          ROLA

          SUBA     DSKINFO:NSPT+1,X   Compute quotient bit
          BCC      *+4                b/ did go in, quotient bit is 1
          ADDA     DSKINFO:NSPT+1,X   didn't go in, quotient bit is zero
          ROL      FDSECTOR,X         save complement of quotient bit
          ASLB                        double the dividend
          ROLA

          SUBA     DSKINFO:NSPT+1,X   Compute quotient bit
          BCC      *+4                b/ did go in, quotient bit is 1
          ADDA     DSKINFO:NSPT+1,X   didn't go in, quotient bit is zero
          ROL      FDSECTOR,X         save complement of quotient bit
          ASLB                        double the dividend
          ROLA

          SUBA     DSKINFO:NSPT+1,X   Compute quotient bit
          BCC      *+4                b/ did go in, quotient bit is 1
          ADDA     DSKINFO:NSPT+1,X   didn't go in, quotient bit is zero
          ROL      FDSECTOR,X         save complement of quotient bit
          ASLB                        double the dividend
          ROLA

          SUBA     DSKINFO:NSPT+1,X   Compute quotient bit
          BCC      *+4                b/ did go in, quotient bit is 1
          ADDA     DSKINFO:NSPT+1,X   didn't go in, quotient bit is zero
          ROL      FDSECTOR,X         save complement of quotient bit
          ASLB                        double the dividend
          ROLA

          SUBA     DSKINFO:NSPT+1,X   Compute quotient bit
          BCC      *+4                b/ did go in, quotient bit is 1
          ADDA     DSKINFO:NSPT+1,X   didn't go in, quotient bit is zero
          ROL      FDSECTOR,X         save complement of quotient bit
;         ASLB                        double the dividend
;         ROLA

          LDAB     FDSECTOR,X         get complement of desired track
          COMB                        invert the inverted quotient bits
          STAA     FDSECTOR,X         save sector within track
          LDX      DSKINFO:SECTORDB,X now save cylinder number in RDSI
          CLR      RDSI:CYLINDER,X
          STAB     RDSI:CYLINDER+1,X
          CLR      RDSI:SECTOR,X
          CLR      RDSI:SECTOR+1,X
          LDX      DCBPOINTER
          page
          LDX      FDMAPALG,X         apply mapalgorithm...
          CPX      #$0001               unless it is :0001
          BEQ      FDSETUP4           B/ map algorithm :0001, all done!
          ADDA     DCBPOINTER+1
          STAA     TEMPX+1
          LDAA     DCBPOINTER
          ADCA     #0
          STAA     TEMPX
          CLRA                        make spiral in (A)
          LDX      DCBPOINTER         assert: cylinder number in (B)
          JSR      MODULONSPTB
          ASRB
          BCC      MAP1
          ADDA     FDK1MODNSPT,X
MAP1      ASRB
          BCC      MAP2
          ADDA     FDK2MODNSPT,X
MAP2      ASRB
          BCC      MAP3
          ADDA     FDK4MODNSPT,X
MAP3      ASRB
          BCC      MAP4
          ADDA     FDK8MODNSPT,X
MAP4      ASRB
          BCC      MAP5
          ADDA     FDK16MODNSPT,X
MAP5      LDX      TEMPX
          ADDA     FDMAP,X
          LDX      DCBPOINTER
          JSR      MODULONSPT
          STAA     FDSECTOR,X
FDSETUP4
          LDX      DCBPOINTER
          OKRTS
          FIN      IODRIVERBODY
          IF       IODRIVERPOLL
        PAGE       Virtual Floppy Driver Interrupt-Level Routines
DISKINTSERVICE 
        IF     PERSCI
        LDAA   PERSCI:PIACB            PerSci Controller
PERSCIINTERRUPTMASK EQU *+1 ; set by RESET routines
        BITA   #$80                    Want interrupt ?
        BEQ    DISKINTPERSCI.NO
        LDX    #CCB:PERSCI             PerSci interrupted, so absorb the
        LDAB   PERSCI:PIADB              interrupt from that controller
        JMP    DISKINTERRUPT
DISKINTPERSCI.NO ; not Persci
        FIN    PERSCI
        IF     DAMFLOPPY
        LDAA   DAMFLOPPY:PIACB         DAM Floppy Controller
DAMFLOPPYINTERRUPTMASK equ *+1
        BITA   #$80                    Want interrupt ?
        BEQ    DISKINTDAMFLOPPY.NO
        LDX    #CCB:DAMFLOPPY          DAM floppy interrupted, so absorb
        LDAB   DAMFLOPPY:PIADB           the interrupt from that controller
        JMP    DISKINTERRUPT
DISKINTDAMFLOPPY.NO
        FIN    DAMFLOPPY
        FIN    IODRIVERPOLL
        IF     IODRIVERBODY
DISKINTERRUPT ; entered with CCB address in (X)
        BSR    DISKINTSETUP            set up a working context area
        JMP    FDDSTATEJ,X             resume process waiting for interrupt
        PAGE
DISKINTSETUP   ; set up a context area of sorts
        STX    DISKINTCCB              remember interface table address
        LDX    CCB:CURRENTDCB,X
        STX    DISKINTDCB
        RTS

        IF     PERSCI
DISKINTSTARTPERSCI                     ; ASSERT: INTERRUPTS ARE DISABLED HERE!
        LDX    #CCB:PERSCI
        IF     DAMFLOPPY
        BRA    DISKINTSTART

        FIN    DAMFLOPPY
        FIN    PERSCI
        IF     DAMFLOPPY
DISKINTSTARTDAMFLOPPY                  ; ASSERT: INTERRUPTS ARE DISABLED HERE!
        LDX    #CCB:DAMFLOPPY
        FIN    DAMFLOPPY
DISKINTSTART
        CLR    CCB:BUSY,X              mark controller busy
*       CLI                            (allow interrupts)
        LDAA   #6                      Set up for 6 1-second interrupts
        STAA   CCB:TIMEOUT,X             to keep disk spinning
        LDAA   #(1*TICKSPERSECOND+NTIMEOUTBLOCKS)/256
        LDAB   #(1*TICKSPERSECOND+NTIMEOUTBLOCKS)&$FF
        STAA   CCB:TIMEOUTBLK+TIMEOUT:FUSE,X
        STAB   CCB:TIMEOUTBLK+TIMEOUT:FUSE+1,X
        BSR    DISKINTSETUP            set up a working context area
        LDAA   FDREADWRITE,X           a write to an IBM format disk
        BEQ    SEEK                      must have the data complemented
        TST    FDCOMPLEMENT,X              before it is written
        BEQ    SEEK                          (and complemented back, after
        JSR    DISKCOMPLEMENT                  it has been written)
        PAGE
*       See if the head must be moved with a seek operation;
*       if it must, the seek is done without verification, as a seek
*       failure will be picked up by a subsequent read/write as
*       "record not found" status, for which the remedy will be
*       a restore operation.  The restore operation IS verified; if
*       it fails, a "seek error" is registered, and the restore is
*       retried, up to the seek-retry count.
*
*
SEEK    LDX    DISKINTCCB              announce intentions
        JSR    CCB:SETSEEK,X
        BCC    SEEKDONE                B/ no seek necessary
        CMPB   #-1                     a seek is necessary: if the B register
        BEQ    SEEKHOME                  contains a -1, then I am lost and
        LDX    DISKINTCCB                  must do a restore; otherwise, a
        JSR    CCB:SEEK,X                    standard seek will suffice
        LDX    DSKINFO:SECTORDB,X      I assert that I am on the right track
        LDAA   RDSI:CYLINDER+1,X
        LDX    DISKINTDCB              remember which track we are on
        BSR    DISKSETCYLADD
SEEKDONE
        LDX    DSKINFO:SECTORDB,X      pick up the buffer page number in the
        LDAB   RDSI:SECTORBASE,X         B register.  pick up the sector number
        LDX    DISKINTDCB                  in the A register and offset by
        LDAA   FDSECTOR,X                    the track base sector
        ADDA   FDFIRSTSEC,X
        TST    FDREADWRITE,X           go off and do the read or write, as
        BNE    DISKWRITE                 appropriate
        JMP    DISKREAD

**** THE WESTERN DIGITAL TRICK OF STEP IN ONE/STEP OUT ONE SHOULD
**** BE ADDED TO MAKE THE DRIVER MORE ROBUST.
**** BUT DENNIS PAINTER SEZ IT DOESN'T WORK ON A PERSCI.
        PAGE
SEEK3   STAA   DSKINFO:SEEKERRSTS+1,X  count a seek error, and
        INC    DSKINFO:SEEKERRCNT+1,X    save the error status
        BNE    SEEK3.1
        INC    DSKINFO:SEEKERRCNT,X
SEEK3.1 LDAA   #-1                     Say that I lost my place...
        BSR    DISKSETCYLADD
        DEC    FDSEEKRETRY,X           DOWN COUNT # TRIES LEFT
        BEQ    DISKSEEKERROR           B/ GAK! CROAK! DIE....
SEEKHOME
        BSR    DISKABORT               KILL WHATEVER DISK IS DOING
        LDX    DISKINTCCB
        JSR    CCB:RESTORE,X
        LDX    DISKINTCCB
        JSR    CCB:STATUS,X            *** why doesn't this check for success?
        BITA   #%00000100              (ON OTHER HAND, IF CYL 0, WHO CARES?)
        BEQ    SEEK3                   B/ DIDN'T GET TO CYL 0 FOR SOME REASON!?
        CLRA
        BSR    DISKSETCYLADD           SET "I'M AT CYLINDER 0"
        BRA    SEEK                    GO TRY SEEK TO PROPER TRACK AGAIN
        PAGE
DISKSEEKERROR
        LDAA   #ERR:DISKSEEK/256       GET APPROPRIATE ERROR CODE
        LDAB   #ERR:DISKSEEK&$FF
        BRA    DISKERROR1

DISKWPERR
        LDAA   #ERR:DSKWRTPROT/256
        LDAB   #ERR:DSKWRTPROT&$FF
        BRA    DISKERROR1

DISKERROR ; fatal read or write error occurred
        LDAA   #ERR:DISKREAD/256       ASSUME READ ERROR
        LDAB   #ERR:DISKREAD&$FF
        TST    FDREADWRITE,X           WAS IT A READ OR A WRITE?
        BEQ    DISKERROR1              B/ IT'S A READ
        LDAA   #ERR:DISKWRITE/256
        LDAB   #ERR:DISKWRITE&$FF
DISKERROR1
        STAA   DCB:LASTERROR,X
        STAB   DCB:LASTERROR+1,X
DISKDONE
        INC    DCB:DONEFLAG,X          SIGNAL "DISK DONE"
        LDX    DISKINTCCB
        INC    CCB:BUSY,X              SO TASK KNOWS WE'RE FREE
DISKDONE1
        LDX    DISKINTDCB
        JSR    WAITFORINTERRUPT
DISKINTUNEXPECTED
        BSR    DISKABORT
        BRA    DISKDONE1
        PAGE
CHECKDISKREADY
; It would be nice if this could be used to tell one that the drive
; did not have a diskette in it... Thank you Dennis Brown
        LDX    DISKINTCCB              return carry set if not ready;
        JSR    CCB:STATUS,X              carry reset if ready--
        TAB                                in either case, status is in A
        ROLB
        RTS

MAKEDISKREADY
        LDX    DISKINTCCB
        JMP    CCB:RESET,X

DISKABORT
        LDX    DISKINTCCB              abort whatever's being done
        JMP    CCB:ABORT,X

DISKSETCYLADD ; mark DCBs that share heads as all being on track (A)
        LDX    FDHEADCHAIN,X
DISKSETCYLADD.1
        STAA   FDCYL,X                 all DCB's sharing the same head
        LDX    FDNEXTCHAIN,X             mechanism are chained together
        BNE    DISKSETCYLADD.1             so that all FDCYL values will
        LDX    DISKINTDCB                    be equally correct
        RTS
        PAGE
DISKWRITE
        LDX    DISKINTCCB
        JSR    CCB:WRITESECTOR,X
        BSR    CHECKDISKREADY
        BITA   #%01000000              IS DISK WRITE PROTECTED ?
        BNE    DISKWPERR               B/ YEP.
        BCC    DISKWRITE2              B/ READY
        BSR    MAKEDISKREADY           since the drive must have shut down,
        BRA    SEEKDONEJ                 we'll try this again

DISKWRITE2
        BITA   #%01111100              Is the write OK?
        BNE    DISKWRITE4              B/ NO
        LDX    DISKINTCCB
        JSR    CCB:VERIFYSECTOR,X      do a verify
        BSR    CHECKDISKREADY          WELL?
        BCC    DISKWRITE3              B/ 10-4
        BSR    MAKEDISKREADY           It's not, so we'll try the write again
SEEKDONEJ
        JMP    SEEKDONE
        PAGE
DISKWRITE3
        BITA   #%00011000              record not found or CRC error?
        BEQ    DISKDONEJ1              B/ no--everything's OK
DISKWRITE4
        STAA   DSKINFO:WRITEERRSTS,X   SAVE WRITE ERROR STATUS
        BSR    DISKSAVEERRLSN          SAVE ERRORING LSN
        INC    DSKINFO:WRITEERRCNT+1,X COUNT # WRITE ERRORS
        BNE    DISKWRITE5
        INC    DSKINFO:WRITEERRCNT,X
DISKWRITE5
        DEC    FDRETRY,X
        BEQ    DISKERRORJ              B/ NO MORE TRIES LEFT
        LDAB   FDRETRY,X               ON LAST TRY ?
        DECB                           (=1?)
        BEQ    SEEKHOMEJ               B/ YES, TRY HARDER
        BITA   #%00010000              NO, DID WE GET "RECORD NOT FOUND" ?
        BEQ    SEEKDONEJ               B/ NOPE, TRY READ/WRITE AGAIN
SEEKHOMEJ
        JMP    SEEKHOME                GO SEE IF RE-SEEK HELPS

DISKERRORJ
        JMP    DISKERROR

DISKSAVEERRLSN ; save RDSI:LSN in DCB
        LDX    DSKINFO:SECTORDB,X
        LDAA   RDSI:LSN+1,X
        LDAB   RDSI:LSN+2,X
        LDX    DISKINTDCB
        CLR    DSKINFO:ERRLSN,X
        STAA   DSKINFO:ERRLSN+1,X
        STAA   DSKINFO:ERRLSN+2,X
        RTS
        PAGE
DISKREAD
        LDX    DISKINTCCB
        JSR    CCB:READSECTOR,X
        BSR    CHECKDISKREADY
        BCC    DISKREAD1               B/ READY
        BSR    MAKEDISKREADY
        BRA    SEEKDONEJ

DISKREAD1
        BITA   #%00011100              Is the read OK?
        BNE    DISKREAD4               B/ no
DISKDONEJ1
        TST    FDCOMPLEMENT,X          complement data?
        BEQ    DISKDONEJ               B/ NO
        JSR    DISKCOMPLEMENT          YES, COMPLEMENT DATA BEFORE WE QUIT!
DISKDONEJ
        JMP    DISKDONE

DISKREAD4
        STAA   DSKINFO:READERRSTS,X    SAVE READ ERROR STATUS
        BSR    DISKSAVEERRLSN          save erroring LSN
        INC    DSKINFO:READERRCNT+1,X  COUNT # READ ERRORS
        BNE    DISKWRITE5
        INC    DSKINFO:READERRCNT,X
        BRA    DISKWRITE5              GO CHECK RETRY COUNT
        PAGE
DISKCOMPLEMENT ; COMPLEMENT SECTOR CONTENTS (FOR IBM 3740 FORMAT)
        LDAA   DSKINFO:NBPS,X          get sector size
        LDAB   DSKINFO:NBPS+1,X
        LDX    DSKINFO:SECTORDB,X
        LDX    RDSI:SECTORBASE,X       MUST COMPLEMENT THE DATA FIRST
        INCA                           TO OFFSET "DECA" BELOW ON 1st PASS
DISKCOMPL
        COM    0,X                     COMPLEMENT A BYTE
        INX                            BUMP POINTER
        COM    0,X                     COMPLEMENT A BYTE
        INX                            BUMP POINTER
        COM    0,X                     COMPLEMENT A BYTE
        INX                            BUMP POINTER
        COM    0,X                     COMPLEMENT A BYTE
        INX                            BUMP POINTER
        SUBB   #4                      = # BYTES LEFT TO COMPLEMENT
        BNE    DISKCOMPL
        DECA
        BNE    DISKCOMPL
        LDX    DISKINTDCB              TO BE NICE TO CALLER
        RTS
        PAGE   Controller Primitives
        IF     DAMFLOPPY!PERSCI
*              WM FLOPPY DISK HARDWARE DEFINITIONS

                     IF         PERSCI
PERSCI:PIACA         EQU        $FFA0
PERSCI:PIACB         EQU        $FFA1
PERSCI:PIADA         EQU        $FFA2        DMA PAGE NUMBER
PERSCI:PIADB         EQU        $FFA3        drive select, misc. control
PERSCI:WDCMDSTS      EQU        $FFA4        COMMAND / STATUS REGISTER
PERSCI:WDTRACK       EQU        $FFA5        CURRENT TRACK REGISTER
PERSCI:WDSECTOR      EQU        $FFA6        TARGET SECTOR REGISTER
PERSCI:WDDATA        EQU        $FFA7        TARGET TRACK /  DATA REGISTER
                     FIN        PERSCI

                     IF         DAMFLOPPY
DAMFLOPPY:PIACA      EQU        $FF80
DAMFLOPPY:PIACB      EQU        $FF81
DAMFLOPPY:PIADA      EQU        $FF82        DMA PAGE NUMBER
DAMFLOPPY:PIADB      EQU        $FF83        drive select, misc. control
DAMFLOPPY:WDCMDSTS   EQU        $FF84        COMMAND / STATUS REGISTER
DAMFLOPPY:WDTRACK    EQU        $FF85        CURRENT TRACK REGISTER
DAMFLOPPY:WDSECTOR   EQU        $FF86        TARGET SECTOR REGISTER
DAMFLOPPY:WDDATA     EQU        $FF87        TARGET TRACK /  DATA REGISTER
                     FIN        DAMFLOPPY
        PAGE
        FIN    DAMFLOPPY!PERSCI
COUNTCOMMAND
        LDX    DISKINTDCB
        INC    DSKINFO:OPSCOUNT+2,X       COUNT # OPERATIONS ISSUED TO FLOPPY
        BNE    WAITFORINTERRUPT
        INC    DSKINFO:OPSCOUNT+1,X
        BNE    WAITFORINTERRUPT
        INC    DSKINFO:OPSCOUNT,X
WAITFORINTERRUPT
        PULA
        PULB
        STAA   FDDSTATE,X
        STAB   FDDSTATE+1,X
        JMP    SDOS+SDOS:RTI
        PAGE
*       Test if an actual seek is required

*       If the drive number has not changed, and the cylinder (track) number
*       has not changed, then no seek is necessary, and return is made with
*       carry clear; otherwise, return is made with carry set and the
*       previous cylinder number in the B register.

*       a side effect is that the values of FDCYL, FDTARGETCYL, and FDDRIVE
*       are copied to the CCB, regardless of whether a seek is necessary
*       (this ensures that the CCB is set up for a subsequent restore, read,
*       or write)

TESTFORSEEK
        LDAA   CCB:DRIVE,X
        LDX    DISKINTDCB
        CMPA   FDDRIVE,X
        BNE    DOSEEK
        LDAA   FDCYL,X
        LDX    DSKINFO:SECTORDB,X
        CMPA   RDSI:CYLINDER+1,X
        BNE    DOSEEK
        LDX    DISKINTDCB
*       BSR    COPYDCBTOCCB
*       OKRTS

COPYDCBTOCCB
        LDAA   FDDRIVE,X
        PSHA
        LDAB   FDCYL,X
        LDX    DSKINFO:SECTORDB,X
        LDAA   RDSI:CYLINDER+1,X
        LDX    DISKINTCCB
        STAA   CCB:CYL,X
        STAB   CCB:LASTCYL,X
        PULA
        STAA   CCB:DRIVE,X
        LDX    DISKINTDCB
        OKRTS

DOSEEK ; seek is required
        LDX    DISKINTDCB
        BSR    COPYDCBTOCCB
        ERRORRTS

        IF     PERSCI
        PAGE   PerSci Controller Primitives
PERSCI:STATUS
        LDAA   PERSCI:WDCMDSTS
        COMA
        LDX    DISKINTDCB
        RTS

PERSCI:RESTORE
        LDAA   CCB:DRIVE,X             get the drive address and add in
        ORAA   #%00001000                slow step, read, no DMA
        STAA   PERSCI:PIADB
        LDAA   #(\%00000010)&$FF       restore
PERSCI:ISSUECOMMAND
        STAA   PERSCI:WDCMDSTS
        JMP    COUNTCOMMAND

PERSCI:ABORT
; Note that the Series 2000 does something funny here; somebody should go look.
        LDAA   #(\%11010000)&$FF       abort with no interrupts
        STAA   PERSCI:WDCMDSTS
        BSR    PERSCI:ABORT.RTS        wait about 30 uS for chip to settle
        BSR    PERSCI:ABORT.RTS        
        BSR    PERSCI:ABORT.RTS        
        LDAB   PERSCI:WDCMDSTS         return with status in B
        COMB
        LDAA   PERSCI:PIADB            clear possible interrupt
        LDX    DISKINTDCB
PERSCI:ABORT.RTS
        RTS

PERSCI:RESET
        LDAA   #(\%11010001)&$FF       abort with interrupt
        BRA    PERSCI:ISSUECOMMAND
        PAGE
PERSCI:SETSEEK
        BRA    TESTFORSEEK

PERSCI:SEEK
        LDAA   CCB:DRIVE,X
        STAA   PERSCI:PIADB
        LDAA   CCB:LASTCYL,X
        LDAB   CCB:CYL,X
        COMA
        STAA   PERSCI:WDTRACK
        COMB
        STAB   PERSCI:WDDATA
        LDAA   #(\%00011001)&$FF       seek, load head, no verify
        BRA    PERSCI:ISSUECOMMAND

PERSCI:VERIFYSECTOR
        LDAA   CCB:DRIVE,X             don't want either write or DMA!!
        STAA   PERSCI:PIADB
        BRA    PERSCI:READSECTOR.2
        PAGE
PERSCI:READSECTOR
        STAB   PERSCI:PIADA            set DMA page number
        LDAB   CCB:DRIVE,X             set up controller for read, DMA
        ORAB   #%01000000
        STAB   PERSCI:PIADB
        COMA
        STAA   PERSCI:WDSECTOR         set the sector number
PERSCI:READSECTOR.2
        JSR    PERSCI:ABORT            load head, if necessary
        LDAA   #(\%10001000)&$FF       read sector
        BITB   #%00100000              head load status
        BNE    PERSCI:READSECTOR.1
        EORA   #%00000100              make the head load
PERSCI:READSECTOR.1
        JMP    PERSCI:ISSUECOMMAND

PERSCI:WRITESECTOR
        STAB   PERSCI:PIADA            set DMA page number
        LDAB   CCB:DRIVE,X
        ORAB   #%11000000              set up controller for write, DMA
        STAB   PERSCI:PIADB
        COMA
        STAA   PERSCI:WDSECTOR         set the sector number
        JSR    PERSCI:ABORT            see if necessary to load heads
        LDAA   #(\%10101000)&$FF       write sector
        BITB   #%00100000
        BNE    PERSCI:WRITESECTOR.1
        EORA   #%00000100              load the heads
PERSCI:WRITESECTOR.1
        JMP    PERSCI:ISSUECOMMAND
        FIN    PERSCI
        IF     DAMFLOPPY
        PAGE   DAM Floppy Controller Primitives
DAMFLOPPY:STATUS
        LDAA   DAMFLOPPY:WDCMDSTS
        LDX    DISKINTDCB
        RTS

DAMFLOPPY:RESTORE
        LDAA   CCB:DRIVE,X
        STAA   DAMFLOPPY:PIADB
        LDAA   #%00000010              restore
DAMFLOPPY:ISSUECOMMAND
        STAA   DAMFLOPPY:WDCMDSTS
        JMP    COUNTCOMMAND

DAMFLOPPY:ABORT
        LDAA   #%11010000              abort with no interrupts
        STAA   DAMFLOPPY:WDCMDSTS
        BSR    DAMFLOPPY:ABORT.RTS     wait about 30 uS for chip to settle
        BSR    DAMFLOPPY:ABORT.RTS
        BSR    DAMFLOPPY:ABORT.RTS
        LDAB   DAMFLOPPY:WDCMDSTS      return with status in B
        LDAA   DAMFLOPPY:PIADB         clear possible interrupt
        LDX    DISKINTDCB
DAMFLOPPY:ABORT.RTS
        RTS

DAMFLOPPY:RESET
        LDAA   #%11010001              abort with interrupt
        BRA    DAMFLOPPY:ISSUECOMMAND
        PAGE
DAMFLOPPY:SETSEEK
        JMP    TESTFORSEEK

DAMFLOPPY:SEEK
        LDAA   CCB:DRIVE,X
        STAA   DAMFLOPPY:PIADB
        LDAA   CCB:LASTCYL,X
        LDAB   CCB:CYL,X
        STAA   DAMFLOPPY:WDTRACK
        STAB   DAMFLOPPY:WDDATA
        LDAA   #%00011001              seek, load head, 12 mS step, no verify
        BRA    DAMFLOPPY:ISSUECOMMAND

DAMFLOPPY:VERIFYSECTOR
        LDAA   DAMFLOPPY:PIADB         turn off write, DMA
        ANDA   #%00101111
        STAA   DAMFLOPPY:PIADB
        BRA    DAMFLOPPY:READSECTOR.2
        PAGE
DAMFLOPPY:READSECTOR
        STAB   DAMFLOPPY:PIADA         set DMA page number
        LDAB   CCB:DRIVE,X             set up controller for read, DMA
        ORAB   #%01000000              read, DMA
        STAB   DAMFLOPPY:PIADB
        STAA   DAMFLOPPY:WDSECTOR      set the sector number
DAMFLOPPY:READSECTOR.2
        BSR    DAMFLOPPY:ABORT         load head, if necessary
        LDAA   #%10000000              read sector
        BITB   #%00100000              head load status
        BNE    DAMFLOPPY:READSECTOR.1
        ORAA   #%00000100              make the head load
DAMFLOPPY:READSECTOR.1
        JMP    DAMFLOPPY:ISSUECOMMAND
        PAGE
DAMFLOPPY:WRITESECTOR
        STAB   DAMFLOPPY:PIADA         set DMA page number
        LDAB   CCB:DRIVE,X             set up controller for write, DMA
        ORAB   #%11000000
        STAA   DAMFLOPPY:WDSECTOR      target sector
        LDAA   DAMFLOPPY:WDTRACK       check if write pre-compensation needed
        CMPA   #21
        BMI    DAMFLOPPY:WRITESECTOR.2
        ORAB   #%00010000              turn on write pre-compensation
DAMFLOPPY:WRITESECTOR.2
        STAB   DAMFLOPPY:PIADB
        JSR    DAMFLOPPY:ABORT         see if necessary to load heads
        LDAA   #%10100000              write sector
        BITB   #%00100000
        BNE    DAMFLOPPY:WRITESECTOR.1
        ORAA   #%00000100              load the heads
DAMFLOPPY:WRITESECTOR.1
        JMP    DAMFLOPPY:ISSUECOMMAND
        FIN    DAMFLOPPY
        PAGE Virtual Floppy Driver Time-Out Routines
        IF     PERSCI
PERSCI:TIMEOUT
        LDX    #CCB:PERSCI
        IF     DAMFLOPPY
        BRA    DISKTIMEOUT

        FIN    DAMFLOPPY
        FIN    PERSCI
        IF     DAMFLOPPY
DAMFLOPPY:TIMEOUT
        LDX    #CCB:DAMFLOPPY
        FIN    DAMFLOPPY
DISKTIMEOUT
        STX    DISKINTCCB              save CCB address
        JSR    CCB:STATUS,X            touch controller to keep drive going
        LDX    DISKINTCCB
        DEC    CCB:TIMEOUT,X           COUNT OFF 1 SEC
        BNE    DISKTIMEOUT1            B/ TIMER NOT ZERO YET
        LDX    CCB:CURRENTDCB,X        point at DCB, again
        LDAA   DCB:DONEFLAG,X
        BNE    DISKTIMEOUT2            B/ DISK IS DONE, GO AWAY
        IF     DAMFLOPPY
* should have code here to reset "load both heads" bit ??
        FIN
        LDAA   #ERR:DEVICETIMEDOUT/256
        LDAB   #ERR:DEVICETIMEDOUT&$FF
DISKTIMEOUTERRORED ; timeout detected an error
        STX    DISKINTDCB              remember DCB address
        STAA   DCB:LASTERROR,X         remember device error code
        STAB   DCB:LASTERROR+1,X
        INC    DCB:DONEFLAG,X          MARK DISK AS 'DONE'
        LDX    DISKINTCCB              point at 'controller busy' flag
        INC    CCB:BUSY,X              and make it unbusy
        CLR    CCB:DRIVE,X             FORCE SEEK W/VERIFY ON NEXT READ/WRITE
        DEC    CCB:DRIVE,X
        JMP    DISKINTUNEXPECTED

DISKTIMEOUT1   ; (X) -> controller table
        ROLA                           save device ready status in carry
        LDX    CCB:CURRENTDCB,X        find DCB for device
        LDAA   DCB:DONEFLAG,X          is device done ?
        BNE    DISKTIMEOUT1A           b/ yes, just keep it spinning
        LDAA   #ERR:DEVICENOTREADY/256 assume the worst...
        LDAB   #ERR:DEVICENOTREADY&$FF
        BCS    DISKTIMEOUTERRORED      b/ drive not ready after 1 second
DISKTIMEOUT1A
        LDX    DISKINTCCB
        LDAA   #(1*TICKSPERSECOND+NTIMEOUTBLOCKS)/256
        LDAB   #(1*TICKSPERSECOND+NTIMEOUTBLOCKS)&$FF
        STAA   CCB:TIMEOUTBLK+TIMEOUT:FUSE,X    plant a 1-second fuse
        STAB   CCB:TIMEOUTBLK+TIMEOUT:FUSE+1,X
DISKTIMEOUT2
        JMP    SDOS+SDOS:RTI
        FIN    IODRIVERBODY
