;*****************************************************************************
;
;  Flop2PC - Transfer floppy image to PC over a serial port
;		(Altair FDC+ with IMSAI SIO-2 serial port)
;
;	This program transmits an image of an Altair 8" floppy to a PC
;       through either port on an IMSAI SIO-2 serial port using the XMODEM
;       protocol. The disk is copied in raw format (137 bytes per sector,
;	32 sectors per track, 77 tracks).
;
;	This program runs standalone at 0x100 or under CP/M. Console
;	I/O is done directly through the first port on an 88-2SIO.
;
;	Written by Mike Douglas
;
;	Rev	 Date	    Desc
;	1.0	03/29/21    Original
;
;  	1.1	04/05/21    Move SIO-2 port base to 10h instead of 00h to
;			    avoid conflict with the Altair FDC.
;
;*****************************************************************************

; Disk information equates

NUMTRK	equ	77		;number of tracks
NUMSEC	equ	32		;number of sectors per track
SECLEN	equ	137		;sector length (as transmitted)
TRKLEN	equ	(NUMSEC*SECLEN)	;length of track in bytes

DATATRK	equ	6		;1st data format track
SSECLEN	equ	133		;Altair system sector length
DSECLEN	equ	136		;Altair data sector length

MINDRV	equ	0		;first drive number
MAXDRV	equ	3		;max drive number 

RDTRIES	equ	6		;read tries (must be <= 8)

; Monitor, CP/M boot entry points

MONITOR	set	init		;no monitor, just start over
CPMBOOT	set	0		;CP/M warm boot jump vector

; XMODEM equates

PKTLEN	equ	128		;128 byte xmodem packet length
SOH	equ	001h
EOT	equ	004h
ACK	equ	006h
NAK	equ	015h
EOF	equ	01Ah		;ctrl-z character
XMTRIES	equ	10		;number of xmodem retries

; CP/M Equates

WBOOT	equ	0		;warm boot jump vector
BDOS	equ	5		;BDOS jump vector

; Misc equates

CR	equ	13		;ascii for carriage return
LF	equ	10		;ascii for line feed
DEL	equ	7Fh		;ascii DEL
BS	equ	08h		;ascii backspace
CTRLC	equ	03		;ascii for control-c
JMPINST	equ	0C3h		;jump instruction opcode
CPUSPD	equ	2000		;CPU speed in khz for delayMs

	org	0100h		;load address
;-----------------------------------------------------------------------------
;   Initialize for transfer
;-----------------------------------------------------------------------------
init	di			;disable interrupts
	lxi	sp,ourStk	;initialize stack pointer

	call	chkCpm		;set flag for CP/M or not
	call	sizeRam		;determine amount of RAM available
	call	serInit		;initialize the 88-2SIO ports

	mvi	a,MINDRV	;a=default drive
	sta	drvNum		;need for pgmExit

	mvi	a,0ffh		;init disk driver selected disk
	sta	curDrv		;  to "none"

;  Display welcome message, then get the drive number and port number
;    to use for the transfer from the operator.

	lxi	h,mWelcom	;display welcome message
	call	dispMsg

; getDrv - get drive number or letter from the user

getDrv	call	dDeslct		;deselect possibly selected drive

	lxi	h,mDrive	;display drive number prompt
	call	dispMsg
	call	rcvCon		;get byte from the console

	ori	20h		;upper case letters to lower case
	cpi	'x'		;exit requested?
	jz	pgmExit		;yes

	lxi	h,baseDrv	;ASCII 1st drive - baseDrv = 0
	sub	m
	cpi	MAXDRV-MINDRV+1	;validate >= min and <= max
	jnc	getDrv		;invalid, prompt again

	adi	MINDRV		;restore offset of 1st drive (if any)
	sta	drvNum		;save the drive number to use

	call	dRestor		;restore to track 0 (selects drive)

; getPort - get serial port to use from the operator

getPort	lxi	h,mPort		;display transfer port prompt
	call	dispMsg
	call	rcvCon		;get byte from the console

	ori	20h		;upper to lower case, nums not affected
	cpi	'x'		;exit requested?
	jz	pgmExit		;yes

	sui	'1'		;remove ASCII offset
	cpi	2		;validate ASCII 1-2
	jnc	getPort		;invalid, prompt again

	sta	xfrPort		;save the pseudo port to use
	
	ora	a		;console port used for transfer?
	jnz	notCon		;no

; 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 or 'C' (CRC protocol) from the PC

notCon	lxi	h,mStart	;display start file transfer prompt
	call	dispMsg

clrRcv1	call	rcvByte		;loop until input clear for 1 second
	jnz	clrRcv1

	xra	a		;set CRC flag to false (checksum mode)
	sta	crcFlag

waitNak	call	chkQuit		;give user chance to abort
	call	rcvByte		;wait for a character
	cpi	NAK
	jz	haveNak		;have a NAK, use checksum protocol

	cpi	'C'		;CRC protocol requested?
	jnz	waitNak		;no
	sta	crcFlag		;set crc flag non-zero = CRC mode

;  NAK or 'C' received meaning the XMODEM receive has started. Initialize
;     for the transfer.

haveNak	mvi	a,0FFh		;track position on drive not known
	sta	curTrk

	xra	a
	sta	trkNum		;starting track number is zero
	sta	haveErr		;no errors have occured yet

	inr	a		;init xmodem block number to one
	sta	blkNum

	lxi	h,trkBuf	;trkBuf is initial starting point for
	shld	sndPtr		;   XMODEM send

	lxi	h,errTbl	;init error table pointer
	shld	pErrTbl

;-----------------------------------------------------------------------------
;  mainLp - Loop through all tracks on the disk buffering as many tracks
;    as will fit in RAM before sending via xmodem, then repeat.
;-----------------------------------------------------------------------------
mainLp	lxi	h,trkBuf	;disk buffering always start at trkBuf
	shld	trkPtr
	call	dLoad		;load drive head

bufLoop	call	readTrk		;read into trkBuf

	lda	trkNum		;increment track number (preserve HL)
	inr	a
	sta	trkNum	
	cpi	NUMTRK		;done all tracks on the disk?
	jz	sendBuf		;yes, go send the buffered tracks

	xchg			;de=current track pointer
	lhld	bufEnd		;hl=end of buffering space
	call	cmpDeHl		;compare current-end
	jc	bufLoop		;still room, keep going	

; Track buffer is full or all tracks have been read. Send the buffered
;    tracks via xmodem.

sendBuf	call	dUnload		;unload the head
	call	sndTrks		;send the buffered tracks via xmodem

	lda	trkNum
	cpi	NUMTRK		;done all tracks?
	jnz	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.

	mov	a,l		;HL->next packet to send, A=LSB of ptr
	cpi	trkBuf AND 0FFh	;pointing to LSB of address of trkBuf?
	jz	noExtra		;same, no extra bytes to send

	call	sndPkt		;otherwise, send one last packet

; Send EOT and wait for response. Then display the success message, list
;    and read errors that, occurred and start the program over.

noExtra	call	sndEot		;send and get response for EOT

	mvi	l,0		;seek to track 0 (don't use dRestor)
	call	dSeek

	lxi	h,mDone		;print the all done message
	call	dispMsg

	lda	haveErr		;any errors occur?
	ora	a
	jz	getDrv		;no, start over

	call	dspErrs		;else, display all read errors
	jmp	getDrv		;start over
	
;----------------------------------------------------------------------------
; 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. This
;   saved value is also returned in HL.
;----------------------------------------------------------------------------
readTrk	call	chkQuit		;check for abort
	mvi	a,2		;init restore/seek try counter
	sta	skRetry	
	call	initTrk		;init all sectors to "bad" (not read)

rtRtry	lxi	h,trkNum	;HL->desired track number
	mov	l,m		;L contains requested track
	call	dSeek		;seek to trkNum

	mvi	d,RDTRIES*NUMSEC   ;D=max sector reads = RDTRIES revolutions
	mvi	e,NUMSEC	   ;E=sectors remaining to read

; Read sector loop - All sectors are originally marked "bad" (unread) and
;    a sector counter in E is started at NUMSEC (a full track of sectors).
;    Sectors are read until sector counter E reaches zero or the total
;    reads counter in D reaches zero (RDTRIES revolutions). The first time
;    the total reads counter reaches zero, a step-out, step-in is done.
;    The second time it reaches zero, a step-in, step-out is done. The
;    third time, the bad sectors remain marked as bad in the track buffer.

rdLoop	call	dNxtSec		;wait for next (any) sector sync
	jz	rdNext		;sector already read, skip it

	push	h		;save sector status address
	lhld	trkPtr		;HL->start of current track buffer
	dad	b		;BC has offset of sector within track
	call	dRead		;read the sector
	pop	h		;(10) HL->sector status within secTbl
	jnz	rdNext		;(10) read error

	xra	a		;(4) set sector flag to zero (good sector)
	mov	m,a		;(7) 

	dcr	e		;(5) decrement sectors left to fill
	jz	rtExit		;(10) all sectors read without error

rdNext	dcr	d		;(5) decrement total sectors counter
	jnz	rdLoop		;(10) go read next sector

	lxi	h,skRetry	;HL->seek retry counter
	dcr	m		;decrement the counter
	jm	rtExit		;retry count negative, all done

	lda	trkNum		;A=current track number
	jz	rtInOut		;retry count zero, step in then back out
	
	ora	a		;on track zero?
	jz	rtRtry		;yes, don't step out
	dcr	a		;step out one track
	jmp	rtBump		;do the track bump

rtInOut	cpi	NUMTRK-1	;already at maximum track number?
	jz	rtRtry		;yes, don't step in
	inr	a		;step in one track

rtBump	mov	l,a		;track specified in L
	call	dSeek		;do the step
	jmp	rtRtry		;try again

; rtExit - Track has been read. Pack sector status table for the track into
;     errTbl and then compute the address for the next track in trkPtr

rtExit	call	packErr		;pack secTbl status into errTbl

	lhld	trkPtr		;HL=current track pointer
	lxi	d,TRKLEN	;DE=bytes in a track
	dad	d		;HL=start of next track in trkBuf
	shld	trkPtr

	ret

;----------------------------------------------------------------------------
; initTrk - Set the status byte in secTbl for each sector to "bad" to
;    indicate none of the sectors have been read.
;
; On Entry
;
; On Exit
;    All sectors in trkBuf marked "bad" (unread)
;    Clobbers B,C,H,L
;----------------------------------------------------------------------------
initTrk	lxi	b,256+NUMSEC	;B=1, C=number of sectors
	lxi	h,secTbl	;HL->sector status table

itLoop	mov	m,b		;mark sector as bad (not read)
	inx	h		;move to next entry
	dcr	c		;repeat for all sectors
	jnz	itLoop
	
	ret

;----------------------------------------------------------------------------
; packErr- Pack the 32 sector status bytes from secTbl into four bytes
;    of bit flags in errTbl
;
; On Entry
;    secTbl has 32 sector status bytes for the current track
;    pErrTbl->next four bytes to use in errTbl
;
; On Exit
;    Next 32 bits in errTbl updated with secTbl flags
;    pErrTbl incremented by four
;    Clobbers All
;----------------------------------------------------------------------------
packErr	lxi	d,secTbl	;DE->sector status table
	lhld	pErrTbl		;HL->next spot in the packed error table
	lxi	b,256+NUMSEC	;B=1 (shift register), C=number of sectors

peLoop	ldax	d		;A=sector status byte
	rrc			;set or clear carry based on status

	mov	a,b		;A=shift register value
	ral			;move flag into LSB
	mov	b,a		;B=updated shift register
	jnc	peNext		;jump if byte not done yet

	mov	m,a		;store the packed flag byte
	inx	h		;move to next errTbl location
	mvi	b,1		;init shift register
	ora	a		;any errors found?
	jz	peNext		;no

	sta	haveErr		;set global error flag non-zero

peNext	inx	d		;DE->next sector status byte
	dcr	c		;decrement sector count
	jnz	peLoop

	shld	pErrTbl		;save updated error table pointer
	ret

;-----------------------------------------------------------------------------
; sndTrks - send the tracks buffered in trkBuf via xmodem. trkPtr points
;    to the end+1 of the data to send
;-----------------------------------------------------------------------------
sndTrks	lhld	trkPtr		;HL=end of buffered data + 1
	xchg			;DE=end of buffered data + 1
	lhld	sndPtr		;HL=start tranmission address
	
sndLoop	push	d		;save end pointer
	call	sndPkt		;send a packet
	pop	d		;DE=end pointer

; At this point, HL->start of next packet and DE->last byte read from disk+1.
;    If the next XMODEM packet will go past the end of the disk data,
;    we don't want to send it yet.

	lxi	b,PKTLEN	;BC=length of XMODEM packet
	dad	b		;HL=address at end of NEXT packet + 1
	call	cmpDeHl		;compare disk end - end of next packet
	jc	sndDone		;next packet will pass the end, stop
	
	lxi	b,-PKTLEN	;restore HL
	dad	b
	jmp	sndLoop		;continue sending

; sndDone - 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.

sndDone	call	subDeHl		;HL=DE-HL = left over byte count - PKTLEN
	mov	a,l		;A=left over count - PKTLEN
	adi	PKTLEN		;A=left over byte count
	mov	b,a		;B=count of bytes to move
	lxi	h,trkBuf	;HL->start of trkBuf
	jz	mvDone		;no bytes to move, we're done
	
; Copy the leftover data backwards to just before the track buffer. The
;   next XMODEM send sequence will start at the beginning of this copied
;   data instead of at the start of trkBuf.

moveLp	dcx	h		;HL->memory just before trkBuf
	dcx	d		;DE->unsent bytes from end of trkBuf
	ldax	d		;move from end of trkBuf to before trkBuf
	mov	m,a	
	dcr	b
	jnz	moveLp
	
mvDone	shld	sndPtr		;save address from which to send next time
	ret

;-----------------------------------------------------------------------------
; sndPkt - send an xmodem format 128 byte packet. HL points to the 128 byte
;    buffer to send. On exit, HL points to the next 128 byte boundary.
;-----------------------------------------------------------------------------
sndPkt	mvi	a,XMTRIES	;init retry counter
	sta	xmRetry

; First, send header bytes

reSend	call	chkQuit		;check for ctrl-c

	push	h		;save hl for possible re-send
	mvi	a,SOH		;1st byte is SOH
	call	sndByte
	
	lda	blkNum		;2nd byte is the block number
	call	sndByte
	
	cma			;2nd complement of block number
	call	sndByte

; Init checksum and CRC and packet length for transmission of data portion

	xra	a		;init CRC
	sta	crc16
	sta	crc16+1
	lxi	d,PKTLEN*256	;D=byte counter, E=0 (checksum)

; Loop sending the data bytes and updating checksum and CRC

pktLoop	mov	a,m
	call	sndByte		;send and update checksum in E
	call	calCrc		;update the CRC
	inx	h		;point to next byte
	dcr	d		;decrement bytes remaining
	jnz	pktLoop

; Send checksum or CRC based on crcFlag

	lda	crcFlag		;CRC or checksum?
	ora	a
	jz	sndCsum		;flag clear = checksum
	
	lda	crc16+1		;A=high byte of CRC
	call	sndByte		;send it
	lda	crc16		;a=low byte of CRC
	jmp	sndSkip		;skip next instruction	
	
sndCsum	mov	a,e		;send the checksum byte

sndSkip	call	sndByte

;  All bytes sent. Wait for the response.

	call	rcvByte		;get the response character
	jz	sndFail		;timeout on response
	
	cpi	ACK		;ACK received?
	jnz	sndFail		;no, send failed
	
	lda	blkNum		;increment block number
	inr	a
	sta	blkNum
	
	pop	b		;remove pushed HL, but don't clobber HL
	ret

; sndFail - ACK not received, decrement retry and try again.

sndFail	lxi	h,xmRetry	;point to retry counter
	dcr	m
	jz	xmFail		;retries used up, failed xmodem transfer

; If we've had 3 NAKs on the 1st packet and CRC is selected, assume
;   we took so long to send the 1st packet due to disk retries that
;   the receiver has since timed out and switched to checksum

	lda	crcFlag		;are we in CRC mode
	jz	clrRcv2		;no, ignore the rest of this
	
	lda	blkNum		;on block 1?
	dcr	a
	jnz	clrRcv2		;no, go on
	
	lda	xmRetry		;failed three times in a row on block 1?
	sui	XMTRIES-3
	jnz	clrRcv2		;no
	
	sta	crcFlag		;clear crcFlag to force checksum

;  clrRcv2 - wait for one second of line clear time and send packet again.

clrRcv2	call	rcvByte		;wait for 1 second of clear line
	jnz	clrRcv2
	
	pop	h		;restore pointer to the packet
	jmp	reSend		;re-send the packet

;  xmFail - Display failure message then restart program

xmFail	lxi	h,mXmdm		;xmodem failure message
	call	dispMsg
	lxi	sp,ourStk	;initialize stack pointer
	jmp	getDrv		;start over asking for drive

;-----------------------------------------------------------------------------
; calCrc - update the 16-bit CRC with one more byte. 
;    (Copied from M. Eberhard)
; On Entry:
;   a has the new byte
;   crc16 is current except this byte
; On Exit:
;   crc16 has been updated
;   Trashes a,bc
;-----------------------------------------------------------------------------
calCrc	push	d
	push	h
	
	lhld	crc16		;get CRC so far
	xra	h		;XOR into CRC top byte
	mov	h,a
	
	lxi	b,1021h		;BC=CRC16 polynomial
	mvi	d,8		;prepare to rotate 8 bits

; do 8 bit shift/divide by CRC polynomial

cRotLp	dad	h		;16-bit shift
	jnc	cClr		;skip if bit 15 was 0
	
	mov	a,h		;CRC=CRC xor 1021H
	xra	b
	mov	h,a
	mov	a,l
	xra	c
	mov	l,a
	
cClr	dcr	d
	jnz	cRotLp		;rotate 8 times

; save the updated CRC and exit

	shld	crc16		;save updated CRC
	
	pop	h
	pop	d
	ret

;-----------------------------------------------------------------------------
;  sndEot - send EOT character and wait for ACK response
;-----------------------------------------------------------------------------
sndEot	mvi	a,XMTRIES	;init retry counter
	sta	xmRetry
	
reEot	mvi	a,EOT		;send the EOT
	call	sndByte
	call	rcvByte		;wait for response
	jz	eotFail		;timeout
	
	cpi	ACK		;ACK received?
	jnz	eotFail		;no, EOT send failed
	ret			;otherwise, we're done.

; timeout waiting for ACK to EOT. Decrement retry counter and try again

eotFail	lxi	h,xmRetry	;point to retry counter
	dcr	m
	rz			;just give up - xfer was probably good
	
clrRcv3	call	rcvByte		;wait for 1 second of clear line
	jnz	clrRcv3
	
	jmp	reEot		;try again

;-----------------------------------------------------------------------------
; dispMsg - display the null-terminated message passed in HL on the
;    console device. Clobbers B, HL
;-----------------------------------------------------------------------------
dispMsg	mov	a,m		;get the next message byte
	ora	a		;null terminates
	rz
	
	mov	b,a		;conOut wants character in B
	call	conOut	
	inx	h		;move to next byte
	jmp	dispMsg

;-----------------------------------------------------------------------------
; 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	call	conIn		;check for input
	jz	rcvCon		;nothing
	
	ani	7Fh
	cpi	CTRLC		;abort requested?
	jz	pgmExit		;yes
	
	cpi	CR		;return pressed?
	rz			;yes, don't echo it
	
	mov	b,a		;conOut needs character in b
	call	conOut		;echo it

; Wait for CR, then return 1st character typed

rcvCr	call	conIn		;check for input
	jz	rcvCr		;nothing
	
	ani	7Fh		;clear MSBit
	cpi	CTRLC		;abort requested?
	jz	pgmExit		;yes
	
	cpi	DEL		;delete?
	rz			;yes, return DEL character
	
	cpi	BS		;backspace?
	rz			;yes, return BS character
	
	cpi	CR		;return pressed?
	jnz	rcvCr		;no, keep waiting
	
	mov	a,b		;return 1st character typed
	ret

;-----------------------------------------------------------------------------
; sndByte - send the byte in a through the specified transfer port. 
;     Adds the byte to register E for checksum. Clobbers B.
;     XMODEM send routine assumes serOut returns with a containing
;     the character sent.
;-----------------------------------------------------------------------------
sndByte	mov	b,a		;b=byte to transmit
	add	e		;update checksum
	mov	e,a		;e=updated checksum
	
	lda	xfrPort		;a=port to use for transfer
	jmp	serOut		;send the character

;-----------------------------------------------------------------------------
; rcvByte - Receive a byte from specified transfer 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 (could be
;     zero) and zero flag is false. ONESEC must be set based on processor
;     speed and the number of cycles in the serIn call + 59. 
;     Clobbers A, B and C.
;-----------------------------------------------------------------------------
rcvByte	lxi	b,ONESEC	;BC=cycles through this loop for 1s

rcvWait	lda	xfrPort		;(13) A=port to use for transfer
	call	serIn		;(17+cycles in serIn)look for a byte
	rnz			;(5)byte received
	
	dcx	b		;(5)otherwise, decrement timer
	mov	a,b		;(5)one second expire?
	ora	c		;(4)
	jnz	rcvWait		;(10)no, keep waiting
	
	ret			;return with timeout (zero true and in A)

;-----------------------------------------------------------------------------
; dspErrs - Display all the sector errors found on the entire disk by
;    scanning through errTbl.
;-----------------------------------------------------------------------------
dspErrs	lxi	h,mFails	;"Failure list" message
	call	dispmsg

	xra	a		;init track number to zero
	sta	trkNum
	lxi	h,errTbl	;HL->packed error table
	mov	b,m		;B=first byte of flags

deTrack	lxi	d,1		;D=0=sector number, e=track error flag

deLoop	mov	a,b		;A=flag shift register
	rlc			;test MSBbit
	mov	b,a		;B=updated shift register
	cc	secErr		;display error if flag set

	inr	d		;increment sector register
	mov	a,d		;A=sector number
	ani	07h		;time for new byte of 8 flags?
	jnz	deLoop		;no

	inx	h		;move to next byte in errTbl
	mov	b,m		;B=next byte of 8 flags

	mov	a,d		;A=track number
	cpi	NUMSEC		;track finished?
	jnz	deLoop		;not yet, continue

	lda	trkNum		;increment track number
	inr	a
	sta	trkNum

	cpi	NUMTRK		;all done?
	jnz	deTrack		;no, continue

	ret

; secErr - Display the number of the failed sector

secErr	push	h		;save the table pointer
	push	b		;save shift register

	dcr	e		;E goes to zero for first error on track
	jnz	secOnly		;not first error, just display sector

	lxi	h,errTrk	;display "Track" message
	lda	trkNum		;A=track with error on it
	call	bin2dec		;track to ascii
	lxi	h,mTrkErr	;display the track error message
	call	dispMsg

secOnly	lxi	h,errSec	;HL->where to put ascii sector
	mov	a,d		;A=sector number
	call	bin2dec
	call	dispMsg		;display the error

	pop	b		;restore BC
	pop	h		;restore HL
	ret

;--------------------------------------------------------------
; bin2dec - Binary byte in A to 2 ASCII digits at (HL)
;   HL is preserved
;--------------------------------------------------------------
bin2dec	mvi	m,' '		;assume zero supression
	sui	10		;value less than 10?
	jc	do1s		;yes, leading blank
	
	mvi	m,'1'		;have one ten already

loop10	sui	10		;count 10s
	jc	do1s		;done with 10s, do 1s
	
	inr	m
	jmp	loop10

do1s	adi	'0'+10		;form ASCII 1s digit
	inx	h		;move to 1s position
	mov	m,a
	
	dcx	h		;restore HL	
	ret

;--------------------------------------------------------------
; chkQuit - check for the user to request abort (ctrl-c). If
;    a character is present on the console port, read it and
;    see if ctrl-c. Clobbers A
;--------------------------------------------------------------
chkQuit	call	conIn		;check for console input
	rz
	
	ani	7Fh		;strip parity bit
	cpi	CTRLC		;abort requested?
	rnz			;no

; Ctrl-C typed while program is running. Return to drive prompt.

	lxi	sp,ourStk	;initialize stack pointer
	jmp	getDrv		;start over asking for drive num

;--------------------------------------------------------------
; pgmExit - Exit to CP/M or to the monitor ROM based on the
;    CP/M flag
;--------------------------------------------------------------
pgmExit	lda	cpmFlag		;running under CP/M?
	ora	a
	jnz	cpmExit		;yes

; Exit to ROM monitor

	lxi	h,mExit		;display "exiting" message
	call	dispMsg
	jmp	MONITOR		;jump to ROM monitor entry point

; CP/M exit. If boot drive was used, prompt user to insert CP/M disk

cpmExit	lda	drvNum		;boot drive used?
	sui	MINDRV
	jnz	noDisk		;not 1, disk prompt not needed
	
	lxi	h,mCpm		;display "insert cp/m disk"	
	call	dispMsg
	call	rcvCon		;wait for a character
	
noDisk	lxi	h,mExit		;display "exiting" message
	call	dispMsg
	jmp	CPMBOOT		;reboot CP/M

;--------------------------------------------------------------
; chkCpm - check if running under CP/M. CP/M flag is set true
;   (non-zero) if yes, cleared otherwise.
;--------------------------------------------------------------
; First, initialize entries for stand-alone

chkCpm	xra	a
	sta	cpmFlag		;clear CP/M flag
	
	mvi	a,MINDRV+'0'	;ASCII for minimum drive number
	sta	mDrvMin		;store in the drive prompt message
	sta	baseDrv		;ASCII 1st drive - baseDrv = 0
	mvi	a,MAXDRV+'0'	;ASCII for max drive number
	sta	mDrvMax

; Determine if we're under CP/M or standalone. CP/M is assumed if
;   a jump instruction is present at the CP/M warm start location (0)
;   and five more jumps (e.g., a jump table) is present at the
;   jump-to destination.
		
	lda	WBOOT		;see if jump instruction present for CP/M
	cpi	JMPINST
	rnz			;no, not CP/M

; A jump instruction is present at the CP/M warm boot location (0),
;   now see if that jump points to five more jumps. If so, assume CP/M

	lxi	h,WBOOT+1	;point to lsb of jump address
	mov	e,m		;E=low byte of jump
	inx	h
	mov	d,m		;DE=destination of jump
	mvi	b,5		;look for 5 more jumps (a jump table)
	
jmpTest	ldax	d		;A=opcode at jump destination
	sui	JMPINST		;another jump present?
	rnz			;no, not CP/M
	
	inx	d		;move to next jump
	inx	d
	inx	d
	dcr	b
	jnz	jmpTest
	
	dcr	a		;A=0FFh
	sta	cpmFlag		;CP/M flag to non-zero = true

; We're running under CP/M. Change drive prompt message to show drive
;    letters instead of drive numbers and change baseDrv to convert
;    an 'A' to the base drive number (MINDRV).

	mvi	a,'A'		;'A' in drive message instead of number
	sta	mDrvMin
	adi	MAXDRV-MINDRV	;max drive letter
	sta	mDrvMax
	mvi	a,'a'		;ASCII 1st drive - baseDrv = 0
	sta	baseDrv
	
	ret

;------------------------------------------------------------------
; 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
;------------------------------------------------------------------
; If running under CP/M, use the BIOS pointer to determine the top
;    of available RAM. Otherwise, size RAM manually.

sizeRam	lhld	BDOS+1		;HL=BDOS entry address
	mvi	l,0		;HL->top of usable RAM+1

	lda	cpmFlag		;running under CP/M?
	ora	a
	jnz	ramEnd		;yes, we have the end of RAM

;  Not under CP/M. Find the end of RAM manually.

	lxi	h,(trkBuf+0ffh) AND 0ff00h   ;next 256 byte boundary
	
ramLoop	mov	a,m		;A=current RAM content
	inr	m		;change RAM
	cmp	m		;did RAM change?
	mov	m,a		;restore RAM
	jz	ramEnd		;end of RAM found
	
	inr	h		;next page
	jnz	ramLoop

	dcr	h		;force end to FF00 instead of 0000

; 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	xchg			;DE=end of RAM + 1
	mvi	a,-((TRKLEN SHR 8) + 1) AND 0ffh
	cmp	d		;force DE < (10000h - TRKLEN)
	jnc	topOk
	
	mov	d,a		;limit max address
	
topOk	lxi	h,trkBuf	;hl=start of track buffer
	lxi	b,TRKLEN	;bc=length of track in bytes

; Loop increasing HL by TRKLEN until HL > end of RAM.

bfEndLp	dad	b		;HL=HL+track length
	call	cmpHlDe		;compare HL-DE
	jc	bfEndLp		;still more room, keep going

; Subtract one track length from HL, this will be the end address + 1
;   of the last track buffer that will fit in RAM

	lxi	b,-TRKLEN	;subtract one track length
	dad	b		;HL = end address of last track + 1
	shld	bufEnd		;save as bufEnd
	
	ret

;--------------------------------------------------------------------
; cmHlDe - compare HL-DE. Carry set if HL<DE, carry clear if HL>=DE
;--------------------------------------------------------------------
cmpHlDe	mov	a,l		;compare HL-DE, do lsbs first
	sub	e
	mov	a,h		;do msbs
	sbb	d
	ret			;carry set HL<DE, clear HL>=DE

;--------------------------------------------------------------------
; cmDeHl - compare DE-HL. Carry set if DE<HL, carry clear if DE>=HL
;--------------------------------------------------------------------
cmpDeHl	mov	a,e		;compare DE-HL, do lsbs first
	sub	l
	mov	a,d		;do msbs
	sbb	h
	ret			;carry set DE<HL, clear DE>=HL

;--------------------------------------------------------------------
; subDeHl - HL=DE-HL
;--------------------------------------------------------------------
subDeHl	mov	a,e		;subtract DE-HL, do lsbs first
	sub	l
	mov	l,a		;lsb result in l
	mov	a,d		;do msbs
	sbb	h
	mov	h,a		;msb result in h	
	ret

;---------------------------------------------------------------------
; Message constants
;---------------------------------------------------------------------
mWelcom	db	cr,lf,lf
	db	'===== Floppy to PC Image Transfer (IMSAI SIO-2) v1.1 ====='
	db	cr,lf,lf
	db	'Sends an image of an Altair 8" disk to a PC via an'
	db	cr,lf
	db	'IMSAI SIO-2 serial port, using the XMODEM protocol.',0

mDrive	db	cr,lf,lf,'Insert and specify source drive ('
mDrvMin	db	'x-'
mDrvMax	db	'x) or X to exit: ',0

mPort	db	cr,lf,lf
	db	'Specify the serial port to use for file transfer',cr,lf
	db	'  1) Console serial port (12h/13h)',cr,lf
	db	'  2) Secondary serial port (14h/15h)',cr,lf
	db	'Enter port or X to exit: ',0

mStart	db	cr,lf,lf,'Start XMODEM receive operation on the PC now...',0

mDone	db	cr,lf,lf,'Transfer complete!',cr,lf,0

mXmdm	db	cr,lf,lf,'XMODEM communication failure',cr,lf,0

mNoTrk0	db	cr,lf,lf,'Seek to track 0 failed',cr,lf,0

mFails	db	cr,lf,'Failed Sector List...',0

mTrkErr	db	cr,lf,'Track '
errTrk	db	'xx: ',0
errSec	db	'xx ',0

mExit	db	cr,lf,lf,'Exiting...',cr,lf,0

mCpm	db	cr,lf,lf
	db	'Insert CP/M disk into drive A, then press Return...',0

;****************************************************************************
;
;  Hardware specific console and serial I/O routines. 
;     The following four routines must be written to provide a common
;     interface to the hardware on which this program is running. The
;     port number specified for serIn and serOut matches the port number
;     input from the operator via the port menu.
;
;****************************************************************************

; 8251 UART Equates

SIOACR	equ	13h		;console port - control register
SIOADR	equ	12h		;console port - data register
SIOBCR	equ	15h		;2nd port - control register
SIOBDR	equ	14h		;2nd port - data register
SIORDA	equ	02h		;read data available flag
SIOTRE	equ	01h		;transmit register empty flag
SIOCTL	equ	18h		;interrupt control register

; The rcvByte subroutine above times a one second timeout with a code
;    loop that calls the hardware specific serIn routine below. ONESEC
;    must be set based on processor speed and the number of cycles in 
;    the serIn call + 59 cycles for the rcvByte code. 

ONESEC	equ	19802		;rcvByte loop count for 1 second (2mhz)

;-----------------------------------------------------------------------
; serInit - Initialize 8251 serial ports on the SIO-2
;-----------------------------------------------------------------------
serInit	xra	a
	out	SIOCTL		;no interrupts
	
	out	SIOACR		;account for possible power up into
	out	SIOBCR		;   synchronous mode
	out	SIOACR
	out	SIOBCR
	out	SIOACR
	out	SIOBCR

	mvi	a,40h		;reset 8251
	out	SIOACR
	out	SIOBCR

	mvi	a,4Eh		;8N1, /16
	out	SIOACR
	out	SIOBCR
	
	mvi	a,37h		;reset errors, enable xmit, rcv
	out	SIOACR
	out	SIOBCR

	ret
	
;----------------------------------------------------------------------------
; conIn - input character from console
;    inputs:
;    outputs: z true if no character present
;	      z false if character returned in a
;    clobbers none
;----------------------------------------------------------------------------
conIn	in	SIOACR		;see if a new character is present
	ani	SIORDA
	rz			;no character, return zero status
	in	SIOADR		;return character and non-zero status
	ret	

;----------------------------------------------------------------------------
; conOut - output character to console
;    inputs: b = character to send
;    clobbers a
;----------------------------------------------------------------------------
conOut	in	SIOACR		;wait for OK to transmit
	ani	SIOTRE
	jz	conOut
	mov	a,b		;a=character to transmit
	out	SIOADR		;send it
	ret

;----------------------------------------------------------------------------
; Hardware specific I/O
; serIn - input character from port specified in a
;       0 = console serial port
;       1 = second serial port
;    inputs: a = port to read from
;    outputs: z true if no character present
;	      z false if character returned in a
;    clobbers none
; 42 cycles including return when no character present
;-----------------------------------------------------------------------------
serIn	ora	a		;(4) port 0 or 1?
	jnz	s2InB		;(10) port 1, 2nd serial port

; Input from console serial port

s2InA	in	SIOACR		;(10+1) see if a new character is present
	ani	SIORDA		;(7)
	rz			;(10) no character, return zero status
	in	SIOADR		;return character and non-zero status
	ret	
	
; Input from 2nd serial port

s2InB	in	SIOBCR		;(10+1) see if a new character is present
	ani	SIORDA		;(7)
	rz			;(10) no character, return zero status
	in	SIOBDR		;return character and non-zero status
	ret		

;-----------------------------------------------------------------------------
; Hardware specific I/O
; serOut - output character to port specified in a
;       0 = console serial port
;       1 = second serial port
;    inputs: a = port to transmit through
;	     b = character to send
;    clobbers a
;-----------------------------------------------------------------------------
serOut	ora	a		;port 0 or 1 ?
	jnz	s2OutB		;second serial port

; Send character through console serial port

S2OutA	in	SIOACR		;wait for OK to transmit
	ani	SIOTRE
	jz	S2OutA
	mov	a,b		;a=character to transmit
	out	SIOADR		;send it
	ret

; Send character through second serial port

s2OutB	in	SIOBCR		;wait for OK to transmit
	ani	SIOTRE
	jz	s2OutB
	mov	a,b		;a=character to transmit
	out	SIOBDR		;send it
	ret


;****************************************************************************
;
; Altair disk I/O routines
;
;****************************************************************************

; MITS Disk Drive Controller Equates

drvSel	equ	8		;drive select port (out)
drvStat	equ	8		;drive status port (in)
drvCtl	equ	9		;drive control port(out)
drvSec	equ	9		;drive sector position (in)
drvData	equ	10		;drive read/write data

enwdMsk	equ	01h		;enter new write data bit mask
headMsk	equ	04h		;mask to get head load bit alone
moveMsk	equ	02h		;mask to get head move bit alone
trk0Msk	equ	40h		;mask to get track zero bit alone
dSelect	equ	80h		;deselects drive when written to drvSel
selMask	equ	08h		;zero when selected, one otherwise

dcStepI	equ	01h		;drive command - step in
dcStepO	equ	02h		;drive command - step out
dcLoad	equ	04h		;drive command - load head
dcUload	equ	08h		;drive command - unload head
dcWrite	equ	80h		;drive command - start write sequence

;---------------------------------------------------------------------------
; dRead - Read a sector, verify checksum, stop byte, and track number.
;    Verification is done after the sector is read in the time between
;    the last byte of the sector and before the start of the next sector.
;    With index alignment and rotation speed tolerance accounted for,
;    we safely have 360 cycles from the last byte of the sector until code
;    should be in the sector true hunt loop. The RET is complete from this
;    routine 159 cycles worst case after the last byte is read.
;
; On Entry
;    Drive is selected, sector true just detected
;    HL->sector buffer
;
; On Exit
;    Sector read to (HL)
;    C = track number from disk with sync bit set
;    Zero true if no error, Zero false for checksum error, missing 
;	FF stop byte, or track number error
;    Clobbers A,B,C,H,L
;---------------------------------------------------------------------------
dRead	push	h		;(11) save pointer to start of sector

	lxi	b,256+DSECLEN	;(10) B=chksum (-FF), C=bytes to read
	lda	curTrk		;(13) A=track we're on
	cpi	DATATRK		;(7) data track or system track?
	jnc	drSecWt		;(10) data track, length in C is correct

	mvi	c,SSECLEN	;(7) C=length of a system sector

; The sector transfer loop is 116 cycles for two bytes read (has to be
;     less than 128) and computes checksum over all bytes read. For the
;     mini disk, only one byte is read per loop iteration.

drSecWt	in	DRVSTAT		;(10) get drive status byte
	ora	a		;(4) wait for NRDA flag true (zero)
	jm	drSecWt		;(10) 

 	in	DRVDATA		;(10) read first byte at 24-48 cycles
	mov	m,a		;(7) store in buffer
	add	b		;(4) update checksum
	mov	b,a		;(5) keep checksum in B
	inx	h		;(5) increment buffer pointer
	dcr	c		;(5) decrement byte count
	jz	drSecDn		;(10) exit if done
 
	in	DRVDATA		;(10) read at 70-94 cycles (data at 64 and 128)
	mov	m,a		;(7) store in buffer
	add	b		;(4) update checksum
	mov	b,a		;(5) keep checksum in B
	inx	h		;(5) increment buffer pointer
	dcr	c		;(5) decrement byte count
	jnz	drSecWt		;(10) repeat until done 

; drSecDn - Sector read is complete, now validate the sector based on whether
;   it is from a system track or data track.

drSecDn	mov	m,c		;(7) store a trailing zero

	lda	curTrk		;(10) A=track we're supposed to be on
	cpi	DATATRK		;(7) system or data track?
	jnc	drDatTk		;(10) data track

; Validate a system track sector. A system track sector is three bytes
;    bytes shorter, so this codes starts 3*64 cycles sooner than for a
;    data sector. This code completes before a data sector would even
;    finish reading. Therefore, a data sector is the worst timing path.

	mov	a,b		;(5) A=calculated checksum over all bytes
	dcx	h		;(5) HL->checksum in sector
	sub	m		;(7) subtract from computed checksum
	sub	m		;(7) final sum will be zero

	dcx	h		;(5) HL->stop byte
	mov	b,m		;(7) B=stop byte
	pop	h		;(10) restore HL->start of buffer
	inr	b		;(4) was FF stop byte there?
	rnz			;(5/11) no, return error

	mov	c,m		;(7) C=track number from disk
	sub	c		;(4) take track out of checksum

	inx	h		;(5) HL->2nd byte of sector
	sub	m		;(7) take out of checksum
	
	inx	h		;(5) HL->3rd byte of sector
	jmp	drExit		;(10) do common compares and exit

; Validate a data track sector (125 + 27 cycles for sector type jump)

drDatTk	dcx	h		;(5) HL->stop byte
	mov	a,m		;(7) A=stop byte
	pop	h		;(10) restore HL->start of buffer
	inr	a		;(4) was FF stop byte there?
	rnz			;(5/11) no, return error

	mov	a,b		;(5) A=calculated checksum over all bytes
	mov	c,m		;(7) C=track number from disk
	sub	c		;(4) take track out of checksum

	inx	h		;(5) HL->sector number
	sub	m		;(7) take out of checksum
	
	inx	h		;(5) move to checksum byte
	inx	h		;(5)
	inx	h		;(5) HL->checksum
	
	sub	m		;(7) subtract from checksum
drExit	sub	m		;(7) checksums match?
	rnz			;(5/11) no, exit with error
	
	lda	curTrk		;(10) A=track we should be on
	ori	80h		;(7) set sync bit
	cmp	c		;(4) same track as from sector?
	ret			;(11)

;--------------------------------------------------------------
; dSeek - Seek Track
; On Entry:
;    curTrk = current track, FFh means we don't know
;    L = desired track
; On Exit:
;    curTrk = current track
; trashes all registers
;--------------------------------------------------------------
dSeek	call	dSelDrv		;make sure drive is selected
	lda	curTrk		;where are we?
	cpi	0FFh
	cz	dRestor		;lost: restore (assumes a returned 0)

; Compute the required number of steps and the direction

	sub	l		;which way to step?
	rz			;exit ifnalready there
	
	mvi	b,dcStepO	;assume desired < current (step out)
	jp	dDoSeek		;it is
	
	cma			;compute a=-a
	inr	a
	mvi	b,dcStepI	;desired > current (step in)

; Save target track and step there

dDoSeek	mov	h,a		;h=number of steps
	mov	a,l		;save new track when done in curTrk
	sta	curTrk
	jmp	dStep		;step to the specified track
	
;--------------------------------------------------------------
; dRestor - Restore to track 0
; On Exit:
;    A=0
;    curTrk=0
; trashes B,C,H (C not clobbered in this version)
;--------------------------------------------------------------
dRestor	call	dSelDrv		;make sure drive is selected
	xra	a		;set current track to zero
	sta	curTrk
	
	in	drvStat		;test for track 0
	ani	trk0Msk
	rz			;at track 0, exit

; Step in three tracks, then step out to track 0

	mvi	h,3		;h=step in 3 tracks
	mvi	b,dcStepI	;b=step in command
	call	dStep		;do 3 steps in
	rz			;already reached track 0

; Seek back out until track 0 detected

	mvi	h,NUMTRK+10	;maximum number of step outs to try
	mvi	b,dcStepO	;step out command
	call	dStep		;step out until track 0
	rz			;track zero reached

; Too many steps, can't find track 0. Display error and start the
;   program over

	call	dUnload		;unload drive head
	lxi	h,mNoTrk0
	call	dispMsg
	lxi	sp,ourStk	;initialize stack pointer
	jmp	getDrv		;start over asking for drive num

;--------------------------------------------------------------
; dStep - Step head (not a required entry). After seek operation
;   is complete, wait an additional 30ms in case another seek
;   operation immediately follows in the opposite direction.
; On Entry:
;   H = number of steps, >0
;   B = step command (dcStepO or dcStepI)
; On Exit:
;   Z set and curTrk=0 if track 0 found
; trashes B,C,H
;--------------------------------------------------------------
dStep	mov	a,b		;a=step command and direction
	out	drvCtl		;issue the step
	
dStepLp	in	drvStat		;loop until OK to move the head again
	ani	moveMsk
	jnz	dStepLp

; see if we hit track zero while stepping. If so, return with z
;   set and curTrk = 0

	in	drvStat		;test for track 0
	ani	trk0Msk
	jz	dStep0		

; not track zero. Loop until the specified number of steps is done

	dcr	h		;decrement step counter
	jnz	dStep		;loop until count reaches zero
	
	mvi	a,30		;delay 30ms for possible dir change
	call	delayMs
	
	inr	h		;set non-zero status
	ret

;  We stepped to track 0 during this operation. Return with
;     curTrk set to 0 and the Z flag true

dStep0	sta	curTrk		;curTrk = 0
	mvi	a,30		;delay 30ms for possible dir change
	call	delayMs
	ret			;return with z true

;-----------------------------------------------------------------------------
; delayMs - delay for the ms specified in a
;   clobbers B,C
;-----------------------------------------------------------------------------
delayMs	mov	b,a		;B=ms to delay

delay1	mvi	c,(CPUSPD/19)	;19 is cycles in the loop below
delayLp	nop			;(4)
	dcr	c		;(5)
	jnz	delayLp		;(10)

	dcr	b		;decrement ms counter
	jnz	delay1
	ret

;--------------------------------------------------------------
; dSelDrv - Select drive in drvNum (not a required entry)
;   If the drive is not already selected, deselect the old
;   drive and select the new drive.
; On Entry:
;    curDrv = drive currently selected, FFh means we don't know
;    drvNum = desired drive to be selected
; On Exit:
;    curDrv = current selected Drive
; Clobbers none
;--------------------------------------------------------------
dSelDrv	push	h		;save HL
	lxi	h,curDrv	;HL->currently selected drive
	lda	drvNum		;a=desired drive
	cmp	m		;proper drive already chosen?
	mov	m,a		;curDrv=drvNum
	jz	dVfySel		;yes, verify it's selected

; De-select current drive and select desired drive until the
;   drive is selected

dSelLp	mvi	a,dSelect	;deselect a possibly attached drive
	out	drvSel
	
	lda	drvNum		;a=drive to select
	out	drvSel	
	
dVfySel	in	drvStat		;loop until drive is selected
	ani	selMask		;drive selected?
	jnz	dSelLp		;no, try again
	
	pop	h		;restore HL
	ret

;--------------------------------------------------------------
; dDeslct - Deselect drive in drvNum (not a required entry)
;--------------------------------------------------------------
dDeslct	mvi	a,dSelect	;select code to de-select drive
	out	drvSel		
	ret

;--------------------------------------------------------------
; dLoad, dUnload - Load and unload head (drive-specific entry)
;   The head load is not issued if the head is already
;   settled so we don't re-start the head load one-shot.
; On Entry:
;    Drive is selected
; On Exit:
;    head loaded/unloaded
;--------------------------------------------------------------
dLoad	in	drvStat		;head loaded already?
	ani	headMsk
	rz			;yes, exit
	
	mvi	a,dcLoad	;issue the load head command
	out	drvCtl
	ret
	
; Head unload

dUnload	mvi	a,dcUload	;issue head unload command
	out	drvCtl
	ret

;--------------------------------------------------------------
; dNxtSec - Wait for next (any) sector
; On Entry:
;    Drive is selected, motor is running
; On Exit:
;    drive is at proper sector
;    BC = byte offset of this sector in a track
;    HL->secTbl status byte for this sector
;    A = status byte from secTbl
;    Zero set/clear based on status byte.
;--------------------------------------------------------------
dNxtSec	in	drvSec		;read sector position register
	rar			;wait for sector true (0=true)
	jc	dNxtSec

	ani	01Fh		;get sector number alone
	mvi	h,(mult137 SHR 8)   ;H=msb of "multiply by 137" table
	rlc			;sector*2 (2 bytes per table entry)
	mov	l,a		;HL->sec*137 (table on 256 byte boundary)
	mov	c,m		;form BC=sec*137
	inx	h
	mov	b,m		;BC=byte offset of sector within a track

	rrc			;A=sector number
	adi	(secTbl AND 0FFh)   ;A=LSB of sector address in secTbl
	mov	l,a		;HL->sector in secTbl
	mov	a,m		;test and return sector status flag
	ora	a

	ret

;------------------------------------------------------------------
; mult137 - Table to multiply byte 0-31 by 137. This table is
;    on a 256 byte boundary to speed up its use.
;------------------------------------------------------------------
	org	($+255) and 0ff00h
mult137	dw	00*137,01*137,02*137,03*137,04*137,05*137,06*137,07*137
	dw	08*137,09*137,10*137,11*137,12*137,13*137,14*137,15*137
	dw	16*137,17*137,18*137,19*137,20*137,21*137,22*137,23*137
	dw	24*137,25*137,26*137,27*137,28*137,29*137,30*137,31*137	

; secTbl contains the status of each sector in the current track.
;    The byte is zero for a good sector, non-zero for a bad sector.
;    dNxtSec assumes this table is fully contained in the same page
;    as the mult137 table.
	
secTbl	ds	32		;sector status flags per sector

;**************************************************************************
; 
;  Data area
;
;**************************************************************************
; disk variables

drvNum	ds	1		;drive number to use
curDrv	ds	1		;drive currently selected
curTrk	ds	1		;current track number drive is on
trkNum	ds	1		;track number to read
trkPtr	ds	2		;pointer into trkBuf at track boundaries
bufEnd	ds	2		;end address + 1 of last track spot in RAM
skRetry	ds	1		;disk read retry counter

; xmodem variables

xfrPort	ds	1		;pseudo port for file transfer
xmRetry	ds	1		;xmodem retry counter
blkNum	ds	1		;current xmodem block number
crcFlag	ds	1		;non zero if using CRC instead of checksum
crc16	ds	2		;crc-16 result
sndPtr	ds	2		;pointer for start of XMODEM send

; misc variables

cpmFlag	ds	1		;non-zero if running under CP/M
baseDrv	ds	1		;ascii 1st drive - baseDrv = 0

; errTbl - bit packed table of flags for every sector on the disk.
;    Zero means no error, one means read error

pErrTbl	ds	2		;pointer into errTbl
haveErr	ds	1		;non-zero if errors occurred
errTbl	ds	NUMTRK*NUMSEC/8

; stack space

	ds	64		;stack space
ourStk	equ	$

; track buffer runs from here to the end of memory

	ds	PKTLEN		;space for pre-pended unsent data
trkBuf	equ	$

	end
