a3DriverDepot/drivers/vsdrive/vsdrive.s

747 lines
25 KiB
ArmAsm

; VSDrive
; 0.01A - Initial release
; 0.02B - Fill in block sizes, implement reset control action
; 1.26 - More bounds checking for block writes, remove underscrores from
; names, clean up module name, synchronize version number with ADTPro release
; 1.28 - Use newer time-transporting protocol, add second drive as .VSDRIVE2
; 1.31 - Add null interrupt handler for ACIA
.TITLE "Apple /// Virtual Serial Drive Driver"
.PROC VSDRIVE
DriverVersion .EQU 1310 ; 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
EnvCmd .EQU 0D6 ; 1 byte envelope command
Checksum .EQU 0D7 ; 1 byte checksum calc
;
; Communications hardware constants
;
ACIADR .EQU 0c0f0 ; ACIA Data register
ACIASR .EQU 0c0f1 ; ACIA Status register
ACIACMD .EQU 0c0f2 ; ACIA Command mode register
ACIACTL .EQU 0c0f3 ; 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
;
; GoSlow macro - slow down via E-Register
;
.MACRO GoSlow
PHA
LDA EReg
ORA #080 ; Set 1MHz switch
STA EReg
PLA
.ENDM
;
; GoFast macro - speed up via E-Register
;
.MACRO GoFast
PHA
LDA EReg
AND #07f
STA EReg ; Whatever it was - set it back
PLA
.ENDM
;
; Comment Field of driver
;
.WORD 0FFFF ; Signal that we have a comment
.WORD 66. ; Length of comment field... entered manually.
; The Pascal Assembler can't count forward references.
; SCP only shows 78 characters' worth of information.
.ASCII "Apple /// Virtual Serial Drive Driver by"
.ASCII " David Schmidt 2012 - 2014"
; 1 2 3 4
; 1234567890123456789012345678901234567890
;------------------------------------
;
; Device identification Block (DIB) - VSDRIVE
;
;------------------------------------
DIB_0 .WORD DIB_1 ; Link pointer
.WORD Entry ; Entry pointer
.BYTE 008 ; Name length byte
.ASCII ".VSDRIVE "; Device name
.BYTE 080 ; Active, no page alignment
.BYTE 000 ; 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 ".VSDRIVE2 "; Device name
.BYTE 080 ; Active, no page alignment
.BYTE 000 ; 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 001 ; ACIA resource
.BYTE 000
.WORD 0FEC4 ; Do-nothing interrupt vector
.BYTE 000 ; Bank register
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
;
; 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
LDA #SIRLen
LDX SIRAddr
LDY SIRAddr+1
JSR AllocSIR ; Allocate the ACIA
BCS NoACIA
PHP
SEI ; Disable system interrupts
GoSlow ; Set up the communications environment
LDA #00b ; No parity, no interrupts
STA ACIACMD ; Store via ACIA command register
LDA #010 ; $16=300, $1e=9600, $1f=19200, $10=115k
STA ACIACTL ; Store via ACIA control register
LDA ACIASR ; Clear any prior ACIA interrupts
GoFast
PLP ; Re-enable system interrupt state
DInitDone
CLC
RTS
NoACIA
LDA #XNORESRC
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 ReadExit
JSR FixUp ; Correct for addressing anomalies
JSR ReadBlock ; Transfer a block to/from the disk
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 WriteExit ; Quantity to write is zero - so done
JSR FixUp
JSR WriteBlock
BCS IOError
WriteExit RTS
;
; 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_CONTROL call processing
; $00 = Reset device
; $FE = Perform media formatting
;
DControl
LDA CtlStat ; Control command
BEQ CReset
JMP DCWhat ; Control code no good!
CReset GoSlow
BIT ACIADR ; Clear ACIA Data register
GoFast
DCDone RTS
DCNoReset LDA #XNORESET ; Things went bad after reset
JSR SysErr ; Return to SOS with error in A
DCWhat LDA #XCTLCODE ; Control/status code no good
JSR SysErr ; Return to SOS with error in A
;------------------------------------
;
; Utility routines
;
;------------------------------------
;
; ReadBlock - Read requested blocks from device into memory
;
ReadBlock
LDA SosBuf ; Copy out buffer pointers
STA DataBuf
LDA SosBuf+1
STA DataBuf+1
LDA SosBuf+ExtPG
STA DataBuf+ExtPG
LDA #003 ; Read request with current time information
CLC
ADC SOS_Unit ; Add two to the request if this is the second unit
ADC SOS_Unit
STA EnvCmd
ReadSend
JSR SendEnvelope
JSR DTReceiveEnvelope
BCS ReadFail
LDY #000
STY Checksum
RdBlk2 JSR GETC
BCS ReadFail
STA (DataBuf),Y
EOR Checksum
STA Checksum
INY
BNE RdBlk2
JSR IncrAdr
RdBlk3 JSR GETC
BCS ReadFail
STA (DataBuf),Y
EOR Checksum
STA Checksum
INY
BNE RdBlk3
JSR IncrAdr
JSR GETC ; Pull Checksum
BCS ReadFail
CMP Checksum
BNE ReadFail
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 ReadSend
INC SosBlk+1
JMP ReadSend
;
; ReadFail - Complain with an OS I/O error
;
ReadFail
LDX StackPtr
TXS ; Pop! Goes the stack pointer
LDA #XIOERROR ; Nearby branch point
JSR SysErr ; Return to SOS with error in A
;
; WriteBlock - write memory out to requested blocks
;
WriteBlock
LDA SosBuf ; Copy out buffer pointers
STA DataBuf
LDA SosBuf+1
STA DataBuf+1
LDA SosBuf+ExtPG
STA DataBuf+ExtPG
LDA #002 ; Write request
CLC
ADC SOS_Unit ; Add two to the request if this is the second unit
ADC SOS_Unit
STA EnvCmd
WriteSend
JSR SendEnvelope
LDX #000
STX Checksum
WrBkLoop
LDY #000
WRLOOP:
LDA (DataBuf),Y
JSR PUTCC
INY
BNE WRLOOP
JSR IncrAdr
INX
CPX #002
BNE WRBKLOOP
LDA Checksum ; Checksum
JSR PUTC
JSR ReceiveEnvelope
BCS WriteFail
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
CLC
RTS ; We're done
WrMore INC SosBlk
BNE WriteSend
INC SosBlk+1
JMP WriteSend
;
; WriteFail - Complain with an OS I/O error
;
WriteFail
LDX StackPtr
TXS ; Pop! Goes the stack pointer
LDA #XIOERROR
JSR SysErr ; Return to SOS with error in A
;
; SendEnvelope - send the command envelope
;
SendEnvelope ; Send a command envelope
LDA #000
STA Checksum
LDA #0c5 ; "E"
JSR PUTCC ; Envelope
LDA EnvCmd
JSR PUTCC ; Send command
LDA SosBlk
JSR PUTCC ; Send LSB of requested block
LDA SosBlk+1
JSR PUTCC ; Send MSB of requested block
LDA Checksum
JSR PUTC ; Send envelope Checksum
RTS ; Carry is clear, return
;
; DTReceiveEnvelope - receive the command envelope back from host, with time data
;
; Note that we can't set the date and time through a SOS call, since device drivers
; are not allowed to make SOS calls.
;
DTReceiveEnvelope
LDA #000
STA Checksum
JSR GETC
BCS DTEnvelopeFail
CMP #0c5 ; "E" - S/B Command envelope
BNE DTEnvelopeFail
EOR Checksum
STA Checksum
JSR GETC
BCS DTEnvelopeFail
CMP EnvCmd ; Command requested
BNE DTEnvelopeFail
EOR Checksum
STA Checksum
JSR GETC ; Read LSB of requested block
BCS DTEnvelopeFail
CMP SosBlk
BNE DTEnvelopeFail
EOR Checksum
STA Checksum
JSR GETC ; Read MSB of requested block
BCS DTEnvelopeFail
CMP SosBlk+1
BNE DTEnvelopeFail
EOR Checksum
STA Checksum
LDX #004 ; Pull the four date/time bytes
DTRETime JSR GETC ; Ignore except for checksum calculations
BCS DTEnvelopeFail
EOR Checksum
STA Checksum
DEX
BNE DTRETime
JSR GETC ; Checksum
BCS DTEnvelopeFail
CMP Checksum
BNE DTEnvelopeFail
LDA #000
CLC
RTS
DTEnvelopeFail
SEC
RTS
;
; ReceiveEnvelope - receive the command envelope back from host
;
ReceiveEnvelope
JSR GETC
BCS EnvelopeFail
CMP #0c5 ; "E" - S/B Command envelope
BNE EnvelopeFail
JSR GETC
BCS EnvelopeFail
CMP EnvCmd ; Command requested
BNE EnvelopeFail
JSR GETC ; Read LSB of requested block
BCS EnvelopeFail
CMP SosBlk
BNE EnvelopeFail
JSR GETC ; Read MSB of requested block
BCS EnvelopeFail
CMP SosBlk+1
BNE EnvelopeFail
JSR GETC ; Checksum
BCS EnvelopeFail
CMP Checksum
BNE EnvelopeFail
LDA #000
CLC
RTS
EnvelopeFail
SEC
RTS
;
; CalcChecksum - Calculate the checksum of the block at DataBuf
;
CalcChecksum ; Calculate the checksum
LDA SosBuf ; Copy out buffer pointers again
STA DataBuf
LDA SosBuf+1
STA DataBuf+1
LDA #000 ; Clean everyone out
TAX
TAY
CCLoop:
EOR (DataBuf),Y
STA Checksum ; Save that tally in CHECKSUM
INY
BNE CCLoop
JSR IncrAdr ; Y just wrapped; bump buffer MSB
INX ; Need two loops
CPX #002 ; Second loop?
BNE CCLoop
RTS
;
; GETC - Get a byte from the ACIA
;
; Carry set on timeout, clear on data (returned in Accumulator)
;
GETC
LDA #000
STA Timer
STA Timer+1
GoSlow
GETC1 LDA ACIASR ; Check status bits via ACIA status register
AND #068
CMP #008
BEQ GETIT ; Data is ready, go get it
INC TIMER
BNE GETC1 ; Input register empty, no timeout; loop
INC TIMER+1
BNE GETC1 ; Input register empty, no timeout; loop
GoFast
SEC ; Timeout; return to caller
RTS
GETIT
LDA ACIADR ; Get character via ACIA data register
GoFast
CLC
RTS
;
; PUTCC - Put a byte to the ACIA, adding to the checksum
;
PUTCC PHA
EOR Checksum
STA Checksum
JMP PUTC0
;
; PUTC - Put a byte to the ACIA
;
PUTC
PHA ; Push 'character to send' onto the stack
PUTC0 LDA #000
STA Timer
STA Timer+1
GoSlow
PUTC1
LDA ACIASR ; Check status bits
AND #070
CMP #010
BNE PUTC1 ; Output register is full, no timeout; so loop
PLA ; Pull 'character to send' back off the stack
STA ACIADR ; Put character
GoFast
RTS
;
; 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
.END