; PiDrive - Based on VSDRIVE by David Schmidt
; 1.0 - Initial release

                .TITLE  "Apple /// PiDrive Driver"
                .PROC   PIDRIVE

DriverVersion   .EQU    1000            ; Version number
DriverMfgr      .EQU    4453            ; Driver Manufacturer - DS
;
; SOS Equates
;
ExtPG           .EQU    1401            ; Driver extended bank address offset
AllocSIR        .EQU    1913            ; Allocate system internal resource
SysErr          .EQU    1928            ; Report error to system
EReg            .EQU    0FFDF           ; Environment register
ReqCode         .EQU    0C0             ; Request code
SOS_Unit        .EQU    0C1             ; Unit number
SosBuf          .EQU    0C2             ; SOS buffer pointer (2 bytes)
ReqCnt          .EQU    0C4             ; Requested byte count
CtlStat         .EQU    0C2             ; Control/status code
CSList          .EQU    0C3             ; Control/status list pointer
SosBlk          .EQU    0C6             ; Starting block number
      QtyRead         .EQU    0C8             ; Pointer to bytes read returned by D_READ
;
; Our temps in zero page
;
Count           .EQU    0CE             ; 2 bytes
Timer           .EQU    0D0             ; 2 bytes
NumBlks         .EQU    0D2             ; 2 bytes lb,hb
DataBuf         .EQU    0D4             ; 2 bytes
CmdAck          .EQU    0D6             ; 1 byte
  ;
; Communications hardware constants
;
ACIADR          .EQU    0C088           ; ACIA Data register
ACIASR          .EQU    0C089           ; ACIA Status register
ACIACMD         .EQU    0C08A           ; ACIA Command mode register
ACIACTL         .EQU    0C08B           ; ACIA Control register
;
; SOS Error Codes
;
XDNFERR         .EQU    010             ; Device not found
XBADDNUM        .EQU    011             ; Invalid device number
XREQCODE        .EQU    020             ; Invalid request code
XCTLCODE        .EQU    021             ; Invalid control/status code
XCTLPARAM       .EQU    022             ; Invalid control/status parameter
     XNORESRC        .EQU    025             ; Resources not available
XBADOP          .EQU    026             ; Invalid operation
XIOERROR        .EQU    027             ; I/O error
XNODRIVE        .EQU    028             ; Drive not connected
XBYTECNT        .EQU    02C             ; Byte count not a multiple of 512
XBLKNUM         .EQU    02D             ; Block number to large
XDISKSW         .EQU    02E             ; Disk switched
XNORESET        .EQU    033             ; Device reset failed
;
; Switch Macro
;
                .MACRO  switch
                .IF     "%1" <> ""      ; If parameter 1 is present
                LDA     %1              ; Load A with switch index
                .ENDC
                CMP     #%2+1           ; Do bounds check
                BCS     $010
                ASL     A
                TAY
                LDA     %3+1,Y          ; Get switch index from table
                PHA
                LDA     %3,Y
                PHA
                                                                .IF     "%4" <> "*"     ; If parameter 4 omitted,
                RTS                     ;   then go to code
                .ENDC
$010            .ENDM
;
; Enter Critical Section
;
                .MACRO  EnterCritSec
                PHA
                LDA     EReg
                ORA     #080            ; Set 1MHz switch
                STA     EReg
                PLA
                PHP                     ; Disable interrupts
                SEI
                .ENDM
;
; Leave Critical Section
;
                .MACRO  LeaveCritSec
                PHA
                LDA     EReg
                AND     #07F
                STA     EReg            ; Whatever it was - set it back
                PLA
  PLP                     ; Restore interrupt state
                .ENDM
;
; Comment Field of driver
;
                .WORD   0FFFF           ; Signal that we have a comment
                .WORD   46.             ; Length of comment field... entered manually.
                                ; The Pascal Assembler can't count forward references.
  ; SCP only shows 78 characters' worth of information.
                .ASCII  "Apple /// PiDrive Driver by"
                .ASCII  " David Schmenk 2014"
;                                 1         2         3         4
;                        1234567890123456789012345678901234567890
;------------------------------------
;
; Device identification Block (DIB) - PIDRIVE
;
;------------------------------------
DIB_0           .WORD   DIB_1           ; Link pointer
                .WORD   Entry           ; Entry pointer
                .BYTE   008             ; Name length byte
                .ASCII  ".PIDRIVE       "; Device name
                .BYTE   080             ; Active, no page alignment
PiSlot          .BYTE   001             ; Slot number
                .BYTE   000             ; Unit number
                .BYTE   0E1             ; Type
                .BYTE   010             ; Subtype
                .BYTE   000             ; Filler
      DIB0_Blks       .WORD   0000            ; # Blocks in device
                .WORD   DriverMfgr      ; Manufacturer
                .WORD   DriverVersion   ; Driver version
                .WORD   0000            ; DCB length followed by DCB
;------------------------------------
;
; Device identification Block (DIB) - VSDRIVE2
;
;------------------------------------
DIB_1           .WORD   0000            ; Link pointer
                .WORD   Entry           ; Entry pointer
                .BYTE   009             ; Name length byte
                .ASCII  ".PIDRIVE2      "; Device name
                .BYTE   080             ; Active, no page alignment
                .BYTE   001             ; Slot number
                .BYTE   001             ; Unit number
                .BYTE   0E1             ; Type
                .BYTE   010             ; Subtype
                .BYTE   000             ; Filler
DIB1_Blks       .WORD   0000            ; # Blocks in device
                                                               .WORD   DriverMfgr      ; Manufacturer
                .WORD   DriverVersion   ; Driver version
                .WORD   0000            ; DCB length followed by DCB
;------------------------------------
;
; Local storage locations
;
;------------------------------------
LastOP          .BLOCK  002, 0FF        ; Last operation for D_REPEAT calls
SIRAddr         .WORD   SIRTbl
SIRTbl          .BYTE   010             ; Slot # resource
                .BLOCK  004, 000        ; No interrupt handling
SIRLen          .EQU    *-SIRTbl
RdBlkProc       .WORD   0000
WrBlkProc       .WORD   0000
StackPtr        .BYTE   000
DCB_Idx         .BYTE   000             ; DCB 0's blocks
                .BYTE   DIB1_Blks-DIB0_Blks ; DCB 1's blocks
  ;------------------------------------
;
; Driver request handlers
;
;------------------------------------
Entry           JSR     Dispatch        ; Call the dispatcher
                LDX     SOS_Unit        ; Get drive number for this unit
                                            LDA     ReqCode         ; Keep request around for D_REPEAT
                STA     LastOP,X        ; Keep track of last operation
                LDA     #000
                RTS
;
; The Dispatcher.  Note that if we came in on a D_INIT call,
; we do a branch to Dispatch normally.
; Dispatch is called as a subroutine!
;
DoTable         .WORD   DRead-1         ; 0 Read request
                .WORD   DWrite-1        ; 1 Write request
                .WORD   DStatus-1       ; 2 Status request
                .WORD   DControl-1      ; 3 Control request
                .WORD   BadReq-1        ; 4 Unused
                .WORD   BadReq-1        ; 5 Unused
                .WORD   BadOp-1         ; 6 Open - valid for character devices
                .WORD   BadOp-1         ; 7 Close - valid for character devices
                .WORD   DInit-1         ; 8 Init request
                .WORD   DRepeat-1       ; 9 Repeat last read or write request
                                                         Dispatch        SWITCH  ReqCode,9,DoTable ; Serve the request
;
; Dispatch errors
;
BadReq          LDA     #XREQCODE       ; Bad request code!
                JSR     SysErr          ; Return to SOS with error in A
BadOp           LDA     #XBADOP         ; Invalid operation!
                JSR     SysErr          ; Return to SOS with error in A
;
; D_REPEAT - repeat the last D_READ or D_WRITE call
;
DRepeat         LDX     SOS_Unit
                LDA     LastOP,X        ; Recall the last thing we did
                CMP     #002            ; Looking for operation < 2
                BCS     BadOp           ; Can only repeat a read or write
                STA     ReqCode
                JMP     Dispatch
NoDevice        LDA     #XDNFERR        ; Device not found
                JSR     SysErr          ; Return to SOS with error in A
;
; ACIA addresses for slot # relocation
;
RelocTbl        .WORD   Reloc1
                .WORD   Reloc2
                .WORD   Reloc3
                .WORD   Reloc4
                          .WORD   Reloc5
                .WORD   Reloc6
                .WORD   Reloc7
;
; D_INIT call processing - called at initialization
;
DInit           LDA     SOS_Unit        ; Check if we're initting the zeroeth unit
                BNE     DInitDone       ; If not - skip the serial setup
                LDY     #001
                STY     DataBuf+ExtPG
                LDX     #14.-1
RelocLoop       LDA     RelocTbl,X
                STA     DataBuf+1
                DEX
                LDA     RelocTbl,X
                STA     DataBuf
                LDA     PiSlot
  ASL     A
                ASL     A
                ASL     A
                ASL     A
                ORA     (DataBuf),Y
                STA     (DataBuf),Y
                DEX
                BPL     RelocLoop
                LDA     PiSlot
                ORA     #010
                STA     SIRTbl
                LDA     #SIRLen
                LDX     SIRAddr
                LDY     SIRAddr+1
                                             JSR     AllocSIR        ; Allocate the ACIA
                BCS     NoACIA
CReset          EnterCritSec            ; Set up the communications environment
                LDA     #00B            ; No parity, no interrupts
Reloc1          STA     ACIACMD         ; Store via ACIA command register
                LDA     #010            ; $16=300, $1e=9600, $1f=19200, $10=115k
Reloc2          STA     ACIACTL         ; Store via ACIA control register
Reloc3          LDA     ACIASR          ; Clear any prior ACIA interrupts
                LDA     #080
                JSR     PutByte         ; Init connection to Pi
                JSR     GetByte         ; Check ACK
                LeaveCritSec            ; Re-enable system state
                CMP     #081
                BNE     NoACIA
DInitDone       CLC
                RTS
NoACIA          LDA     #XNORESRC
                JSR     SysErr          ; Return to SOS with error in A
;
; D_CONTROL call processing
;  $00 = Reset device
               ;  $FE = Perform media formatting
;
DControl        LDA     CtlStat         ; Control command
                BEQ     CReset
                LDA     #XCTLCODE       ; Control/status code no good
                JSR     SysErr          ; Return to SOS with error in A
;
; D_STATUS call processing
;  $00 = Driver Status
;  $01 = Report drive size
;  $FE = Return preferred bitmap location ($FFFF)
;
DStatus         LDA     CtlStat         ; Which status code to run?
                BNE     DS0
                LDY     #000            ; 000 - Driver status, return zero
                STA     (CSList),Y
                CLC
                RTS
DS0             CMP     #0FE
                BNE     DSWhat
                LDY     #000            ; Return preferred bit map locations.
                LDA     #0FF            ; We return FFFF, don't care
                STA     (CSList),Y
                INY
                STA     (CSList),Y
                CLC
                RTS
                                           DSWhat          LDA     #XCTLCODE       ; Control/status code no good
                JSR     SysErr          ; Return to SOS with error in A
;
; D_READ call processing
;
DRead           TSX
                STX     StackPtr        ; Hang on to the stack pointer for hasty exits
                JSR     CkCnt           ; Checks for validity, aborts if not
                JSR     CkUnit          ; Checks for unit below unit max
                LDA     #000            ; Zero # bytes read
                STA     Count           ; Local count of bytes read
                STA     Count+1
                TAY
                STA     (QtyRead),Y     ; Userland count of bytes read
                INY
                STA     (QtyRead),Y     ; Msb of userland bytes read
                LDA     NumBlks         ; Check for NumBlks greater than zero
                ORA     NumBlks+1
                BEQ     RdDone
                LDA     SosBuf          ; Copy out buffer pointers
                STA     DataBuf
                              LDA     SosBuf+1
                STA     DataBuf+1
                LDA     SosBuf+ExtPG
                STA     DataBuf+ExtPG
                JSR     FixUp           ; Correct for addressing anomalies
RdBlk           JSR     ReadBlock       ; Transfer a block to/from the disk
                DEC     NumBlks         ; Did we get what was asked for?
                BNE     RdMore
                DEC     NumBlks+1
                BPL     RdMore
                LDA     SosBlk
                CMP     #002            ; Is this block #2 (lsb=2)?
                BNE     RdDone
                LDA     SosBlk+1
                BNE     RdDone          ; Is this block #2 (msb=0)?
                LDY     #029            ; Yes - store off the disk size
                LDA     (SosBuf),Y
                PHA
                INY
                LDA     (SosBuf),Y
                PHA
                LDX     SOS_Unit        ; Get the stats on this unit
                LDY     DCB_Idx,X
                PLA
                      STA     DIB0_Blks+1,Y
                PLA
                STA     DIB0_Blks,Y
RdDone          CLC
                RTS
RdMore          INC     SosBlk
                BNE     RdBlk
                INC     SosBlk+1
                JMP     RdBlk
                LDY     #000
                LDA     Count           ; Local count of bytes read
                STA     (QtyRead),y     ; Update # of bytes actually read
                INY
                LDA     Count+1
                STA     (QtyRead),y
                BCS     IOError         ; An error occurred
ReadExit        RTS                             ; Exit read routines
IOError         LDA     #XIOERROR       ; I/O error
                JSR     SysErr          ; Return to SOS with error in A
;
; D_WRITE call processing
;
DWrite          TSX
                STX     StackPtr        ; Hang on to the stack pointer for hasty exits
                JSR     CkCnt           ; Checks for validity
                                                                       JSR     CkUnit          ; Checks for unit below unit max
                LDA     NumBlks         ; Check for NumBlks greater than zero
                ORA     NumBlks+1
                BEQ     WrDone          ; Quantity to write is zero - so done
                LDA     SosBuf          ; Copy out buffer pointers
                STA     DataBuf
                LDA     SosBuf+1
                STA     DataBuf+1
                LDA     SosBuf+ExtPG
                STA     DataBuf+ExtPG
                JSR     FixUp
WrBlk           JSR     WriteBlock
                DEC     NumBlks         ; Did we put what was asked for?
                BNE     WrMore          ; Not done yet... go around again
                DEC     NumBlks+1       ; (16 bit decrement)
                BPL     WrMore          ; Not done yet... go around again
WrDone          CLC
                RTS                     ; We're done
WrMore          INC     SosBlk
                BNE     WrBlk
  INC     SosBlk+1
                                    JMP     WrBlk
;------------------------------------
;
; Utility routines
;
;------------------------------------
;
; Check ReqCnt to ensure it's a multiple of 512.
;
CkCnt           LDA     ReqCnt          ; Look at the lsb of bytes requested
                BNE     $1              ; No good!  lsb should be 00
                STA     NumBlks+1       ; Zero out the high byte of blocks
                LDA     ReqCnt+1        ; Look at the msb
                LSR     A               ; Put bottom bit into carry, 0 into top
                STA     NumBlks         ; Convert bytes to number of blks to xfer
                BCC     CvtBlk          ; Carry is set from LSR to mark error.
$1              LDA     #XBYTECNT
                JSR     SysErr          ; Return to SOS with error in A
;
; Test for valid block number; abort on error
;
CvtBlk          LDX     SOS_Unit
                LDY     DCB_Idx,X
                SEC
                LDA     DIB0_Blks+1,Y   ; Blocks on unit msb
                                  SBC     SosBlk+1        ; User requested block number msb
                BVS     BlkErr          ; Not enough blocks on device for request
                BEQ     $1              ; Equal msb; check lsb.
                RTS                     ; Greater msb; we're ok.
$1              LDA     DIB0_Blks,Y     ; Blocks on unit lsb
                SBC     SosBlk          ; User requested block number lsb
                BVS     BlkErr          ; Not enough blocks on device for request
                RTS                     ; Equal or greater msb; we're ok.
BlkErr          LDA     #XBLKNUM
                JSR     SysErr          ; Return to SOS with erorr in A
IncrAdr         INC     Count+1         ; Increment byte count MSB
                INC     DataBuf+1       ; Increment DataBuf MSB in userland
;
; Fix up the buffer pointer to correct for addressing
; anomalies.  We just need to do the initial checking
; for two cases:
; 00xx bank N -> 80xx bank N-1
; 20xx bank 8F if N was 0
                ; FDxx bank N -> 7Dxx bank N+1
; If pointer is adjusted, return with carry set
;
FixUp           LDA     DataBuf+1       ; Look at msb
                BEQ     $1              ; That's one!
                CMP     #0FD            ; Is it the other one?
                BCS     $2              ; Yep. fix it!
                RTS                     ; Pointer unchanged, return carry clear.
$1              LDA     #080            ; 00xx -> 80xx
                STA     DataBuf+1
                DEC     DataBuf+ExtPG   ; Bank N -> band N-1
                LDA     DataBuf+ExtPG   ; See if it was bank 0
                CMP     #07F            ; (80) before the DEC.
                BNE     $3              ; Nope! all fixed.
                LDA     #020            ; If it was, change both
                STA     DataBuf+1       ; Msb of address and
                LDA     #08F
                STA     DataBuf+ExtPG   ; Bank number for bank 8F
                RTS                     ; Return carry set
                     $2              AND     #07F            ; Strip off high bit
                STA     DataBuf+1       ; FDxx ->7Dxx
                INC     DataBuf+ExtPG   ; Bank N -> bank N+1
$3              RTS                     ; Return carry set
CkUnit          CLC
                RTS
;
; CmdSend = Send request and block #
;
CmdSend         STA     CmdAck
                JSR     PutByte
                LDA     SosBlk
                JSR     PutByte
                LDA     SosBlk+1
                JSR     PutByte
                INC     CmdAck
                JSR     GetByte
                CMP     CmdAck
                BNE     RWFail
                RTS
;
; RWFail - Complain with an OS I/O error
;
RWFail          LeaveCritSec
                LDX     StackPtr
                TXS                     ; Pop! Goes the stack pointer
                LDA     #XIOERROR       ; Nearby branch point
                JSR     SysErr          ; Return to SOS with error in A
;
                                                           ; ReadBlock - Read requested blocks from device into memory
;
ReadBlock       EnterCritSec
                LDA     SOS_Unit
                ASL     A
                ORA     #0A4            ; Read command
                JSR     CmdSend
                LDY     #000
                LDX     #002            ; Two pages per block
$1              JSR     GetByte
                BCS     RWFail
                STA     (DataBuf),Y
                INY
                BNE     $1
                JSR     IncrAdr
                DEX
                BNE     $1
                JSR     GetByte         ; Get status
                BNE     RWFail
                LeaveCritSec
                RTS
;
; WriteBlock - write memory out to requested blocks
;
WriteBlock      EnterCritSec
                LDA     SOS_Unit
                ASL     A
                ORA     #0A8            ; Write command
                JSR     CmdSend
                LDY     #000
                LDX     #002            ; Two pages per block
               $1              LDA     (DataBuf),Y
                JSR     PutByte
                INY
                BNE     $1
                JSR     IncrAdr
                DEX
                BNE     $1
                JSR     GetByte         ; Get status
                BNE     RWFail
                LeaveCritSec
                RTS
;
; GetByte - Get a byte from the ACIA
;
; Carry set on timeout, clear on data (returned in Accumulator)
;
GetByte         LDA     #000
                STA     Timer
                STA     Timer+1
Reloc4
GetByte1        LDA     ACIASR          ; Check status bits via ACIA status register
                AND     #008
                BNE     GETIT           ; Data is ready, go get it
                INC     TIMER
                BNE     GetByte1        ; Input register empty, no timeout; loop
                INC     TIMER+1
                BNE     GetByte1        ; Input register empty, no timeout; loop
                SEC                     ; Timeout; return to caller
                                   RTS
Reloc5
GETIT           LDA     ACIADR          ; Get character via ACIA data register
                CLC
                RTS
;
; PutByte - Put a byte to the ACIA
;
PutByte         PHA                     ; Push 'character to send' onto the stack
Reloc6
PutByte1        LDA     ACIASR          ; Check status bits
                AND     #010
                BEQ     PutByte1        ; Output register is full, no timeout; so loop
                PLA                     ; Pull 'character to send' back off the stack
Reloc7          STA     ACIADR          ; Put character
                RTS
                .END
                                                                                                                                                                                                                                                                                                                                                                                                       