*****************************************************************************
*
*  Flop2PC - Transfer floppy image to PC over a serial port
*
*	This program is for a SWTPC 6800 with a FD-2 floppy controller.
*	It transmits an image of a 5.25" SDOS disk through an MP-S
*	serial port in any one of the SWTPC 6800 I/O ports. These disks
*	are DSSD, 35 tracks with 18 sectors of 128 bytes per track.
*
*	The XMODEM protocol is used for the serial transfer. The SWTBUG
*	or MIKBUG PROM must be present in the computer. If using the
*	console port for the XMODEM transfer, error messages and prompts
*	won't be seen once the XMODEM transfer is started. This makes it
*	difficult to notice and determine disk I/O problems. A second
*	serial port for the XMODEM transfer is the ideal configuration,
*	though not required.
*
*	This program can run stand alone under SWTBUG (use the L command
*	to load FLOP2PC.S19) or as a program under FLEX.
*
*	Written by Mike Douglas
*
*	Rev	 Date	    Desc
*	1.0	05/10/23    Original
*
*****************************************************************************

* SWTBUG (MIKBUG) equates

OUTEEE	equ	$E1D1		;SWTBUG display character
INEEE	equ	$E1AC		;SWTBUG input character
PDATA1	equ	$E07E		;SWTBUG display message
dispMsg	equ	PDATA1
STACK	equ	$A07F		;stack in SWTBUG RAM
OUT4HS	equ	$E0C8		;display 4 hex digits
CONTRL	equ	$E0E3		;re-enter SWTBUG
PORECH	equ	$A00C		;zero for INEEE to echo input

* Disk equates

NUMTRK	equ	35		;number of tracks
NUMSEC	equ	18		;number of sectors per track
SECLEN	equ	128		;sector length
MINDRV	equ	0		;first drive number
MAXDRV	equ	3		;max drive number 
TRKLEN	equ	NUMSEC*SECLEN	;length of track in bytes
RDTRIES	equ	3		;read retries

* XMODEM equates

PKTLEN	equ	128		;128 byte xmodem packet length
SOH	equ	$01
EOT	equ	$04
ACK	equ	$06
NAK	equ	$15
EOF	equ	$1A		;ctrl-z character
XMTRIES	equ	10		;number of xmodem retries

* MP-S serial board equates

SIORDRF	equ	$01		;receive data register full
SIOTDRE	equ	$02		;transmit data register empty

* Misc equates

WFLEX	equ	$AD03		;FLEX warm start address
CR	equ	$0D		;ASCII for carriage return
LF	equ	$0A		;ASCII for line feed
DEL	equ	$7F		;ASCII DEL
BS	equ	$08		;ASCII backspace
CTRLC	equ	$03		;ASCII for control-c

	org	$0100		;load address
*-----------------------------------------------------------------------------
*   Initialize for transfer
*-----------------------------------------------------------------------------
init	bra	start
	fcb	10		;current version number

start	lds	#STACK		;initialize stack pointer
	clr	PORECH		;enable input echo (INEEE)
	ldaa	#$FF		;set invalid drive number
	staa	drvNum

* Display welcome message, then get the drive number

	ldx	#mWelcom	;display welcome message
	jsr	dispMsg

getDrv	ldx	#mDrive		;display drive number prompt
	jsr	dispMsg

	jsr	rcvCon		;get byte from the console
	oraa	#$20		;upper case letters to lower case
	cmpa	#'x'		;exit requested?
	bne	chkDrv		;no, check the drive number entered
	jmp	pgmExit		;else, exit as requested

chkDrv	suba	#MINDRV+'0'	;ASCII 1st drive goes to binary 0
	cmpa	#MAXDRV-MINDRV+1   ;validate <= max drive
	bcc	getDrv		;invalid drive number, prompt again
	
	adda	#MINDRV		;restore offset of 1st drive (if any) 
	staa	drvNum		;save the drive number to use

* Track length and number of tracks are fixed for this 
*   version of FLEX

	ldaa	#1		;force double sided
	staa	fDouble
	ldx	#2*TRKLEN	;init track length (double sided)
	stx	trkLen
	ldaa	#NUMTRK		;init number of tracks
	staa	numTrk
	jsr	sizeRam		;see how much RAM we can use

* Get the serial port to use for the transfer

getPort	ldx	#mPort		;display port number prompt
	jsr	dispMsg

	jsr	rcvCon		;get byte from the console
	oraa	#$20		;upper case letters to lower case
	cmpa	#'x'		;exit requested?
	bne	chkPort		;no, check the port number entered
	jmp	pgmExit		;else, exit as requested

chkPort	suba	#'0'		;subtract ACII offset
	cmpa	#5		;must be 0-4
	bcc	getPort		;invalid port, re-enter

	asla			;convert to port address
	asla
	staa	portAdr+1	;LSB of port address
	ldab	#$80		;MSB of port address
	stab	portAdr

	ldx	portAdr		;initialize transfer port
	cmpa	#$04		;using console?
	beq	skipRst		;yes, skip port reset
	
	ldaa	#3		;reset serial port
	staa	0,x

skipRst	ldaa	#$15		;8N1
	staa	0,x

* Prompt the user to start the XMODEM receive operation on the PC. 
*    Wait for a clear receive line for a least one second, then start
*    looking for NAK from the PC.

	ldx	#mStart		;display start file transfer prompt
	jsr	dispMsg

clrRcv1	jsr	rcvByte		;loop until input clear for 1 second
	bne	clrRcv1
	
waitNak	jsr	rcvByte		;wait for a character
	cmpa	#NAK
	bne	waitNak		;have a NAK, use checksum protocol

* NAK received meaning the XMODEM receive has started. Initialize
*     for the transfer.

	clr	trkNum		;starting on track 0
		
	ldaa	#1		;init xmodem block number to one
	staa	blkNum
	
	ldx	#trkBuf		;trkBuf is initial starting point for
	stx	sndPtr		;   XMODEM send

*-----------------------------------------------------------------------------
*  mainLp - Loop through all tracks on the disk buffering as many tracks
*    as will fit in RAM before sending via xmodem, then repeat.
*-----------------------------------------------------------------------------
mainLp	ldx	#trkBuf		;disk buffering always starts at trkBuf
	stx	trkPtr
	jsr	dSelect		;select drive and start motor

* Read and buffer tracks until RAM is full or all tracks have been read.

bufLoop	bsr	readTrk		;read into trkBuf

	inc	trkNum		;increment track number
	ldaa	trkNum		;done all tracks on the disk?
	cmpa	numTrk		;done all tracks on the disk?
	beq	sendBuf		;yes, go send the buffered tracks
	
	ldaa	fTrkErr		;read error occur?
	bne	sendBuf		;yes, send what has been buffered

	ldaa	trkPtr+1	;compare trkPtr-bufEnd
	suba	bufEnd+1
	ldaa	trkPtr		;MSBs
	sbca	bufEnd
	bcs	bufLoop		;still room, keep going

* Track buffer is full or all tracks have been read. Send the buffered
*    tracks via xmodem.

sendBuf	jsr	dMotOff		;turn off drive motor
	jsr	sndTrks		;send the buffered tracks via xmodem

	ldaa	trkNum
	cmpa	numTrk		;done all tracks?
	bne	mainLp		;no, not done yet

* The entire disk has been read and (mostly) transmitted. See if there are
*    left over bytes to send. If so, send them.

	ldaa	sndPtr+1	;A=LSB of transmit pointer
	cmpa	#trkBuf&$FF	;pointing to LSB of address of trkBuf?
	beq	noExtra		;same, no extra bytes to send
	
	jsr	sndPkt		;otherwise, send one last packet

* Send EOT and wait for response. Then display the success message and
*    start the program over.

noExtra	jsr	sndEot		;send and get response for EOT

	ldx	#mDone		;print the all done message
	jsr	dispMsg
	jmp	getDrv		;start over asking for a drive
	
*-----------------------------------------------------------------------------
* readTrk - read NUMSEC sectors from the current track into a trkBuf as
*   pointed to by trkPtr. After the track is read, trkPtr is updated
*   by the length of a track to point to the next track buffer.
*-----------------------------------------------------------------------------
readTrk	clr	side		;start on side 0 (bottom)
	jsr	dSeek		;seek to track in trkNum
	bne	seekErr		;seek failed

sideLp	ldaa	#2		;init retry step
	staa	rtStep
	clr	fTrkErr		;no error on this track yet

	clr	secIdx		;start with 1st sector on track
	ldaa	#NUMSEC		;set number of sectors to read
	staa	secCnt

secLoop	jsr	dSecAdr		;get sector number and buffer address

stpLoop	jsr	dRead		;read the sector
	bne	secRtry		;read error, run retry logic
	
nxtSec	dec	secCnt		;decrement sector count
	bne	secLoop		;loop until all sectors read

	ldaa	side		;done with required number of sides?
	cmpa	fDouble
	beq	rdDone		;yes, read is done

	inc	side		;else, now on side 1
	jsr	dSide1		;select top side
	bra	sideLp		;repeat track read for top side

rdDone	ldaa	trkPtr+1	;increment tkrPtr by the track length
	adda	trkLen+1
	staa	trkPtr+1
	
	ldaa	trkPtr		;MSB
	adca	trkLen
	staa	trkPtr

	rts

*-----------------------------------------------------------------------------
* Seek error. This is either an actual seek error or the disk is
*    write protected. 
*-----------------------------------------------------------------------------
seekErr	ldx	#mSkErr		;display seek error message
	jsr	dispMsg	

	jsr	dMotOff		;turn off drive motor
	lds	#STACK		;re-initialize stack pointer
	jmp	getDrv		;start over	

*--------------------------------------------------------------------------
* secRtry - Read error retry logic. Three retry steps are tried in
*   the order shown below:
*
*   Step    Action
*    2	Re-read RDTRIES times
*    1	Seek out one track and back, then re-read RDTRIES times
*    0	Seek in one track and back, then re-read RDTRIES times
*
*    Upon entry, if the error is "sector not found," then retries are
*    not performed since the controller has already tried to read the
*    address mark across three revolutions.
*--------------------------------------------------------------------------	
secRtry	bita	#sSECERR	;sector not found error?
	bne	nxtStep		;yes, skip retries, go to next step

	ldaa	#RDTRIES	;init retry counter
	staa	rdRtry

retryLp	jsr	dRead		;try the read again
	beq	rtDone		;success, retry is done

	dec	rdRtry		;decrement retry counter
	bne	retryLp		;try again

* nxtStep - move to next retry step

nxtStep	dec	rtStep		;decrement to next retry step
	beq	skPast		;step 0, step in one track and come back
	bpl	skBack		;step 1, step out one track and come back
	jsr	dspErr		;else, give up, display the error

rtDone	ldaa	#2		;restart the retry step counter
	staa	rtStep
	bra	nxtSec		;re-enter the sector loop

*-----------------------------------------------------------------------------
* skBack - step out then back in, retry read
* skPast - step in then back out, retry read
*-----------------------------------------------------------------------------
skBack	ldaa	trkNum		;A=current track
	tab			;save in B
	beq	skPast		;already on track zero, step in instead

	deca			;step out one track
	bra	doBump		;go bump the head

skPast	ldaa	trkNum		;A=current rack
	tab			;save in B
	inca			;step in one track
	cmpa	numTrk		;already at max track?	
	bcc	skBack		;yes, seek back instead

doBump	staa	trkNum		;save new track position
	jsr	dSeek		;see to new track

	stab	trkNum		;restore track number
	jsr	dSeek		;back to original track

	jmp	stpLoop		;try reads again

*-----------------------------------------------------------------------------
* dspErr - display the current track and sector number which just had
*     a read error. If this is the first error on a track, the track
*     number is displayed first.
*-----------------------------------------------------------------------------
dspErr	ldaa	fTrkErr		;test track error flag
	bne	dspSec		;track num already displayed, disp sector

	inca			;set track flag non-zero
	staa	fTrkErr

* First error on this track. Display "Track xx errors: "

	ldx	#errTrk		;X->where to put ASCII track number
	ldaa	trkNum		;A=track with error
	jsr	bin2dec		;store ASCII track number

	ldx	#mTrkErr	;X->track error message
	jsr	dispMsg		;display the message

* dspSec - display the sector number with an error

dspSec	ldx	#errSec		;X->where to put ASCII sector number
	ldaa	secNum		;A=sector with error
	jsr	bin2dec	

	ldx	#errSec		;X->completed sector error message
	jmp	dispMsg		;display the message and return	

*--------------------------------------------------------------
* bin2dec - Binary byte in A to 2 ASCII digits at (X)
*--------------------------------------------------------------
bin2dec	ldab	#' '		;assume zero supressed
	suba	#10		;value less than 10?
	bcs	do1s		;yes, leading blank
	ldab	#'1'		;have one ten already

loop10	suba	#10		;count 10s
	bcs	do1s		;done with 10s, do 1s
	incb
	bra	loop10

do1s	stab	0,x		;save 10's digit
	adda	#$3a		;add back 10 plus '0' ASCII offset
	staa	1,x		;save 1's digit
	rts

*-----------------------------------------------------------------------------
* sndTrks - Send the tracks buffered in trkBuf using XMODEM until the next
*    packet will go past the end of queued data (sndptr+PKTLEN > trkPtr).
*-----------------------------------------------------------------------------
sndTrks	bsr	sndPkt		;send a packet

	ldaa	sndPtr+1	;compute sndPtr+PKTLEN
	adda	#PKTLEN
	staa	saveX+1

	ldaa	sndPtr		;MSB
	adca	#0
	staa	saveX

	ldaa	trkPtr+1	;compute trkPtr-(sndPtr+PKTLEN)
	suba	saveX+1
	ldaa	trkPtr
	sbca	saveX

	bcc	sndTrks		;next packet will not pass the end, continue

* All the packets we can send have been sent. Move any bytes left
*    over to just before the start of trkBuf. The next group of
*    xmodem packets will be transmitted starting there.

	ldx	sndPtr		;duplicate sndPtr in saveX
	stx	saveX

	ldab	trkPtr+1	;compute trkPtr-sndPtr = bytes left
	subb	sndPtr+1	;B=bytes to move (always < 128)

	ldaa	#trkBuf&$FF	;compute B bytes before trkBuf
	sba
	staa	trkPtr+1
	staa	sndPtr+1	;will also be sndPtr for next round

	ldaa	#trkBuf/256	;MSB
	sbca	#0
	staa	trkPtr		;trkPtr->trkBuf-B (bytes to move)
	staa	sndPtr		;will also be sndPtr for next round

	tstb			;any bytes to move?
	beq	mvExit		;no, exit

moveLp	ldx	saveX		;X->leftover byte to move
	ldaa	0,x
	inx
	stx	saveX

	ldx	trkPtr		;X->space before trkBuf
	staa	0,x
	inx	
	stx	trkPtr

	decb			;decrement bytes to mvoe
	bne	moveLp

mvExit	rts

*-----------------------------------------------------------------------------
* sndPkt - send an xmodem format 128 byte packet. sndPtr points to the 
*    128 byte buffer to send. On exit, sndPtr is updated to point to the
*    next 128 byte boundary.
*-----------------------------------------------------------------------------
sndPkt	ldaa	#XMTRIES	;init retry counter
	staa	xmRetry

* Send header bytes

reSend	ldaa	#SOH		;1st byte is SOH
	bsr	sndByte
	
	ldaa	blkNum		;2nd byte is the block number
	bsr	sndByte
	
	coma			;send complement of block number
	bsr	sndByte

* Send the 128 byte data portion of the packet followed by checksum

	ldx	sndPtr		;X->packet data
	ldaa	#PKTLEN		;set transfer length
	staa	byteCnt
	clr	chkSum		;init checksum

pktLoop	ldaa	0,x		;get next byte to send
	bsr	sndByte		;send and update checksum
	inx			;point to next byte
	dec	byteCnt		;decrement bytes remaining
	bne	pktLoop

	ldaa	chkSum		send the checksum
	bsr	sndByte

* All bytes sent. Wait for the response.

	stx	saveX		;save current data pointer
	bsr	rcvByte		;get the response character
	beq	sndFail		;timeout on response
	
	cmpa	#ACK		;ack received?
	bne	sndFail		;no, send failed
	
	ldx	saveX		;update sndPtr
	stx	sndPtr
	inc	blkNum		;increment block number
	rts

* sndFail - ACK not received, decrement retry count and try again.

sndFail	dec	xmRetry		;decrement retry counter
	beq	xmFail		;retries used up, failed xmodem transfer

clrRcv2	bsr	rcvByte		;wait for 1 second of clear line
	bne	clrRcv2

	bra	reSend		;re-send the packet

* xmFail - Display failure message then restart program

xmFail	ldx	#mXmdm		;xmodem failure message
	jsr	dispMsg
	
	lds	#STACK		;re-init stack pointer
	jmp	getDrv		;start over asking for drive

*-----------------------------------------------------------------------------
*  sndEot - send EOT character and wait for ACK response
*-----------------------------------------------------------------------------
sndEot	ldaa	#XMTRIES	;init retry counter
	staa	xmRetry

reEot	ldaa	#EOT		;send the EOT
	bsr	sndByte

	bsr	rcvByte		;wait for response
	beq	eotFail		;timeout

	cmpa	#ACK		;ACK received?
	bne	eotFail		;no, EOT send failed

eotExit	rts			;otherwise, we're done.

* Timeout waiting for ACK to EOT. Decrement retry counter and try again.

eotFail	dec	xmRetry		;decrement retry counter
	beq	eotExit		;just give up - xfer was probably good

clrRcv3	bsr	rcvByte		;wait for 1 second of clear line
	bne	clrRcv3

	jmp	reEot		;try again

*-----------------------------------------------------------------------------
* sndByte - send the byte in A through the serial port. Adds the byte 
*     to chkSum. XMODEM send routine assumes serOut returns with A
*     containing the character sent. Clobbers B.
*-----------------------------------------------------------------------------
sndByte	tab			;B=character to send
	addb	chkSum		;update the checksum
	stab	chkSum

	stx	ioSaveX		;preserve X
	ldx	portAdr		;X->serial port
		
sbLoop	ldab	0,x		;wait for OK to transmit
	andb	#SIOTDRE
	beq	sbLoop	

	staa	1,x		;send the character
	ldx	ioSaveX		;restore X
	rts

*-----------------------------------------------------------------------------
* rcvByte - Receive a byte from the serial port with a one second
*     timeout. If a timeout occurs, zero is returned in A and the zero 
*     flag is true. Otherwise, the character is returned in A and zero 
*     flag is false. 
*-----------------------------------------------------------------------------
rcvByte	ldx	#29412		;one second, 34 cycles per loop

rbLoop	stx	ioSaveX		;(6) preserve counter
	ldx	portAdr		;(5) X->serial port
	
	ldaa	0,x		;(4) see if a new character is present
	anda	#SIORDRF	;(2)
	bne	rbRead		;(4) new character present

	ldx	ioSaveX		;(5) restore counter
	dex			;(4) decrement timeout
	bne	rbLoop		;(4)

	rts			;timeout, exit with Z true
	
rbRead	ldaa	1,x		;return character in A
	inx			;ensure Z is false
	rts

*-----------------------------------------------------------------------------
* rcvCon - Receive a character from the console device, echo it, then
*   wait for a CR. Exits program if Ctrl-c typed. Returns with invalid
*   character (null) if BS or DEL pressed after 1st character
*   Returns character in a, clobbers b.
*-----------------------------------------------------------------------------
rcvCon	jsr	INEEE		;use SWTBUG to get character from console
	cmpa	#CTRLC		;abort requested?
	beq	pgmExit		;yes, exit
	
	cmpa	#CR		;return pressed?
	beq	rcExit		;yes, exit
	
	tab			;save character in B

* Wait for CR, then return 1st character typed

rcWtCr	jsr	INEEE		;use SWTBUG to get character from console
	cmpa	#CTRLC		;abort requested?
	beq	pgmExit		;yes
	
	cmpa	#DEL		;delete?
	beq	rcExit		;yes, return DEL character
	
	cmpa	#BS		;backspace?
	beq	rcExit		;yes, return the backspace
	
	cmpa	#CR		;return pressed?
	bne	rcWtCr		;no, keep waiting
	
	tba			;return 1st character typed in A
rcExit	rts

*------------------------------------------------------------------
* sizeRam - determine how much RAM we have for buffering tracks.
*   Sets the bufEnd variable which points to end address of 
*   the last possible track buffer + 1
*------------------------------------------------------------------
sizeRam	ldx	#trkBuf+$100	;look for end of RAM starting
	stx	saveX		;    at trkBuf+$100

ramLoop	ldaa	0,x		;A=current RAM content
	inc	0,x		;change value in RAM
	ldab	0,x		;B=new value from RAM
	staa	0,x		;restore original value
	
	cba			;did RAM change?
	beq	ramEnd		;no, so end of RAM found
	
	inc	saveX		;increment to next page
	ldx	saveX		;X->next page in RAM	
	bpl	ramLoop		;loop until $80xx reached

* ramEnd - end of RAM found. Now determine the end address + 1 of the
*   last track buffer that will fit in RAM. Store in bufEnd

ramEnd	ldab	saveX		;B=1st page not RAM
	ldx	#trkBuf		;start saveX at trkBuf
	stx	saveX

bfEndLp	ldaa	saveX+1		;increment saveX by TRKLEN
	staa	bufEnd+1	;bufEnd has previous value of saveX
	adda	trkLen+1
	staa	saveX+1

	ldaa	saveX		;MSB
	staa	bufEnd		;bufEnd has previous value of saveX
	adca	trkLen
	staa	saveX

	cba			;saveX MSB < RAM end MSB?
	bcs	bfEndLp		;yes, still more room,

	rts			;exit with bufEnd properly set

*--------------------------------------------------------------
* pgmExit - Exit program to SWTBUG or to FLEX
*--------------------------------------------------------------
pgmExit	ldaa	drvNum		;drive selected yet?
	bmi	exitJmp		;no, just exit
	jsr	dSelect		;else restore to track 0
	jsr	dRestor

exitJmp	ldx	WFLEX		;is FLEX in place?
	cpx	#$7EAD
	bne	swtExit		;no, exit to SWTBUG
	jmp	WFLEX		;else, exit to FLEX

swtExit	jmp	CONTRL		;exit to SWTBUG

*---------------------------------------------------------------------
* Disk I/O subroutines
*---------------------------------------------------------------------
DRVREG	equ	$8014 		;drive select register
CMDREG	equ	$8018 		;1771 command register
TRKREG	equ	$8019 		;1771 track register
SECREG	equ	$801A 		;1771 sector register
DATAREG	equ	$801B 		;1771 data register

* Select register equates

rMOTOFF	equ	$80		;1=motor off
rSIDE1	equ	$40		;1=side 1 (top)

* 1771 Commands

cREAD	equ	$88		;read, head assumed settled
cWRITE	equ	$A8		;write, head assumed settled
cWAITHD	equ	$04		;bit to wait for head settle

cRESTOR	equ	$0B		;restore to track 0, load head
cSEEK	equ	$1B		;seek to track, load head
cWRTRK	equ	$F4		;write track (for formatting disk)
cRESET	equ	$D0		;reset 1771 to idle

* 1771 Status Masks

sNOTRDY	equ	$80		;1=drive not ready
sWRPROT	equ	$40		;1=write protected
sHEADLD	equ	$20		;1=head is loaded
sSKERR	equ	$10		;1=seek error
sCRCERR	equ	$08		;1=CRC error
sTRACK0	equ	$04		;1=on track zero
sINDEX	equ	$02		;1=index detected
sBUSY	equ	$01		;1=1771 busy

sSECERR	equ	$10		;1=sector not found
sWRTFLT	equ	$20		;1=write fault
sDLOST	equ	$04		;1=lost data
sDRQ	equ	$02		;1=data reques

* Error masks for status of a seek, read, write

eSEEK	equ	sSKERR+sNOTRDY
eREAD	equ	sSECERR+sCRCERR+sDLOST+sNOTRDY
eVERIFY	equ	sSECERR+sCRCERR+sNOTRDY
eWRITE	equ	eREAD+sWRPROT+sWRTFLT

*--------------------------------------------------------------
* dRead
*    Read sector specified by secNum into buffer at secPtr
*--------------------------------------------------------------
dRead	ldx	secPtr		;X->sector buffer

	ldaa	secNum		;give sector number to the 1771
	staa	SECREG

	ldaa	#cREAD		;1771 read command
	oraa	fWaitHd		;include possible wait head bit
	clr	fWaitHd		;off for subsequent reads
	sei			;disable interrupts
	staa	CMDREG		;issue command to 1771
	jsr	dCmdDly		;ensure command processing time

drLoop	ldaa	CMDREG		;A=1771 status
	bita	#sBUSY		;read still in progress>?
	beq	drDone		;no, read has finished

	bita	#sDRQ		;data ready flag set?
	beq	drLoop		;no, keep waiting
	
	ldaa	DATAREG		;get the next byte
	staa	0,x		;save in buffer
	inx
	bra	drLoop

drDone	cli			;enable interrupts
	anda	#eREAD		;test and return read error bits
	rts
	
*--------------------------------------------------------------
* dSeek
*    Seek to track trkNum. Head is loaded and the wait head
*    bit (E) is set in the fWaitHd flag. If trkNum is track
*    zero (will be the 1st seek in this program), then the 
*    restore command is used to establish the track number
*    in the 1771. The returned status includes the write 
*    protect bit.
*--------------------------------------------------------------
dSeek	ldaa	drvNum		;always go to side 0 after seek
	staa	DRVREG

	ldaa	#cWAITHD	;next I/O must wait on head status
	staa	fWaitHd
	
	ldaa	trkNum		;A=desired track
	beq	dRestor		;if track 0 use restore instead
	
	staa	DATAREG		;give track to the 1771
	ldaa	#cSEEK		;A=seek to track and load head
	bsr	dCmdWt		;issue command and wait for it
	anda	#eSEEK
	rts
	
*--------------------------------------------------------------
* dRestore
*    Issue and wait on restore to track zero. The returned
*    status includes the write protect bit.
*--------------------------------------------------------------
dRestor	ldaa	#cRESTOR	;A=restore to track 0 and load head
	bsr	dCmdWt		;issue command and wait for it
	anda	#eSEEK		;status mask includes write protect
	rts
	
*--------------------------------------------------------------
* dCmdWt
*    Write sector specified by secNum from secPtr
*--------------------------------------------------------------
dCmdWt	staa	CMDREG		;issue command from A
	bsr	dCmdDly		;ensure command processing time
	
dWtCmd	ldaa	CMDREG		;poll for busy bit to go false
	bita	#sBUSY
	bne	dWtCmd	

	rts
	
*--------------------------------------------------------------
* dSelect
*    Select the drive in drvNum. Wait 1s for motor startup.
*--------------------------------------------------------------
dSelect	ldaa	CMDREG		;start motor
	ldaa	drvNum		;select target drive
	staa	DRVREG
	
	ldx	#0		;wait 1s for motor start up
motrDly	inx			;(4)
	dex			;(4)
	dex			;(4)
	bne	motrDly		;(4)
	
	rts

*--------------------------------------------------------------
* dMotOff
*    Wait for head to unload, then turn off drive motor
*--------------------------------------------------------------
dMotOff	ldx	#$C000		;delay 3/4s to give time for 
	jsr	motrDly		;  head unload (deselects drive)

	ldaa	drvNum		;turn off drive motor
	oraa	#rMOTOFF
	staa	DRVREG
	rts

*--------------------------------------------------------------
* dSide1
*    Select side 1 (top) of disk
*--------------------------------------------------------------
dSide1	ldaa	drvNum		;set side 1
	oraa	#rSIDE1
	staa	DRVREG
	rts

*--------------------------------------------------------------
* dCmdDly
*    Wait 76us (including the calling JSR) to ensure a 1771
*    command is accepted and the status register can be read
*--------------------------------------------------------------
dCmdDly	ldaa	#10		;10*6=60us in loop

dlyLoop	deca
	bne	dlyLoop
	rts
	
*--------------------------------------------------------------
* dSecAdr
*    Convert the sector index (secIdx) into the sector number
*    on the disk (secNum) and a pointer into the track buffer
*    (secPtr)
*--------------------------------------------------------------
dSecAdr	ldx	#skewTbl	;X->side 0 sector translate table 
	ldaa	secIdx		;index into skewTbl by secIdx
	staa	*+4		;modify index offset in next inst
	ldaa	0,x		;A=sector to look for on disk
	staa	secNum		;save in secNum
	
	tst	trkNum		;on track 0?
	bne	dsNotT0		;no
	cmpa	#5		;sector 1-5?
	bhi	dsNotT0		;no
	tst	side		;on bottom?
	bne	dsNotT0		;no
	dec	secNum		;else sector 1-5 to 0-4

dsNotT0	tst	side		;on top side?
	beq	dsMp128		;no, go multiply by 128 
	adda	#NUMSEC		;else, use 19-36 for top indexing

dsMp128	deca			;form (secNum-1)*128 in A:B
	clrb
	lsra
	rorb

	addb	trkPtr+1	;compute LSB of sector address
	stab	secPtr+1
	adca	trkPtr		;compute MSB of sector address
	staa	secPtr

	inc	secIdx		;increment to the next physical sector
	rts	

*---------------------------------------------------------------------
* Message constants
*---------------------------------------------------------------------
mWelcom	fcb	CR,LF,LF
	fcc	'===== Floppy to PC Image Transfer, Version 1.0 ====='
	fcb	CR,LF,LF
	fcc	'Sends an image of a DSSD 5.25" SDOS disk through an'
	fcb	CR,LF
	fcc	'MP-S serial port using the XMODEM protocol.'
	fcb	EOT

mDrive	fcb	CR,LF,LF
	fcc	'Insert and specify source drive (0-3) or X to exit: '
	fcb	EOT

mPort	fcb	CR,LF,LF
	fcc	'Specify the port to use for file transfer'
	fcb	CR,LF
	fcc	'  0) MP-S at $8000'
	fcb	CR,LF
	fcc	'  1) MP-S at $8004 (console port)'
	fcb	CR,LF
	fcc	'  2) MP-S at $8008'
	fcb	CR,LF
	fcc	'  3) MP-S at $800C'
	fcb	CR,LF
	fcc	'  4) MP-S at $8010'
	fcb	CR,LF
	fcc	'Enter port or X to exit: '
	fcb	EOT

mStart	fcb	CR,LF,LF
	fcc	'Start XMODEM receive operation on the PC now...'
	fcb	EOT

mTrkErr	fcb	CR,LF
	fcc	'Track '
errTrk	fcc	'xx failed sectors: '
	fcb	EOT
errSec	fcc	'xx '
	fcb	EOT

mSkErr	fcb	CR,LF,LF
	fcc	'Restore or seek error'
	fcb	CR,LF,EOT

mDone	fcb	CR,LF,LF
	fcc	'Transfer complete.'
	fcb	CR,LF,EOT

mXmdm	fcb	CR,LF,LF
	fcc	'XMODEM communication failure'
	fcb	CR,LF,EOT

*---------------------------------------------------------------------
*  Data area
*---------------------------------------------------------------------

* skewTbl - Returns logical sector numbers in the physical order
*    they appear on the disk. This allows reading the track in one
*    revolution.

skewTbl	fcb	1,7,13,2,8,14,3,9,15,4,10,16,5,11,17,6,12,18

* disk variables

numTrk	rmb	1		;number of tracks on the disk
trkLen	rmb	2		;length of a track

drvNum	rmb	1		;drive number
trkNum	rmb	1		;track number to read
secIdx	rmb	1		;ordinal sector on disk
secNum	rmb	1		;sector number in address mark
secCnt	rmb	1		;count of sectors to read
side	rmb	1		;1=top side (must be 1)

trkPtr	rmb	2		;pointer into trkBuf at track boundaries
secPtr	rmb	2		;pointer to current sector in trkBuf
bufEnd	rmb	2		;end address + 1 of last track spot in RAM

fDouble	rmb	1		;1=double sided 
fWaitHd	rmb	1		;wait for head load bit
fTrkErr	rmb	1		;track read error flag

rdRtry	rmb	1		;read retry counter
rtStep	rmb	1		;retry step

* xmodem variables

portAdr	rmb	2		;serial port address
xmRetry	rmb	1		;xmodem retry counter
blkNum	rmb	1		;current xmodem block number
sndPtr	rmb	2		;pointer for start of XMODEM send
chkSum	rmb	1		;packet checksum
byteCnt	rmb	1		;count of bytes to send

* misc variables

saveX	rmb	2		;generic save location for X
ioSaveX	rmb	2		;save X in I/O subroutines

* track buffer runs from here to the end of memory

	rmb	PKTLEN		;space for pre-pended unsent data
trkBuf	equ	*

	org	$A048		;start address for "G" command
	fdb	init

	end

