
; File: drv.cdisk1.text
; Date: 13-February-84

        IDENT   DRVCDISK
        GLOBAL  DRVCDISK

; DRVCDISK - The Corvus OMNINET and LOCAL disk driver
;
; v 2.0  06-30-83   KB  driver for both local and omninet disk.
;                       Based on drivers DRV.ODISK and DRV.LDISK
;                       written by LEF.  CDISK1 does not use the
;                       Transporter driver.
;        09-15-83   KB  Added reset, format, and verify disk cmds
;                       to no timeout list.
;
;        09-28-83   KB  forgot to move param blk address into A3
;                       in DskST.
;        10-14-83   KB  increased retry count on giveups in Send.
;
;        10-26-83   KB  retry on sends to disk server for NAKed msgs.
;                       result codes : TooLong,NoSockt,HdrErr.
;
;        02-13-84  MEB  Add driver busy flag, return IOEtbliu when
;                       driver is called and flag is set
;
; PROBLEMS :
;        1) Need to define timeouts for Local and Omninet disks.
;           Are these drive command dependent?  Are they adaptive?
;
; Parameters:   D0.W - Unit number
;               D1.L - Address of buffer
;               D2.L - Count
;               D3.L - Block Number
;               D4.W - Command
;               D5.W - Access Mode
;
;                   Input Parameters:              Result values:
;     Command       Unit  Addr  Count Block Mode   IORESULT  Busy
;
;    0 - Install    D0.W                            D7.W
;    1 - Read       D0.W  D1.L  D2.L  D3.L  D5.W    D7.W
;    2 - Write      D0.W  D1.L  D2.L  D3.L  D5.W    D7.W
;    3 - Clear      D0.W                            D7.W
;    4 - Busy       D0.W                            D7.W     D0.B
;    5 - Status     D0.W  D1.L  D2.L                D7.W
;    6 - Unmount    D0.W                            D7.W

; ----  include '/ccos/os.gbl.asm.text'    (not listed)
        list 0
        include '/ccos/os.gbl.asm.text'
        list 1
  page
; Disk Driver Definitions - Local and OmniNet
;
; Commands
;
UNMCMD   EQU       6         ;unitunmount command

; Corvus Disk commands
;
DskRead  equ    $32             ;disk read command
DskWrit  equ    $33             ;disk write command

; Arbitrary Corvus disk command send parameter block
;
DCmdBuff EQU       0                   ;lint - pointer to drive command
DrvCLen  EQU       DCmdBuff+4          ;word - length of drive command
SndBuff  EQU       DrvCLen+2           ;lint - Pointer to send buffer
SndBLen  EQU       SndBuff+4           ;word - send buffer length
RcvBuff  EQU       SndBLen+2           ;lint - Pointer to receive buffer
RcvBLen  EQU       RcvBuff+4           ;word - receive buffer length
SlotNmbr EQU       RcvBLen+2           ;word - slot number
SrvrHost EQU       SlotNmbr+2          ;word - server host number
TrueRcvd EQU       SrvrHost+2          ;word - actual number of bytes received
DskRslt  EQU       TrueRcvd+2          ;word - disk return code
CRWpbLen EQU       DskRslt+2           ;Length of parameter block

; I/O address for slots
;
DskPort  EQU       $30000              ;Address of Apple slot 0 (non-existant)

; Local Disk interface card register indices (off base register)
;
CrvsData EQU       1         ; Corvus controller data register
CrvsStat EQU       3         ; Corvus controller status register

; Local Disk interface card status register bit definitions
;
CRVScrdy EQU       7         ;controller ready, off - ready, on - not ready
CRVSbdir EQU       6         ;bus direction ---
                             ;off - host to controller, on - controller to host
  page
; Ominet Addrresses
;
StrAdr  equ     $30FA1  ;address of Transporter register
RdyAdr  equ     $30F7F  ;address of VIA register A, used for Omninet ready
TOintvl equ     $FFFE   ;timeout interval

; Transporter Return Codes
;
Waiting equ     $FF     ;driver puts in to show command NOT complete
CmdAcpt equ     $FE     ;Socket setup successful
Echoed  equ     $C0     ;echo command was successful

GaveUp  equ     $80     ;aborted a send command after MaxRetries tries
TooLong equ     $81     ;last message sent was too long for the receiver
NoSockt equ     $82     ;sent to an uninitialized socket
HdrErr  equ     $83     ;sender's header length did not match receiver's
BadSock equ     $84     ;illegal socket number
Inuse   equ     $85     ;tried to set up a receive on an active socket
BadDest equ     $86     ;sent to an illegal host number

; Transporter Opcodes
;
RecvOp  equ     $F0     ;SETUPRECV opcode
SendOp  equ     $40     ;SENDMSG opcode
InitOp  equ     $20     ;INIT opcode
EndOp   equ     $10     ;ENDRECV opcode
DebOp   equ     $08     ;PEEK/POKE opcode
EchoOp  equ     $02     ;ECHOCMD opcode
WhoOp   equ     $01     ;WHOAMI opcode
                        ;
RestSkt equ     $A0     ;dest. socket for REST packet
CnstSkt equ     $B0     ;socket for Constellation protocol
        page
;
; OMNINET disk driver data area equates
;
DCmd    equ     0            ;byte - disk command offset
DCdrv   equ     1+DCmd       ;byte - offset for drive number
DCblklo equ     1+DCdrv      ;byte - LSB of block number to read or write
DCblkhi equ     1+DCblklo    ;byte - MSB      "     "
DClen   equ     1+DCblkhi    ;Length of drive command

; result vector and header used for all setuprecv commands
;
RHdr    equ     4+DCmd       ;follows Drive command record
RHpktRC equ     0+RHdr       ;byte - return code from transporter
RHsor   equ     1+RHpktRC    ;byte - the source of the message
RHpktLN equ     1+RHsor      ;word - total length of data portion of packet
RHdskLN equ     2+RHpktLN    ;word - length of info returned from drive
RHdskRC equ     2+RHdskLN    ;byte - return code from drive

; result vector and header for all sendmsg commands
;
SHdr    equ     8+RHdr       ;follows Result record, force to even boundary
SHpktRC equ     0+SHdr       ;byte - return code from transporter
SHunu1  equ     1+SHpktRC    ;byte - unused
SHunu2  equ     1+SHunu1     ;word - unused
SHtoLN  equ     2+SHunu2     ;word - number of bytes to send to drive
SHfmLN  equ     2+SHtoLN     ;word - number of bytes expected from drive

GData   equ     8+SHdr       ;word - area to receive "GO" into

; area used for constructing Transporter commands
;
TCmd    equ     2+GData      ;follows GO data space
TCop    equ     0+TCmd       ;byte - op code
TCrADhi equ     1+TCop       ;byte - result address HI
TCrADlo equ     1+TCrADhi    ;word - result address MED, LO
TCsock  equ     2+TCrADlo    ;byte - socket number
TCdADhi equ     1+TCsock     ;byte - data buffer address HI
TCdADlo equ     1+TCdADhi    ;word - data buffer address MED, LO
TCdtaLN equ     2+TCdADlo    ;word - data length
TChdrLN equ     2+TCdtaLN    ;byte - header length
TCdest  equ     1+TChdrLN    ;byte - destination host number

ODdw    equ     12+TCmd      ;lint - temporary buffer (for 3 byte nmbrs)
ODdwhi  equ     1+ODdw       ;byte - temporary HI
ODdwlo  equ     1+ODdwhi     ;word - temporary MID, LO
ODwrAD  equ     2+ODdwlo     ;lint - to save buffer address for CWrites

ODdataLN equ    4+ODwrAD     ;length of entire data area
  page
;
; Entry to disk driver
;
DRVCDISK bra.s     cDisk                         ;start of code
         data.b    1                             ;device blocked
         data.b    31                            ;valid commands - ALL
         data.b    84,02,13,0                    ;date
         data.b    hmlen                         ;header message length
hm       data.b    'Corvus disk driver 1 (v 2.0f)'
hmlen    equ       %-hm

BusyFlag DATA.B    0,0         ; meb first byte=1: driver interrupted,
                               ;                0: not busy

cDisk    MOVEQ     #IOEioreq,D7                  ;assume invalid command
         CMPI.W    #UNMCMD,D4                    ;VALID COMMAND
         BHI.S     cDskERR                       ;no, exit
         CLR.L     D7                            ;clear IOresult
         MOVEM.L   D1-D6/A0-A6,-(SP)             ;SAVE REGISTERS
         MOVE.W    SR,D6                         ; meb save Status Register
         MOVE.W    #$2700,SR                     ; meb turn off interrupts
         LEA       BUSYFLAG,A0                   ; meb
         BSET      #0,(A0)                       ; meb if BusyFlag=1
         BOFF.S    FlagOff                       ; meb
         MOVEQ     #IOEtbliu,D7                  ; meb    then driver is busy:
         MOVE.W    D6,SR                         ; meb         restore Status Reg,
         BRA.S     cDskRet                       ; meb         exit
                                                 ; meb else
FlagOff  MOVE.W    D6,SR                         ; meb      turn interrupts back on
         LEA       DskTabl,A0                    ;TURN THE COMMAND INTO A
         LSL.W     #1,D4                         ;INDEX TO THE FUNCTION
         MOVE.W    0(A0,D4.W),D4
         JSR       0(A0,D4.W)                    ;DO FUNCTION
         LEA       BUSYFLAG,A0                   ; meb common exit
         CLR.B     (A0)                          ; meb BusyFlag = 0
cDskRet  MOVEM.L   (SP)+,D1-D6/A0-A6             ;RESTORE registers
cDskERR  RTS

DskTabl  data.w    DskINST-DskTabl
         data.w    DskRD-DskTabl
         data.w    DskWR-DskTabl
         data.w    DskCLR-DskTabl
         data.w    DskBSY-DskTabl
         data.w    DskST-DskTabl
         data.w    DskUNMT-DskTabl
  page
;
; DskBSY
;
; Returns: D0.B - Result
;
DskBSY   CLR.L     D0

; DskCLR, DskINST, DskUNMT
;
DskUNMT
DskINST
DskCLR
         RTS
;
;DevTblEntry - Fetches the beginning of the entry of the device table
;              for this unit and the associated slot address.
;
;CANNOT USE A0
;        Entry: D0 = unit number (destroyed)
;        Exit : A4 = device table entry pointer
;               (NE) = error and D7 = IOresult code
;               (EQ) = good, A4 and A5 valid
;
; check unit number in range
;
DevTblEntry
         TST.W     D0                            ;See if the volume is negative
         BMI.S     DESerror                      ;NEGATIVE, bad unit number
         MOVE.L    pSysCom.W, A4                 ;see if to large
         MOVE.L    SCdevtab(A4), A4              ;PTR TO DEV TBL --> A4
         CMP.W     (A4)+, D0                     ;Compare Vol num to MAXDEV
         BLS.S     DESgood                       ;good unit number
;
DESerror MOVEQ     #IOEinvdev, D7                ;To large, bad unit number
         BRA.S     DESexit

;  Get the beginning of the device entry for this vol
;
DESgood  MULU      #UTlen, D0
         ADDA.L    D0, A4

DESexit  TST.W     D7
         RTS
  page
;
; DskRD/DskWR
;
; Parameters:   D0.W - Unit number
;               D1.L - Address of buffer
;               D2.L - Count
;               D3.L - Block Number
;               D5.W - Mode
;
; Returns:      D7.W = IOresult
;
DskRD    MOVEQ     #DskRead,D6         ;Set function to read
         BRA.S     DskRdWr             ;Process disk read request

DskWR    MOVEQ     #DskWrit,D6         ;Set function to write
DskRdWr  EXG       D5, D6              ;save mode in D6 and function in D5
         MOVE.L    D1,A0               ;A0 = buffer address

; 1. Get pointer to Unit Table entry and I/O port address
;
         BSR.S     DevTblEntry         ;get device table address
         BNE.S     Derr2               ;invalid unit number, exit

         LEA       DskPort.L,A1        ; Get slot 0 address (non-existant)
         MOVE.B    UTslt(A4),D4        ; Get slot number (1-4)
         EXT.W     D4                  ; *
         LSL.W     #5,D4               ; Compute disk port address for slot
         ADDA.W    D4,A1               ; *

; 1a. Check for unblocked I/O (Corvus disk controller commands)
;
         TST.W     D6                  ;Is this a Corvus disk command?
         BNE.S     Derr2               ;Yes, set error return code

; 2. Check for read only volume
;
         CMPI.B    #DskWrit,D5         ;Is this a "write"?
         BNE.S     BlkIO               ;No, go on
         TST.B     UTro(A4)            ;Is volume "read only"?
         BNE.S     Derr16              ;Yes, set error return code

; 3. Make sure block number is valid.
;
BlkIO    TST.L     D3                  ;Is block number negative?
         BLT.S     Derr3               ;Yes. Error.
         MOVE.L    D2,D4               ;Find final block
         SUBQ.L    #1,D4               ;final = (cnt-1)
         LSR.L     #8,D4               ;        div BlockSz
         LSR.L     #1,D4               ;        + start block
         ADD.L     D3,D4               ;D4 = final block number
         CMP.L     UTsiz(A4),D4        ;Is final block nmbr > volume length?
         BLE.S     DunitOK             ;No, go on
  page
Derr3    MOVEQ     #IOEioreq,D7        ;Set IORESULT to "invalid I/O request"  1.1
         BRA.S     DskExit             ;Return
Derr2    MOVEQ     #IOEinvdev,D7       ;Set IORESULT to "invalid device nmbr"  1.1
         BRA.S     DskExit             ;Return
Derr16   MOVEQ     #IOEwrprot,D7       ;Set IORESULT to "write protected"      1.1
DskExit  BRA.S     DskRtrn             ;Return

; 4. Compute absolute starting block number.
;
DunitOK  MOVE.L    UTblk(A4),D0        ;Get base block nmbr of volume
         ADD.L     D3,D0               ;Add user block number
         MOVE.B    UTdrv(A4),D1        ;D1 = drive number

         MOVE.B    UTsrv(A4),D6        ;Get disk server number

; 5. Process all whole blocks.
;
Dblcks   SUBI.L    #BlockSz,D2         ;Are there more whole blocks?
         BLT.S     DRWbyte             ;No. See if any partial blocks
         BSR.S     DRWsect             ;Read/write this block (D0-D2,D5-D6,A0-A1)
         BNE.S     DskRtrn             ;IORESULT error, return
         ADDQ.L    #1,D0               ;Bump block number
         BRA.S     Dblcks              ;Process next block, if any
  page
; 6. Process any partial blocks
;
DRWbyte  ADDI.L    #BlockSz, D2        ;add in block size
         BEQ.S     DskDone             ;if = 0 then no more left

; 6a. Read in whole block
;
         MOVE.L    A0,A2               ;Save buffer pointer
         SUBA.W    #BlockSz,SP         ;Get temp buffer
         MOVE.L    SP,A0               ;  on the stack
         MOVE.W    D5, -(SP)           ;Save r/w command
         MOVEQ     #Dskread,D5         ;Make it Read to get in block
         BSR.S     DRWsect             ;Read a whole block
         MOVE.W    (SP)+, D5           ;Get disk command (straighten stack)
         TST.W     D7                  ;I/O error?
         BNE.S     DRrmvbuf            ;if I/O error then remove buff and exit
         MOVE.L    SP,A0               ;Reset pointer to temp buffer

; 6b. Move partial data to true destination
;
         CMPI.W    #Dskread,D5         ;See if read or write
         BEQ.S     DRWpread            ;it is read, move data to user buffer
         EXG       A0,A2               ;move data to temp for Write

DRWpread SUBQ.W    #1,D2               ;Do for (number of bytes)-1 to 0
DRWmovp  MOVE.B    (A0)+,(A2)+         ;Copy byte to real dest
         DBRA      D2,DRWmovp          ;  and loop

; 6a. Write partial block.
;
         CMPI.W    #Dskread,D5         ;If Write then write back out temp
         BEQ.S     DRrmvbuf            ;it is read, so done
         MOVE.L    SP,A0               ;Reset pointer to temp buffer
         BSR.S     DRWsect             ;Write temp buffer to block

DRrmvbuf ADDA.W    #BlockSz,SP         ;Remove temp buffer

DskDone
DskRtrn  RTS                           ;Return
  page
;
; DRWsect - call the correct disk support to get 1 block
;        local or omninet, use slot number
;
; Parameters: A0.L - Buffer address
;             A1.L - I/O port address
;             A4.L - Device table entry address
;             D0.L - Block number
;             D1.W - Drive number
;             D5.W - Read ($32) or Write ($33) command
;             D6.B - Server number
;
; Returns:    A0.L - Next free location in buffer
;             D7.W - IORESULT
;
; All other registers are preserved.
;
DRWsect  MOVEM.L   D0-D6/A1-A6,-(SP)             ; Save registers

         LEA       LDRWsect, A3                  ; assume Local disk
         MOVE.B    UTslt(A4), D4                 ;get slot number
         CMPI.B    #5, D4                        ;is it omninet?
         BNE.S     DRWlocal                      ;no, it is local
         LEA       ODRWsect, A3                  ;use omninet disk rtn

DRWlocal
         JSR       (A3)                          ;call disk read/write rtn

         MOVEM.L   (SP)+,D0-D6/A1-A6             ; Restore registers
         TST.W     D7
         RTS
  page
;********** BEGIN of LOCAL DISK CODE *****************************
;
; LDRWsect - Read or Write a disk block on a Local disk
;
; Parameters: A0.L - Buffer address
;             A1.L - I/O port address
;             D0.L - Block number
;             D1.W - Drive number
;             D5.W - Read ($32) or Write ($33) command
;
; Returns:    A0.L - Next free location in buffer
;             D7.W - IORESULT
;
;        CrvsData = Corvus controller data register
;        CrvsStat = Corvus controller status register
;        Status bits...
;          CRVScrdy = controller ready, off - ready, on - not ready
;          CRVSbdir = bus direction ---
;                     off - host to controller, on - controller to host
;
LDRWsect MOVE.W    D5,D2               ; Send a read ($32) or
         BSR.S     LSndDsk1            ;   write ($33) block command
         MOVE.L    D0,D2               ; Compute drive nmbr/MSN block nmbr
         SWAP      D2                  ; Block # = 20 bits.  Put hi order 4 bits
         LSL.W     #4,D2               ;   of block # in hi order nibble of drive
         OR.B      D1,D2               ;   number byte.
         BSR.S     LSndDsk             ; Send drive number and hi nibble block #
         MOVE.W    D0,D2
         BSR.S     LSndDsk             ; Send LSB of block number
         LSR.W     #8,D2
         BSR.S     LSndDsk             ; Send MSB of block number
         CMPI.W    #DskWrit,D5         ; Are we reading or writing?
         BNE.S     LDRread0            ; Reading

; Write block processing
;
         MOVE.W    #BlockSz-1,D2       ; Block size - 1          1.1
LDWloop  BTST      #CRVScrdy,CrvsStat(A1) ; Test controller status
         BON.S     LDWloop             ; Wait until controller ready
         MOVE.B    (A0)+,CrvsData(A1)  ; Send a byte
         DBRA      D2,LDWloop          ; Loop until done
         BSR.S     LWLine              ; Wait for line to turn
         MOVE.B    CrvsData(A1),D7     ; Fetch result code
         BRA.S     LDRWdone            ; Return
  page
;
; Read block processing
;
LDRread0 BSR.S     LWLine              ; Wait for the line to turn
         MOVE.B    CrvsData(A1),D7     ; Fetch result code

LDRrdlop BTST      #CRVScrdy,CrvsStat(A1) ; Test controller status
         BON.S     LDRrdlop            ; Wait until controller ready
         BTST      #CRVSbdir,CrvsStat(A1) ; Test bus direction
         BOFF.S    LDRWdone            ; Finished if "host to controller"
         MOVE.B    CrvsData(a1),(a0)+  ; Store next byte
         BRA.S     LDRrdlop            ; Go get any more

LDRWdone BSR.S     LDErrChk            ; Check return code for error
         RTS                           ; Return
  page
; NEED timeouts for talking to disk ????
;
; LSndDsk - Send a byte to the local disk port
;
;       Enter:  A1.L - I/O port address
;               D2.B - Byte to send
;
;       All registers are preserved.
;
LSndDsk  BTST      #CRVScrdy,CrvsStat(A1) ; Test controller status
         BON.S     LSndDsk             ;Wait until controller ready
         MOVE.B    D2,CrvsData(A1)     ;Send the byte
         RTS
;
; LSndDsk1 -- Send first byte to the local disk port
;
;       Enter:  A1.L - I/O port address
;               D2.B - Byte to send
;
;       All registers are preserved.
;
LSndDsk0 MOVE.W    (SP)+,SR            ;enable interrupts
         NOP                           ;leave some time for interrupt
         NOP                           ;processing
LSndDsk1 MOVE.W    SR,-(SP)            ;save interrupt level
         ORI.W     #$0700,SR           ;disable interrupts
         BTST      #CRVScrdy,CrvsStat(A1) ; Test controller status
         BON.S     LSndDsk0            ;wait until controller ready
         MOVE.B    D2,CrvsData(A1)     ;send first byte
         MOVE.W    (SP)+,SR            ;enable interrupts
         RTS                           ;return
;
; LWLine - Wait for the line to turn On Local
;
;       Enter:  A1.L - I/O port address
;
LWLine   MOVE.L    D0,-(SP)            ;save register
         MOVEQ     #20,D0
LDwait1  DBRA      D0,LDwait1          ;wait a little bit
         BSR.S     LDwait2             ;check two times in case of glitch
         MOVE.L    (SP)+,D0            ;restore register

LDwait2  BTST      #CRVScrdy,CrvsStat(A1) ;Test controller status
         BON.S     LDwait2             ;wait until controller ready
         BTST      #CRVSbdir,CrvsStat(A1) ;Test bus direction
         BOFF.S    LDwait2             ;wait until "controller to host"
         RTS
  page
;
; LDErrChk - Convert Corvus disk return code to IOresult code
;
;       Enter:  D7.B - Corvus Disk return code
;       Exit :  D7.W - IOresult code for disk return code
;               D0.L - CLOBBERED
;
LDErrChk CLR.L     D0                  ;assume no errors
         TST.B     D7                  ;Error reported by controller?
         BPL.S     LDErr90             ;No

; Determine error type
;
         MOVEQ     #IOEnebhrd, D0      ;Set IORESULT to "hardware error"
         ANDI.B    #$1F, D7            ;Remove upper bits
         CMPI.B    #$1D, D7            ;beyond known error codes?
         BHI.S     LDErr90             ;yes, give "hardware error"

; Get IOresult code from table
;
         LEA       LDErrCodes, A6      ;address of Disk error/IOresult table
         MOVE.B    0(A6,D7), D0        ;get IOresult code (hi byte already clear)

LDErr90  MOVE.W    D0, D7              ;Set D7 with IOresult
         RTS

; Disk Error Code to IOresult code table
;
LDErrCodes
         DATA.B    IOEseek,IOEseek,IOEseek,IOEseek         ;1st 4 seek errors
         DATA.B    IOEcrcer                                ;header crc error
         DATA.B    IOEnoT0,IOEnoT0                         ;restore errors
         DATA.B    IOEoffln                                ;drive off-line
         DATA.B    IOEnebhrd,IOEnebhrd,IOEnebhrd           ;data faults
         DATA.B    IOEcrcer                                ;data crc error
         DATA.B    IOEnfmtd                                ;sector locate error
         DATA.B    IOEwrprot                               ;write protected
         DATA.B    IOEinvblk                               ;illegal sector address
         DATA.B    IOEbadcmd                               ;illegal disk command
         DATA.B    IOEoffln,IOEoffln                       ;Acknowledge problems
         DATA.B    IOEdrvTO                                ;drive timeout
         DATA.B    IOEnebhrd                               ;faults
         DATA.B    IOEcrcer                                ;crc error
         DATA.B    IOEseek                                 ;seek error
         DATA.B    IOEnebhrd                               ;verification
         DATA.B    IOEsvrdrv,IOEsvrdrv,IOEsvrdrv           ;last 7 severe drive errors
         DATA.B    IOEsvrdrv,IOEsvrdrv,IOEsvrdrv,IOEsvrdrv

;********** END of LOCAL DISK CODE *****************************
  page
;********** Rest of the driver is *****************************
;********** the OMNINET DISK CODE *****************************
;
; ODRWsect - Read or Write a disk block on a Omninet disk
;
; Parameters: A0.L - Buffer address
;             D0.L - Block number
;             D1.W - Drive number
;             D5.W - Read ($32) or Write ($33) command
;             D6.B - Server number
;
; Returns:    A0.L - Next free location in buffer
;             D7.W - IORESULT
;
ODRWsect SUBA.W    #ODdataLN, SP       ;allocate temporary data space
         MOVE.L    SP, A1              ;save data space address
         MOVE.B    D5,DCmd(A1)         ;set disk command - read or write
         MOVE.B    D0,DCBlkLo(A1)      ;set low order byte of block number
         LSR.L     #8,D0               ;set high order byte of block number
         MOVE.B    D0,DCBlkHi(A1)      ;block number = 20 bits
         CLR.B     D0                  ;put high order nibble of block number
         LSR.W     #4,D0               ;   with drive number in the hi order
         OR.B      D1,D0               ;   nibble
         MOVE.B    D0,DCDrv(A1)        ;set drive number and hi nibble block #
         MOVEQ     #-1, D3             ;show check disk result code
         MOVE.L    A0,ODwrAD(A1)       ;save address of REST of data

         CMPI.W    #DskWrit,D5         ;Are we reading or writing?
         BNE.S     ODRsect             ;Reading

ODWsect  MOVE.W    #BlockSz+DClen,SHtoLN(A1) ;number of bytes to send to drive
         CLR.W     SHfmLN(A1)          ;number of bytes expected back
         LEA       GData(A1), A0       ;pointing to area to receive data
         BSR       LongCmds            ;Writing
         BRA.S     ODRWdone            ;return

ODRsect  MOVE.W    #DClen,SHtoLN(A1)   ;number of bytes to send to drive
         MOVE.W    #BlockSz,SHfmLN(A1) ;number of bytes expected back
         BSR       ShortCmds

ODRWdone MOVE.L    ODwrAD(A1), A0      ;restore user's buffer address
         ADDA.W    #BlockSz, A0        ;Update buffer pointer
         ADDA.W    #ODdataLN, SP       ;remove temporary data space
         RTS
  page
;
; StrobIt -- Strobe command address to Transporter
;
;       Enter:  A2 = command address
;
;       Exit:   D7 = Transporter strobe status
;
;               EQ = successful
;               NE = Transporter not responding
;
;       All other registers are preserved
;
StrobIt  MOVEM.L   D0-D1/A6,-(SP)      ;save registers
         LEA       StrAdr.L, A6        ;strobe address
         MOVE.W    SR, -(SP)           ;save current interrupt level
         MOVE.W    SR, D0              ;get it to check it
         ANDI.W    #$0700, D0          ;get int level bits
         CMPI.W    #$0300, D0          ;is level 3 already disabled?
         BHI.S     SBnodis             ;yes, then omninet int is disabled
         MOVE.W    #$2300, SR          ;no, then disable level 3

SBnodis  CLR.L     D7                  ;assume no Transporter error
         MOVE.L    A2,D0               ;get command address
         ROL.L     #8,D0               ;move command address to msb

         BSR.S     SBwait              ;wait for Ready
         BEQ.S     SBerr               ;timed out, error exit
         MOVE.B    D0, (A6)            ;strobe address HI
         BSR.S     SBwait              ;wait for Ready
         BEQ.S     SBerr               ;timed out, error exit
         MOVE.B    D0, (A6)            ;strobe address MED
         BSR.S     SBwait              ;wait for Ready
         BEQ.S     SBerr               ;timed out, error exit
         MOVE.B    D0, (A6)            ;strobe address LO
         BSR.S     SBwait              ;wait for Ready
         BNE.S     SBexit              ;all done, exit

SBerr    MOVEQ     #IOEnotrn, D7       ;no transporter ...

SBexit   MOVE.W    (SP)+, SR           ;restore interrupt level
         MOVEM.L   (SP)+,D0-D1/A6      ;restore registers
         TST.W     D7                  ;set return condition code
         RTS                           ;return

SBwait   ROL.L     #8,D0               ;shift address byte in msb to lsb
         MOVE.W    #TOintvl,D1         ;get timeout interval
SBW1     BTST      #0,RdyAdr.L         ;is transporter ready?
         DBNE      D1,SBW1             ;repeat until transporter ready or time out
         RTS                           ;return(EQ=time out, NE=good)
  page
;
; ODcomnd -- send simple command to Transporter
;
;       Enter:  D0.B - Transporter command
;               D1.B - Socket number (if ENDRECV)
;
;       Exit:   D7.B - IORESULT
;
ODcomnd  MOVEM.L   A1-A2/D0-D1,-(SP)      ;save registers
         SUBA.W    #ODdataLN, SP       ;allocate temporary data space
         MOVE.L    SP, A1              ;save data space address
         PEA       RHdr(A1)            ;get pointer to result record
         MOVE.L    (SP)+,TCop(A1)      ;set result record pointer
         MOVE.B    D0,TCop(A1)         ;set Transporter command
         MOVE.B    D1,TCsock(A1)       ;set socket number (if ENDRECV)
         MOVE.B    #Waiting,RHpktRC(A1) ;set Transporter waiting flag
         LEA       TCmd(A1),A2         ;get command address
         BSR.S     StrobIt             ;strobe command address to Transporter
         BNE.S     ODcmd9              ;Transporter not responding
                                       ;if StrobIt worked then D7.L = 0
         MOVE.W    #TOintvl,D1         ;get timeout interval

ODcmd1   MOVE.B    RHpktRC(A1), D0     ;get Transporter result code
         CMPI.B    #Waiting, D0        ;has Transporter responded?
         DBNE      D1,ODcmd1           ;Do until (done) or (timeout)
         BSR       ODtrnChk            ;check result code in D0
         MOVE.W    D7, D0              ;put in correct register

ODcmd9   ADDA.W    #ODdataLN, SP       ;remove temporary data space
         MOVEM.L   (SP)+,A1-A2/D0-D1   ;restore registers
         RTS
;
; EndRecv -- end receive on disk server socket
;
EndRecv  MOVEM.L   D0-D1,-(SP)         ;save registers
         MOVE.W    #EndOp,D0           ;set command to ENDRECV
         MOVE.W    #CnstSkt,D1         ;set socket number
         BSR.S     ODcomnd             ;do ENDRECV on disk server socket
         MOVEM.L   (SP)+,D0-D1         ;restore registers
         MOVEQ     #0,D7               ;set no error IOresult
         RTS                           ;return
  page
;
; SetGo -- set up a receive for the 'GO' packet
;
SetGo    MOVE.W    SHtoLN(A1), -(SP)       ;save to length (used in SCexit)
         MOVE.W    #2,TCdtaLN(A1)          ;2 bytes of data, "GO"
         CLR.B     TChdrLN(A1)             ;no header (user control data)
         PEA       GData(A1)               ;put address of "GO" data area
         MOVE.L    (SP)+,ODdw(A1)          ;  into the data buffer address
         MOVE.B    ODdwhi(A1),TCdADhi(A1)  ;  field in the command block
         MOVE.W    ODdwlo(A1),TCdADlo(A1)
         BRA.S     SetGo1
;
; SetRecv -- set up a receive for the disk results and read data
;            returns result in D0
;
SetRecv  MOVE.W    SHtoLN(A1), -(SP)       ;save to length (used in SCexit)
         MOVE.L    A0,ODdw(A1)             ;put data buffer address
         MOVE.B    ODdwhi(A1),TCdADhi(A1)  ;  into the data buffer address
         MOVE.W    ODdwlo(A1),TCdADlo(A1)  ;  field in the command block
         MOVE.W    SHfmLN(A1),TCdtaLN(A1)
         MOVE.B    #3,TChdrLN(A1)          ;disk results have a hdr len of 3

SetGo1   MOVE.B    #Waiting,RHpktRC(A1)    ;set result to FF to see it change
         PEA       RHdr(A1)                ;load result vector address
         MOVE.L    (SP)+,ODdw(A1)          ;   into command block
         MOVE.B    ODdwhi(A1),TCrADhi(A1)
         MOVE.W    ODdwlo(A1),TCrADlo(A1)
         MOVE.B    #RecvOp,TCop(A1)        ;set up a receive
         MOVE.B    #CnstSkt,TCsock(A1)     ;  on socket $B0

         LEA       TCmd(A1),A2             ;get command address
         BSR       StrobIt                 ;strobe command address to Transporter
         MOVE.W    D7, D0                  ;save IOresult
         BNE       SCexit                  ;Transporter not responding

         MOVE.W    #TOintvl,D0             ;time out counter while waiting for
SC10     CMPI.B    #Waiting,RHpktRC(A1)    ;   internal transporter function
         DBNE      D0,SC10                 ;REPEAT until done or timeout

         MOVE.B    RHpktRC(A1), D0         ;get Transporter return code
         CMPI.B    #CmdAcpt, D0            ;was command accepted?
         BNE       SCerr1                  ;no, check Transporter error
         CLR.L     D0                      ;indicate success
         BRA       SCexit                  ;return
  page
; SndRest -- send the rest of the data (from long command) to the disk server
;            result of call is in D0, 0 = success
;
SndRest  MOVE.W    SHtoLN(A1), -(SP)       ;save to length  (used in SCexit)
         MOVEQ     #2, D1                  ;retry on NAKs and give ups  *kb 10/26/83*
         MOVE.B    ODwrAD+1(A1),TCdADhi(A1) ;put data buffer address in cmd block
         MOVE.W    ODwrAD+2(A1),TCdADlo(A1) ;Setup by ODRWsect or other caller of Longcmds
         MOVE.B    #RestSkt,TCSock(A1)     ;send rest of data to socket $A0
         SUBQ.W    #4,SHtoLN(A1)           ;send data, already sent 4 byte cmd
         BGE.S     SC20                    ;if don't branch error ????
         CLR.W     SHtoLN(A1)              ;result was negative, make it zero
SC20     MOVE.W    SHtoLN(A1),TCdtaLN(A1)  ;put length sending in cmd block
         CLR.B     TChdrLN(A1)             ;no user header for sending the
         BRA.S     SendIt                  ;    rest packets
;
; SndCmds -- send a disk command to the disk server
;            result of call is in D0, 0 = success
;
SndCmds  MOVE.W    SHtoLN(A1), -(SP)       ;save to length  (used in SCexit)
         MOVEQ     #2, D1                  ;retry on NAKs and give ups  *kb 10/26/83*
         PEA       DCmd(A1)                ;data is the Disk command
         MOVE.L    (SP)+,ODdw(A1)          ;put data buffer address
         MOVE.B    ODdwhi(A1),TCdADhi(A1)  ;  in command block
         MOVE.W    ODdwlo(A1),TCdADlo(A1)
         MOVE.B    #CnstSkt,TCsock(A1)     ;send command to socket $B0
         MOVE.W    SHtoLN(A1),TCdtaLN(A1)  ;assume to length is <= 4
         CMPI.W    #4,SHtoLN(A1)           ;are we sending less than 4 bytes
         BLS.S     SC30                    ;yes
         MOVE.W    #4,TCdtaLN(A1)          ;disk command is 4 bytes long
SC30     MOVE.B    #4,TChdrLN(A1)          ;send header is 4 bytes

SendIt   MOVE.B    D6,TCdest(A1)           ;dest host number is disk server host #
         MOVE.B    #Waiting,SHpktRC(A1)    ;set result to FF to see it change
         PEA       SHdr(A1)                ;put send result vector address
         MOVE.L    (SP)+,ODdw(A1)          ;into the command block
         MOVE.B    ODdwhi(A1),TCrADhi(A1)
         MOVE.W    ODdwlo(A1),TCrADlo(A1)
         MOVE.B    #SendOp,TCop(A1)        ;sendmsg opcode
SC50     LEA       TCmd(A1),A2             ;get command block address
         BSR       StrobIt                 ;strobe command address to Transporter
         MOVE.W    D7, D0                  ;save IOresult
         BNE.S     SCexit                  ;Transporter not responding

         MOVE.L    #$F0000,D0              ;wait for Transporter to try the send
SC60     CMPI.B    #Waiting, SHpktRC(A1)   ;is it done?
         BNE.S     SC70                    ;yes, see if worked
         SUBQ.L    #1, D0                  ;no, did it timeout?
         BNE.S     SC60                    ;no, look again
SC70     MOVE.B    SHpktRC(A1), D0         ;get result code
         CMPI.B    #TooLong, D0            ;get NAK because of bad msg? *kb 10/26/83*
         BEQ.S     SCrtry                  ;yes, try again              *kb 10/26/83*
         CMPI.B    #NoSockt, D0            ;get NAK because of bad msg? *kb 10/26/83*
         BEQ.S     SCrtry                  ;yes, try again              *kb 10/26/83*
         CMPI.B    #HdrErr, D0             ;get NAK because of bad msg? *kb 10/26/83*
         BEQ.S     SCrtry                  ;yes, try again              *kb 10/26/83*
         CMPI.B    #GaveUp, D0             ;did disk srvr accept message?  1.1
SCrtry   DBNE      D1, SendIt              ;no, Retry again if can      *kb 10/26/83*

SCerr1   BSR.S     ODtrnChk                ;convert transporter error code
SCexit   MOVE.W    (SP)+, SHtoLN(A1)       ;restore length in case of restart
         TST.W     D0
         RTS
  page
;
; ODtrnChk - convert Transporter result code to IOresult code
;
;        Enter: D0.B = Transporter Error code
;        Exit : D0.W = IOresult code
;
ODtrnChk MOVE.L    A0, -(SP)           ;save A0 for internal use
         TST.B     D0                  ;if positive then
         BPL.S     ODTCgood            ;    no error
         LEA       TPErrCodes, A0      ;address of IOresult codes
         CMPI.B    #Waiting,D0         ;timeout while waiting for Transporter?
         BEQ.S     ODTCto              ;yes

         ANDI.W    #$FF, D0            ;clear hi byte
         CMPI.W    #GaveUp, D0         ;is code in range of error codes
         BLT.S     ODTCgood            ;returned by the Transporter?
         CMPI.W    #BadDest, D0
         BLS.S     ODTCbad             ;yes

ODTCgood CLR.L     D0                  ;no, then show success
         BRA.S     ODTCexit

ODTCbad  ANDI.W    #7, D0              ;turn code into index into table
         MOVE.B    0(A0,D0), D0        ;get code from table
         BRA.S     ODTCexit            ;exit

ODTCto   MOVEQ     #IOEtimot,D0        ;time out code

ODTCexit MOVE.L    (SP)+, A0
         EXT.W     D0
         RTS

; Transporter error code to IOresult code table
;
TPErrCodes
         DATA.B    IOEtimot  ;GaveUp  aborted a send command after MaxRetries
         DATA.B    IOEtrnpdt ;TooLong last message sent was too long for the receiver
         DATA.B    IOEnobuf  ;NoSockt sent to an uninitialized socket
         DATA.B    IOEtrnpdt ;HdrErr  sender's header length did not match receiver's
         DATA.B    IOEtblid  ;BadSock illegal socket number
         DATA.B    IOEtbliu  ;Inuse   tried to set up a receive on an active socket
         DATA.B    IOEtrnpdt ;BadDest sent to an illegal host number

         DATA.B    0         ;fill, put it on even boundary
  page
;
; DskSrvTO -- Disk server timeout subroutine
;
DskSrvTO
         MOVEM.L   D1/A2,-(SP)         ;save registers
Dsto0    MOVE.L    #$400000,D1         ;set timeout counter              1.1
                                       ;   ($100000 = approx 14 seconds) 1.1
                                       ;   ($400000 = approx 56 seconds) 1.1
Dsto1    MOVE.B    RHpktRC(A1),D0      ;get Transporter return code
         CMPI.B    #CmdAcpt, D0        ;has return code changed?
         BNE.S     Dsto3               ;yes, check Transporter return code
         SUBQ.L    #1,D1               ;decrement timeout counter
         BNE.S     Dsto1               ;no timeout, check again

; Only time out if not doing mirror type commands
;
         LEA       DstoCmd,A2          ;list of mirror type commands
         MOVE.B    (A2)+,D1            ;number of commands - 1
         MOVE.B    DCmd(A1),D0         ;get disk command
                                       ;
Dsto2    CMP.B     (A2)+,D0            ;is mirror type command
         BEQ.S     Dsto0               ;yes, wait some more
         DBRA      D1,Dsto2            ;
                                       ;not mirror type,error exit
         MOVEQ     #IOEtimot,D0        ;set timeout result
         BRA.S     Dsto9

Dsto3    BSR.S     ODtrnChk            ;convert Transporter error code to IOresult
Dsto9    MOVEM.L   (SP)+,D1/A2         ;restore registers
         TST.W     D0                  ;set return condition code
         RTS

DstoCmd  DATA.B  DstoNbr-1             ;nmbr of commands - 1
         DATA.B  $8,9,$A,$C,$D         ;MIRROR type commands
         DATA.B  0,1,7                 ;FORMAT type commands  *kb 9/15/83*
DstoNbr  EQU     (%-DstoCmd)-1         ;number of commands
         DATA.B  0                     ;FILL                  *KB 9/15/83*
  page
; LongCmds
;
;        Enter :  A0 = pointer to receive data buffer
;                 DCmd(A1) = user control data
;                 ODwrAD(A1) = pointer to send data buffer
;        Exit  :  D7 = IOresult code
;
; 1. set up a receive for the GO message
;
LongCmds
Lcmd1    BSR       SetGo
         BNE       LcmdErr             ;if NE then fatal Transporter error

; 2. send disk command
;
         BSR       SndCmds
         BNE       LcmdErr             ;if NE then fatal Transporter error

; 3. wait to receive GO
;
Lcmd2    BSR.S     DskSrvTO            ;wait for data from disk server
         BEQ.S     Lcmd3               ;no error, go on
         CMPI.W    #IOEtimot,D0        ;timeout error?
         BNE.S     LcmdErr             ;no, set error return
         BSR       DskSrvFl            ;flush disk server request
         BRA.S     Lcmd1               ;start disk server request over

; 4. validate GO packet
;
Lcmd3    MOVE.B    TCdest(A1),D0       ;get host number of sender
         CMP.B     RHsor(A1),D0        ;did response come from the right place?
         BNE.S     Lcmd4               ;no, wait for right response
         BTST      #7,Gdata(A1)        ;disk server restart?
         BON.S     LongCmds            ;yes, start request over
         CMPI.W    #'GO',Gdata(A1)     ;get the correct response?
         BEQ.S     Lcmd5               ;got the "GO"

Lcmd4    BSR       SetGo               ;set up for GO receive again
         BNE.S     LcmdErr             ;error
         BRA.S     Lcmd2               ;wait for GO

; 5. set up receive for results
;
Lcmd5    BSR       SetRecv
         BNE.S     LcmdErr             ;if NE then fatal Transporter error

; 6. send REST
;
         BSR       SndRest
         BEQ.S     Lcmd6               ;sent, wait for results
         CMPI.W    #IOEnobuf, D0       ;was socket uninitialezed
         BEQ.S     Lcmd1               ;yes, then restart command
         BRA.S     LcmdErr             ;no, then fatal error
  page
; 7. wait for results
;
Lcmd6    BSR       DskSrvTO            ;wait for data from disk server
         BEQ.S     Lcmd7               ;no error, go on
         CMPI.W    #IOEtimot,D0        ;timeout error?
         BNE.S     LcmdErr             ;no, set error return
         BSR       DskSrvFl            ;flush disk server request
         BRA.S     Lcmd1               ;start disk server request over

; 8. validate results
;
Lcmd7    MOVE.B    TCdest(A1),D0       ;get host number of sender
         CMP.B     RHsor(A1),D0        ;did response come from the right place?
         BEQ.S     Lcmd8               ;yes, then check for restart
         BSR       SetRecv             ;no, set up receive again....
         BNE.S     LcmdErr             ;if NE then fatal Transporter error
         BRA.S     Lcmd6               ;go back and wait again...

Lcmd8    BTST      #7,RHdskLN(A1)      ;disk server restart?
         BON       Lcmd1               ;yes, start request over

; 9. done, check disk result code
;
LcmdOK   CLR.L     D0                  ;assume shouldn't check disk return code
         TST.L     D3                  ;should?
         BEQ.S     LcmdErr             ;no, show no IOresult error
         MOVE.B    RHdskRC(A1), D0     ;get disk return code then
         BSR       LDErrChk            ; convert to IOresult code
         MOVE.W    D7, D0              ;save IOresult

LcmdErr  BSR       EndRecv             ;end receive on disk server socket
         MOVE.W    D0, D7              ;get error return code
         RTS                           ;return for ShortCmds and LongCmds
  page
; ShortCmds
;
;        Enter :  A0 = pointer to receive data buffer
;                 DCmd(A1) = user control data
;                 ODwrAD(A1) = pointer to send data buffer
;        Exit  :  D7 = IOresult code

; 1. set up a receive for the results
;
ShortCmds
Scmd1    BSR       SetRecv
         BNE.S     LcmdErr             ;if NE then fatal Transporter error

; 2. send disk command to disk server
;
         BSR       SndCmds
         BNE.S     LcmdErr             ;if NE then fatal Transporter error

; 3. wait to receive results
;
Scmd2    BSR       DskSrvTO            ;wait for data from disk server
         BEQ.S     Scmd3               ;no error, go on
         CMPI.W    #IOEtimot,D0        ;timeout error?
         BNE.S     LcmdErr             ;no, set error return
         BSR       DskSrvFl            ;flush disk server request
         BRA.S     Scmd1               ;restart disk server request

; 4. validate results
;
Scmd3    MOVE.B    TCDest(A1),D0       ;get host number of sender
         CMP.B     RHsor(A1),D0        ;did response come from the right place?
         BNE.S     Scmd4               ;no
         BTST      #7,RHdskLN(A1)      ;disk server restart?
         BON.S     Scmd1               ;yes, start request over
         BRA.S     LcmdOK              ;no, done with request

Scmd4    BSR       SetRecv             ;set up receive again....
         BNE.S     LcmdErr             ;if NE then fatal Transporter error
         BRA.S     Scmd2               ;go back and wait again...
  page
;
; DskSrvFL -- Disk server flush subroutine
;
DskSrvFL BSR       EndRecv             ;end receive on disk server socket
         MOVE.W    SHtoLN(A1),-(SP)    ;save original to and
         MOVE.W    SHfmLN(A1),-(SP)    ;*    from length
         CLR.W     SHtoLN(A1)          ;set to and from length
         CLR.W     SHfmLN(A1)          ;*    to zero (0)
         BSR       SndCmds             ;flush disk server
         MOVE.W    (SP)+,SHfmLN(A1)    ;restore original to and
         MOVE.W    (SP)+,SHtoLN(A1)    ;*    from length
         MOVE.L    #$060000, D0        ;set timeout counter
                                       ;   ($060000 = approx 5 seconds)
Dsfl1    NOP                           ;use some instruction and
         NOP                           ;  fetch time
         NOP
         SUBQ.L    #1, D0              ;decrement timeout counter
         BNE.S     Dsfl1               ;no timeout, wait some more
         RTS                           ;return
  page
;
; DskST - unitstatus
;
;        Enter: D1 = pointer to parameter block
;               D2 = function code
;
;        Only 1 function supported :
;
;        Send arbitrary disk command to disk.
;        Function code = 0
;        Parameter Block :
;            record
;              DskCmdPtr : pointer;    {pointer to drive command}
;              DskCmdLen : word;       {length of drive command}
;              SendBufPtr: pointer;    {pointer to block to send}
;              SendBufLen: word;       {length to send}
;              RecvBufPtr: pointer;    {pointer to receive buffer}
;              RecvBufLen: word;       {max. length to receive}
;              SlotNumber: word;       {slot number of drive}
;              ServerHost: word;       {server host number}
;              ActualRcvd: word;       {actual number of bytes received}
;              DiskResult: word;       {Corvus disk return code}
;              end;
;
DskST    MOVEQ     #IOEfnccd, D7       ;assume invalid function code
         TST.W     D2                  ;is this valid function code?
         BNE.S     DskSTex             ;no, error exit
         CLR.L     D7                  ;clear IOresult code
         MOVE.L    D1, A3              ;get param blk adr *kb 9/28/83*
         MOVEA.L   DCmdBuff(A3), A2    ;address of drive command (4 bytes)
         MOVE.W    DrvCLen(A3), D3     ;get drive command length
         MOVE.W    SndBLen(A3), D2     ;get to length
         MOVEA.L   SndBuff(A3), A0     ;get send buffer pointer
         MOVE.W    SlotNmbr(A3), D6    ;get slot number from param block
         CLR.W     DskRslt(A3)         ;clear hi byte of result
         CMPI.W    #5, D6              ;is it Local or OmniNet disk?
         BEQ.S     OCRWdisk            ;OmniNet disk
         BCS.S     LCRWdisk            ;Local disk
         MOVEQ     #IOEuiopm, D7       ;invalid slot number, exit
DskSTex  RTS
  page
;
; OCRWdisk - Send arbitrary Corvus disk command to OmniNet disk
;
; Parameters: A0.L - pointer to send buffer
;             A2.L - pointer to drive command
;             A3.L - parameter block address
;             D2.W - send buffer length
;             D3.W - drive command length
;
; Returns:    D7.W - IOresult code
;
OCRWdisk SUBA.W    #ODdataLN, SP                 ;allocate temporary data space
         MOVE.L    SP, A1                        ;save data space address

         ADD.W     D3, D2                        ;send length includes command
         MOVE.W    D2, SHtoLN(A1)                ;To length is send buffer length
         MOVE.B    (A2)+,DCmd+0(A1)              ;put command in user control data
         MOVE.B    (A2)+,DCmd+1(A1)              ;    area of Transporter command
         MOVE.B    (A2)+,DCmd+2(A1)              ;    block.  Maybe less than 4
         MOVE.B    (A2)+,DCmd+3(A1)              ;    bytes to command.
         MOVE.L    A0,ODwrAD(A1)                 ;send buffer = REST of data

         MOVE.W    RcvBLen(A3), SHfmLN(A1)       ;From length is rcv buffer length
         MOVEA.L   RcvBuff(A3), A0               ;Get pointer to Rcv buffer
         MOVE.W    SrvrHost(A3), D6              ;Get disk server net host number
         CLR.L     D3                            ;show not check disk return code

         CMPI.W    #4, D2                        ;if sending more then the command
         BHI.S     OCRWlc                        ;  do longcmds
         BSR       ShortCmds                     ;if len <= 4 do ShortCmd
         BRA.S     OCRWdec                       ;  only sending drive command
OCRWlc   BSR       LongCmds

; the return code must be loaded explicitly since it comes from
; the header portion of the results packet....
;
OCRWdec  MOVE.W    RHpktLN(A1), TrueRcvd(A3)     ;give user actual # of bytes received
         MOVE.B    RHdskRC(A1), DskRslt+1(A3)    ;get return code
         BPL.S     OCRWdone                      ;fatal disk error?
         TST.W     D7                            ;did Transporter level fail
         BNE.S     OCRWdone                      ;yes, then tell user
         MOVEQ     #IOEnebhrd, D7                ;no, tell user about fatal disk error

OCRWdone ADDA.W    #ODdataLN, SP                 ;remove temporary data space
         RTS
  page
; LCRWdisk - Send arbitrary Corvus disk command to Local disk
;
; Parameters: A0.L - send buffer address
;             A2.L - drive command pointer
;             A3.L - parameter block address
;             D2.W - send buffer length
;             D3.W - drive command length
;             D6.W - slot number
;
; Returns:    D7.W - IORESULT
;
LCRWdisk LEA       DskPort.L,A1        ;Get slot 0 address (non-existant)
         LSL.W     #5,D6               ;Compute disk port address for slot
         ADDA.W    D6,A1               ;slot base adr = offset + slot 0 addr
         MOVE.W    D2, D4              ;Save send count

; Send drive command to Local Corvus disk
;PROBLEM: timeout AND user sending to much data or Not enough
;        must watch line if turns around or timeout if never turns around.
;
         SUBQ.W    #1, D3              ;set up loop counter
         MOVE.B    (A2)+, D2           ;get first byte
         BSR       LSndDsk1            ;send first byte
         BRA.S     LCWsc10             ;send rest of bytes

LCWscmd  MOVE.B    (A2)+, D2           ;get next byte
         BSR       LSndDsk             ;send next byte
LCWsc10  DBRA      D3, LCWscmd         ;loop until done

; Send data to Local Corvus disk
;
         SUBQ.W    #1,D4               ;Loop counter for send
         BLT.S     LCRdata             ;if not sending data then receive
LCWdata  MOVE.B    (A0)+, D2           ;get next byte
         BSR       LSndDsk             ;send next byte
         DBRA      D4, LCWdata         ;loop until done

; Read Corvus disk processing
; Problem :  need timeouts on line turn around & wait for ready
;
LCRdata  MOVEA.L   RcvBuff(A3), A0     ;Get receive buffer address
         MOVE.W    RcvBLen(A3), D3     ;Get max rcv length
         CLR.W     TrueRcvd(A3)        ;no actual bytes received yet
         BSR       LWLine              ;Wait for the line to turn
         MOVE.B    CrvsData(A1),DskRslt+1(A3)  ;Fetch result code
         BPL.S     LCRloop2            ;if fatal disk error
         MOVEQ     #IOEnebhrd, D7      ;   then tell user

LCRloop2 BTST      #CRVScrdy,CrvsStat(A1) ;Test controller status
         BON.S     LCRloop2            ;Wait until controller ready
         BTST      #CRVSbdir,CrvsStat(A1) ;Test bus direction
         BOFF.S    LCRWdone            ;Finished if "host to controller"
         MOVE.B    CrvsData(A1), D2    ;get next byte
         TST.W     D3                  ;user's rcv buffer full?
         BEQ.S     LCRloop2            ;yes, don't put byte in
         SUBQ.W    #1, D3              ;have one byte less space
         MOVE.B    D2, (A0)+           ;no, give user byte and
         ADDQ.W    #1, TrueRcvd(A3)    ;count actual bytes received
         BRA.S     LCRloop2            ;Go get any more

LCRWdone RTS

         END       DRVCDISK            ;entry point to driver
