; 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