;********************************************************
;*							*
;*			  SSSD				*
;*							*
;*	Version 2.1			Lennie Araki	*
;*	09/23/80					*
;*							*
;*	Hooks up into WH9 to allow access to single	*
;*  sided single density minifloppies in PolyMorphic	*
;*  or North Star format.				*
;*							*
;*	Copyright (C), 1979 Interactive Products	*
;*  Corporation dba PolyMorphic Systems, 460 Ward Dr.	*
;*  Santa Barbara CA 93111 (805) 967-2351.		*
;*							*
;********************************************************
;
False	EQU	0
True	EQU	NOT False
;
	REFS	SYSTEM
	;
	REF	USER
	REF	Msg
	REF	WH0
	REF	Fold
	REF	Ioret
	REF	MEMTOP
	REF	DONT
	REF	TIMER
	REF	CMPTR
WH9	EQU	WH0+4*9
;
;	Ascii character codes.
;
TAB	EQU	9
CR	EQU	13
;
;	Macros...
;
db	MACRO
#L	DB	#A
	ENDM
;
align	MACRO	size
#L	ORG	($+#1-1) AND NOT #1-1
	ENDM
;
;	Other constants.
;
MinMem	EQU	5FFFH		;Minimum MEMTOP.
;
	ORG	USER		; USER program but assemble
	RELOC			; relocatably!
	IDNT	$,$
;
;	Start code.  Check to see if we have already been 
;  installed and if so just return.  Else copy code above
;  MEMTOP, relocate, and hookup into WH9.
;
PROG	JMP	START		; Start and
	JMP	START		; Reenter vectors.
;
Version   DB	'(Sssd/ 2.1 - ',0
Connect   DB	'connected to WH9)',CR,0
MoreMem   DB	'not enough memory)',CR,0
BadArg	  DB	'bad arguments)',CR,0
;
START	LXI	H,Version
	CALL	Msg		; Say hello.
	LHLD	MEMTOP
	XCHG
	LHLD	WH9+1		; (WH9+1) < MEMTOP?
	CALL	DCMP		; (See if installed)
	JNC	HOOK2		; Jmp/ already connected.
	;
	LHLD	MEMTOP
	LXI	D,MinMem+1
	CALL	DCMP		; MEMTOP < MinMem+1?
	JNC	HOOK1		; Jmp/ nope, got enough.
	LXI	H,MoreMem
	JMP	Msg		; Sorry, need more memory.
HOOK1	LHLD	WH9+1
	SHLD	OldDio+1	; Set address of JMP.
	LHLD	MEMTOP
	LXI	B,-Size*256
	DAD	B
	SHLD	MEMTOP		; Now move MEMTOP down.
	INX	H
	XCHG			; Destination is MEMTOP+1
	LXI	H,Code		; Source is code.
	LXI	B,-CodeLen
	CALL	Move		; Copy code above MEMTOP.
	;
	;	Now relocate the code.
	;
	LHLD	MEMTOP		; Start of resident-1
	LXI	B,1-ProgLen	; (Length of non-resident)
	DAD	B		; Point to starting 
	XCHG			; Memory addr (DE)
	LXI	H,Length+ProgLen
	DAD	D
	SHLD	RELEND		; Set RELEND for RELOCS
	LHLD	MEMTOP
	INX	H		; Where code runs
	LXI	B,SSSD		; (Assembly addr)
	CALL	SubBC		; Subtract the two.
	MOV	B,H
	MOV	C,L		; Relocation offset (BC)
	LXI	H,TAIL		; Relocation table (HL)
	CALL	RELOCS		; Relocate it!
	;	
	LHLD	MEMTOP
	INX	H
	SHLD	WH9+1		; Hookup SSSD
HOOK2	CALL	GetArgs 	; Parse arguments.
	LXI	H,BadArg
	JC	Msg		; Jmp/ say Bad arguments
	;
	CALL	DIOINIT 	; else initialize for DIO.
	LDA	NPFlag
	ORA	A
	JZ	HOOK11
	CALL	NSINIT		; Initialize USRT for Poly
	JMP	HOOK12
HOOK11	CALL	PMINIT		; or North Star format.
HOOK12	DI			;-----
	LHLD	WH9+1
	LXI	B,Data-SSSD	; Relative offset to Data
	DAD	B		; Offset+(WH9+1)
	XCHG			; Destination
	LXI	H,Data		; Source
	LXI	B,-DataLen
	CALL	Move
	EI			;-----
	LXI	H,Connect
	JMP	Msg		; Return displaying message.
;
;	Get the arguments off the command line.  Should be of
;  the form:
;	Logical Drive - Physical drive { "N": north star
;				      or "P": Poly format }
;	Default is 3-3P.
;
GetArgs MVI	A,3		; Map logical drive 3 to
	STA	LogDrive
	MVI	A,3		; physical drive 1.
	STA	PhyDrive
	XRA	A		; 0=Poly format
	STA	NPFlag		; Setup default.
	LHLD	CMPTR		; Get scan pointer in HL.
	CALL	Nxtc
	RZ			; Ret/ not specified.
	SUI	'0'		; Logical drive number.
	CPI	10
	CMC
	RC			; Ret/ not 0-9 (CARRY).
	STA	LogDrive	; else set logical
	STA	PhyDrive	; and physical drive
	CALL	Nxtc
	RZ			; Ret/ not specified.
	CPI	'-'
	JNZ	GetAr1		; Jmp/ not "-"
	CALL	Nxtc
	SUI	'0'		; Physical drive number.
	CPI	10
	CMC
	RC			; Ret/ not 0-9.
	STA	PhyDrive	; else set physical drive.
	CALL	Nxtc
	RZ			; Ret/ no format letter.
GetAr1	CALL	Fold
	CPI	'N'
	MVI	B,True		; NZ=North* format
	JZ	GetAr11 	; Jmp/ "N" North Star
	CPI	'P'
	MVI	B,0		; 00=Poly format
	JZ	GetAr11 	; Jmp/ "P" PolyMorphic
	STC
	RET
GetAr11 MOV	A,B
	STA	NPFlag		; Set flag.
	CALL	Nxtc
	RZ			; Ret/ end of line
	STC			; else error (CARRY)
	RET
	PAGE
;
;	Initialization for DIO.
;
DIOINIT MVI	A,86H
	OUT	DCO		; shut up controller.
	XRA	A
	OUT	DSEL
	STA	MTO		; Say motor timed out and
	STA	CDRIV		; mark nobody selected.
	OUT	DCI		; clean out 8255
	LXI	H,Ioret+5	; Ioret's Return.
	SHLD	SINT		; sector interrupt exit
	MVI	A,10
	STA	TRIES		; retry counter for disk errs
	MVI	A,0FFH
	STA	TRK1		; set TRK1-3 to FF so we will
	STA	TRK2		; re-synch the head mechanism
	STA	TRK3		; when we bring up a disk
	MVI	A,20
	STA	STEPTIME	; 20 milliseconds for SA-400.
	MVI	A,0
	STA	SETTLETIME	; No settle time for these.
	RET
;
;	NSINIT initializes the USRT for North Star format.
;  Preserves BC,DE,HL.
;
NSINIT	MVI	A,0BH
	OUT	USTAT		; clear USART
	MVI	A,18H
	OUT	UDAT		;wl=8, no parity
	MVI	A,40H
	OUT	USTAT		; 2 byte mode
	MVI	A,0EH
	OUT	UDAT		; clear ctuf & cts, 1 synch
	MVI	A,80H
	OUT	USTAT
	MVI	A,NSSync
	OUT	UDAT		; synch char
	MVI	A,0C0H
	OUT	USTAT		; select FIFO
	RET
;
;	PMINIT initializes the USRT for PolyMorphics format.
;  Preserves BC,DE,HL.
;
PMINIT	MVI	A,0BH
	OUT	USTAT		; clear USART
	MVI	A,18H
	OUT	UDAT		;wl=8, no parity
	MVI	A,40H
	OUT	USTAT		; 2 byte mode
	MVI	A,0CH
	OUT	UDAT		; clear ctuf & cts, 2 synch chr
	MVI	A,80H
	OUT	USTAT
	MVI	A,PMSync	; synch
	OUT	UDAT		; chr
	MVI	A,0C0H
	OUT	USTAT		; select FIFO
	RET
	PAGE
;
;		MODIFIED Relocation Subroutine
; RELOCS
;	Relocate a file loaded into memory at DE
;  with HL pointing at the start of the relocation
;  block and RELEND pointing at the end of the program.
;  Offset is in BC.
;
RELOCS	SHLD	RELBLK	 	;Save HL in RELBLK.
	MOV	H,B
	MOV	L,C
	SHLD	RELOFF
	LHLD	RELBLK
	XCHG
	JMP	Reloc3		;Jmp get into the loop!
	;
Reloc1	INX	H		;Main loop
	INX	H		;Bump HL two.
	PUSH	D
	XCHG
	LHLD	RELEND		;Get end of code pointer.
	XCHG
	CALL	DCMP		;Present pointer - end.
	POP	D
	RNC			;Ret/ all done, no carry.
	;
Reloc2	DCR	C		;Dink bit count
	MOV	A,B		;Get bit pattern in A
	JNZ	Reloc4		;Jmp/ some bits left
Reloc3	LDAX	D		; else get a new byte
	INX	D
	MVI	C,8		; and reset bit count
Reloc4	RAL
	MOV	B,A		;Put bits back into B.
	JNC	Reloc1		;Jmp/ 0: just skip two
	;
	DCR	C		;Dink bit count
	MOV	A,B		;Get bit pattern in A
	JNZ	Reloc5		;Jmp/ some bits left
	LDAX	D		; else get a new byte
	INX	D
	MVI	C,8		; and reset bit count
Reloc5	RAL
	MOV	B,A		;Put bits back into B.
	JNC	Reloc6		;Jmp/ 0: relocate even
	INX	H		; else relocate odd.
Reloc6	PUSH	B
	PUSH	D
	MOV	C,M
	INX	H
	MOV	B,M		;Get old addr in BC
	XCHG
	LHLD	RELOFF		;Offset to add.
	DAD	B		;Add the two together.
	XCHG
	MOV	M,D
	DCX	H
	MOV	M,E		;Stick back in memory.
	POP	D
	POP	B
	JMP	Reloc1		;Back to main loop.
;
;	Data section
;
RELBLK	  DS	2
RELEND	  DS	2
RELOFF	  DS	2
;
;	***** UTILITIES *****
;
DCMP	MOV	A,H
	CMP	D
	RNZ
	MOV	A,L
	CMP	E
	RET
;
DSUB	MOV	A,L
	SUB	E
	MOV	L,A
	MOV	A,H
	SBB	D
	MOV	H,A
	RET
;
SubBC	MOV	A,L
	SUB	C
	MOV	L,A
	MOV	A,H
	SBB	B
	MOV	H,A
	RET
;
;	Move -BC bytes from HL@ to DE@.
;
Move	MOV	A,M
	STAX	D
	INX	H
	INX	D
	INR	C
	JNZ	Move
	INR	B
	JNZ	Move
	RET
;
Nxtc	MOV	A,M
	INX	H
	CPI	' '
	JZ	Nxtc		; Skip spaces
	CPI	TAB
	JZ	Nxtc		; and tabs
	CPI	CR
	RET			; CR sets ZERO.
;
ProgLen EQU	$-PROG		; Length of non-resident code.
	PAGE
;
;***************
;
;	SSSD is the actual code which resides above MEMTOP.
;  NOTE: the entry point is assumed to be the first byte.
;
Code	EQU	$
;
SSSD	PUSH	PSW		; Must preserve AF.
	LDA	LogDrive
	CMP	C
	JNZ	SSSD1		; Jmp/ use old Dio
	LDA	PhyDrive
	MOV	C,A		; New drive number
	POP	PSW
	JMP	DIO		; Dio for SSSD mini-floppies
SSSD1	POP	PSW
OldDio	JMP	0		; Address is poked into here
;
;		DIO taken from Root/77
;
MTIME	EQU	50*5		; 5 seconds worth of spin.
;
; Disk controller stuff
;
DCO	EQU	2BH		; commands out to controller
DCI	EQU	2AH		; command status back
;
; You send the command port (DCO) an 86H to reset it to
; eating command words. The commands are:
;
CDSI	EQU	4		; disable sector interrupt
CESI	EQU	5		; enable sector interrupt
CMH	EQU	6		; motor halt
CMG	EQU	7		; motor go
CWH	EQU	8		; write off (halt)
CWG	EQU	9		; write on (go)
CRH	EQU	0AH		; read halt (off)
CRG	EQU	0BH		; read go (on)
CSI	EQU	0CH		; step in
CSO	EQU	0DH		; step out
CPL	EQU	0EH		; step pulse low
CPH	EQU	0FH		; step pulse high
;
; The status from the command status port looks like:
;
CSF	EQU	1		; sector flag (found a hole)
CINT	EQU	4		; interrupt enable
;
; Drive select is port 28H, drive status is port 29H.
; the bits to those look like:
;
DST	EQU	29H		; drive status (read only)
DSEL	EQU	28H		; drive select (write only)
;
; Drive status back bits
;
DWP	EQU	20H		; write protected
DTR0	EQU	10H		; track 0 hit
DSM	EQU	0FH		; sector number mask
;
; Select bits out
;
DS0	EQU	0		; select no drives
DS1	EQU	1		; select drive 1
DS2	EQU	2		; select drive 2
DS3	EQU	3		; select drive 3
;
; Finally, the USART is ports 20 and 21H.
;
UDAT	EQU	21H		; USART data port
USTAT	EQU	20H		; USART status port
;
; Looks more or less like a USART.
;
RBF	EQU	1		; reader buffer full
TBE	EQU	2		; tx buffer empty
;
; The synch byte used on the two disks.
;
NSSync	EQU	0DFH		; (0FBH flipped around)
PMSync	EQU	0E6H		; Poly's synch byte
;
; SPINT is entered as a result of a sector pulse 
; interrupt from the controller. If SSEC equals the 
; sector we just hit, we bail out thru SINT 
; to process. Otherwise, we go to
; Ioret, after checking to see if we time out the motor
; by decrementing DTO. If 0, turn off motor and deselect drive.
;
SPINT	CALL	SPInt		; Process interrupt
	JMP	Ioret		; and return
	;
SPInt	IN	DST		; drive stat
	ANI	DSM		; mask to sector number
	MOV	B,A
	LDA	SSEC
	CMP	B
	JNZ	SPIN1		; jmp/not there yet.
	LHLD	SINT
	PCHL			; zoom off if we hit it
SPIN1	LXI	H,MTO
	DCR	M		; halt drives when timed out
	RNZ			; Ret/ not yet
;
; DHALT called to stop things, and mark them stopped.
;
DHALT	PUSH	PSW		; does not change any regs.
	MVI	A,0FEH		; if any I/O is in progress, 
	STA	DSTAT		; we will abort it code FE.
	MVI	A,DS0
	OUT	DSEL		; select drive 0
	STA	CDRIV		; mark nobody selected
	STA	MTO		; mark motors off
	MVI	A,CMH
	OUT	DCO		; and turn off motors
	MVI	A,CDSI
	OUT	DCO		; disable interrupt on sp
	POP	PSW
	RET			; and leave.
;
; DIO is the generalized disk I/O handler. The register are:
;	A	number of sectors
;	B	command, 0=write, 1=read, 2=verify
;	C	unit number
;	DE	memory address
;	HL	disk address (will bust later)
;
DIO	PUSH	PSW		; Save number of sectors
	MOV	A,B		; Get command
	CPI	5		; Dsize?
	JNZ	DIO1		; Jmp/ nope
	;
	;	This is the disk size code for the single 
	; density controller.
	;
	POP	PSW		; Throw away stack
	LXI	H,350		; Size of each side
	IN	DST
	RAL			; Check number of sides (bit7)
	RNC			; Ret/ single sided
	DAD	H		; Double size
	RET			; NO CARRY by DAD H
	;
DIO1	ANI	1		; Mask out illegal codes
	MOV	B,A
	POP	PSW
	;
	DI
	PUSH	D
	PUSH	B
	PUSH	PSW
	PUSH	H		; Stash everything on stack.
	LXI	D,DERR1 	; Error vector
	PUSH	D		; Saves bytes
	ORA	A		; check for 0 blocks
	RZ			; Ret/ nuts to you.
	STA	DONT		; like I said, don't bug me!
	;
	PUSH	PSW
	LXI	D,-350		; Negative Dsize (sssd)
	IN	DST
	RAL
	JNC	DIO11		; Jmp/ single-sided
	LXI	D,-700		; Negative Dsize (dssd)
DIO11	POP	PSW
	DAD	D		; Check DiskAdd
	RC			; Ret/ DA stinks.
	MOV	E,A
	MVI	D,0
	DCX	D
	DAD	D		; Check disk address + NS
	RC			; Ret/ too big.
	MOV	A,C
	CPI	1
	RC			; Ret/ unit number too low
	CPI	4
	RNC			; Ret/ unit number too high
	POP	H		; Clean stack
	LXI	H,SPINT
	SHLD	SRA2		; Insure WE get the interrupts.
	;
	; See if the motor is going. If it is, go make final
	; check for write enable on write command. Motor on is
	; indicated by MTO being non-zero. If it is zero, we 
	; must wait the power-up and synch time, check for a 
	; disk in that unit, etc.
	;
DIO2	LXI	H,MTO
	MOV	A,M
	ORA	A
	MVI	M,MTIME
	JZ	DIO3		; Jmp/ motor not on.
	LDA	CDRIV		; well, is the drive we want
	CMP	C		; selected now?
	JZ	DIOG		; Jmp/ yup, scratch off.
	MVI	A,40		; wait 20 ticks for head load
	JMP	DIO31		; go select that drive.
	;
	;	Start up the motor, see if we have a disk by 
	;  counting sector interrupts over a period of time.
	;  We sit around a while, then check to see if DTO has 
	;  changed.  If it hasn't, report 106 error.
	;
DIO3	MVI	A,CMG		; motor go command
	OUT	DCO		; huzzak!
	XRA	A
DIO31	STA	TIMER		; Set up timer
	MOV	D,M		; Get mtime vale
	MOV	A,C		; Drive number
	OUT	DSEL		; Select a drive
	STA	CDRIV		; Update register
	MVI	A,CESI
	OUT	DCO		; Enable sector interrupts
	IN	DST		; Flush crud interrupt
	;
	;	Now wait for 60 ticks (1 second) for motor 
	;  to speed up.
	;
DIOW	EI
	HLT
	DI
	LDA	TIMER
	CPI	60		; a second yet?
	JC	DIOW		; nope, wait
	;
	;	A second has gone by. See if DTO has changed.
	; Note still disabled, and (HL) -> MTO.
	;
	MOV	A,M
	ADI	5		; Want at least 5...
	CMP	D		; Any interrupts?
	JC	DIOG		; Jmp/ yup, assume motor going
	CALL	DHALT		; else halt the disks
	JMP	DERR6		; and report error-no disk!
	;
	;	Drive is going; see if write protected and 
	;  write requested.
	;
DIOG	MVI	M,MTIME 	; Reset the counter.
	XRA	A
	CMP	B		; Write?
	JNZ	DIOA		; Jmp/ nope, do it
	IN	DST		; Get status
	ANI	DWP		; See if write protected
	JZ	DIOA		; Jmp/ nope
	;
	;	The start of the error chain for DIO. Error 
	;  code in D, subcode in E. The error code is 01.
	;
	MVI	E,5		; Disk is write protected
	DB	21H		; two byte skip
DERR1	MVI	E,1		; bad parameters. pfui!
	DB	21H
DERR6	MVI	E,6		; No media in drive.
	DB	2EH		; one byte skip
DERRA	MOV	E,A		; Hard disk error!
	;
	; Now stuff in code and pull registers for exit.
	;
	MVI	D,1
	POP	H
	POP	PSW
	POP	B
	XCHG			; DE and HL
	XTHL			; EC on stack, old DE in HL
	POP	H		; We want to leave HL alone
	XCHG			; and error code in DE.
	XRA	A
	STA	DONT		; Clear DONT flag.
	STC			; CARRY.
	EI			; Insure enabled on return!
	RET			; Not sure that's right!
;
;	Get the disk address and number of sectors off the
;  stack and convert to physical track and sector.
;
DIOA	POP	H		; Pop off DiskAdd
	LXI	D,-10
	MVI	B,-1
DIO4	INR	B		; Bump trk #
	DAD	D		; Subtract 10 from DA
	JC	DIO4
	POP	PSW		; Number of Sectors
	;
	;	D now contains track and (L+10) is the first 
	;  sector wanted on that track (i.e. L is the negative
	;  of the number of sectors left on the track.  The 
	;  Acc contains the total number of sectors left 
	;  to transfer.
	;
DIO5	CPI	10+1
	JNC	DIO51		; Jmp/ more than a track
	ADD	L
	JM	DIO6		; Jmp/ one shot transfer
	JZ	DIO6
	DB	26H		; Skip 1 byte
DIO51	ADD	L		; "Subtract" number on track
DIO52	MOV	H,A		; Save nsect left in H.
	MOV	A,L
	ADI	10
	MOV	D,A		; D is the first sector wanted
	MVI	A,10		; Acc = 10 - D or the number
	SUB	D		; of sectors to transfer
	STA	MUNG2		; Save in MUNG2 also
	MOV	L,B		; L contains track number.
	SHLD	MUNG3		; Save nsect and track.
	POP	H		; (BC) Command,Drive
	SHLD	MUNG4		; Save in MUNG4
	MOV	C,L		; Drive in C.
	MOV	E,H		; Command to E.
	POP	H		; (DE) Memory addr
	CALL	DOIO		; Go do the I/O.
	RC			; Ret/ pass error along
	LDA	MUNG1+1 	; Number of sectors we did.
	ADD	H		; Update memory address
	MOV	H,A		; for the next one....
	PUSH	H		; Restack new mem addr.
	LHLD	MUNG4
	PUSH	H		; Command and Drive #
	LHLD	MUNG3
	MOV	A,H		; Total nsect left
	MOV	B,L		; Track
	INR	B		; Bump track #
	MVI	L,-10		; For first sector.
	JMP	DIO5		; Spin for another pass
	;
DIO6	SUB	L		; Restore nsect left.
	PUSH	PSW
	MOV	A,L
	ADI	10
	MOV	D,A		; D is the first sector wanted
	POP	PSW
	POP	H		; (BC) Drive, command
	MOV	C,L		; Drive in C.
	MOV	E,H		; Command to E.
	POP	H		; (DE) Memory addr
;
;  DOIO is the actual device I/O code.
;
DOIO	STA	MUNG1+1
	LDA	TRIES		; number of retries
DOI1	DI
	PUSH	PSW
	PUSH	H
	PUSH	D
	PUSH	B
	LDA	MUNG1+1
	STA	DONT		; dont bug me!
	CALL	RDSEC		; do the operation
	POP	B		; (Possible error code in Acc)
	POP	D
	JZ	DOI2		; jmp/ no error.
	PUSH	D
	LXI	H,BUGS-2	; error counter cell
	MOV	E,A		; error code- a 2, 3, or 4
	MVI	D,0
	DAD	D		; point at counter
	INR	M		; bump.
	POP	D
	JNZ	DOI11		; jmp/no overflow
	DCR	M		; make it stick at 255.
DOI11	POP	H
	MVI	A,MTIME
	STA	MTO		; dont timeout retries!
	POP	PSW
	DCR	A
	JNZ	DOI1		; jmp/retry the operation
	LDA	DSTAT		; error code to report
	PUSH	H
	PUSH	H
	PUSH	H		; should be nuff...
	PUSH	H
	JMP	DERRA		; else hard disk error.
DOI2	MOV	A,E
	ORA	A
	JNZ	RHD		; Jmp/ return popping H and D
	POP	H		; we ALWAYS verify
	POP	PSW		; (covers a bug!)
	MVI	E,2
	JMP	DOI1		; fake reentry for verify.
;
; WMS is called to wait a number of milliseconds, passed in A. 
;
WMS	ORA	A
	JZ	WMS3		; Jmp/ handle zero case.
	EI
	PUSH	B		; save stuff.
WMS1	MVI	B,143		; 143*14=2000
WMS2	DCR	B
	JNZ	WMS2		; 1mS loop
	DCR	A
	JNZ	WMS1
	POP	B
WMS3	DI
	RET			; vanish.
;
; TRKS is called to step in or out a number of tracks.
; Entry is with HL pointing to track counter for the
; drive, A contains number of tracks to move- +=in, -=out.
;
TRKS	ORA	A		; Well, what do we do?
	RZ			; Ret/ nothing.
	PUSH	B		; save.
	MVI	B,CSO
	JP	TRKS1		; Jmp/ step in.
	DCR	B
	CMA
	INR	A
TRKS1	MOV	C,A		; number steps
	MOV	A,B		; command
	OUT	DCO		; issue direction set command
;
;	This changed by J.B. Stephensen for faster-than
; one-sector-hole step times.  Code incorporated permanently 
; 10/26/78.  We need to step the number of steps in C.  After 
; issuing the step pulse, we wait for STEPTIME milliseconds to
; let the step complete, and SETTLETIME milliseconds for the 
; head to settle.  If servicing interrupts, the time may be 
; too long, but that is OK.
;
TRKS2	MVI	A,MTIME
	STA	MTO		; Don't let motor time out
	MVI	A,CPH		; step ff high
	OUT	DCO		; raise line
	DCR	A
	OUT	DCO		; lower line
	LDA	STEPTIME
	CALL	WMS		; wait STEPTIME mS for that
	DCR	C		; step some more?
	JNZ	TRKS2		; jmp/ yes
	LDA	SETTLETIME	; wait SETTLETIME mS
	CALL	WMS		; for head settling
	POP	B
	RET			; split.
;
; RDSEC is the actual device control code. You enter
; with:
;	HL	memory address
;	DE	sector/command
;	BC	track/unit number
;	A	number of sectors
;
RDSEC	PUSH	H
	PUSH	D
	PUSH	B
	PUSH	PSW		; save everything.
	MVI	A,9
	ADD	D		; we want to start on the 
	DAA			; previous sector (mod 10)
	ANI	0FH
	STA	SSEC		; starting sector.
	MOV	D,B		; trk desired
	MVI	B,0
	LXI	H,TRK1-1
	DAD	B		; point to right drive ctr
	MOV	A,M		; current track for drive
	CPI	0FFH		; is it set up?
	JNZ	RDS2		; jmp/yes
;
;	If the track cell contins FF, it means we have not 
; used this drive yet.  To synchronize the  hardware and the 
; software, we step in until we detect NOT TRK0 twice in a 
; row and then step out until we sense TRK0.
;
RDS1	IN	DST
	MOV	B,A		; Save status in B.
	MVI	A,1
	CALL	TRKS		; step in 1
	IN	DST
	ORA	B		; TRK0 must be low before and
	ANI	DTR0		; after stepping.
	JNZ	RDS1		; jmp/ keep stepping in
	;
RDS11	MVI	A,-1		; step out 1
	CALL	TRKS
	IN	DST
	ANI	DTR0		; See if at TRK0.
	JZ	RDS11		; jmp/ not there yet
	MVI	M,0		; Set holder to 0.
;
;	Now select the proper side of the disk and figure out 
; which way to step, and step there.
;
RDS2	MOV	A,D		; track we want
	SUI	35
	JP	RDS21 		; jmp/access side 0
	MOV	A,D		; get track we want
RDS21	PUSH	PSW
	SUB	M		; minus where we are
	CALL	TRKS		; go there already.
	POP	PSW
	MOV	M,A		; say we are there
	MOV	A,D
	STA	DTRK		; remember track for I/O code.
	CPI	35		; carry set if A<35
	MVI	A,CSO		; CSO=side 0
	SBI	0		; change to CS1 if carry
	OUT	DCO		; select proper side
;
; Now set up to do the operation.
;
	MOV	A,E
	STA	DioOp		; Save operation code (0,1,2)
	ADD	A		; double
	MOV	E,A
	MVI	D,0
	LDA	NPFlag		; Flag tells whether it is
	ORA	A		; Poly or NS.
	LXI	H,PMVec
	JZ	RDS22		; Jmp/ false, use Poly ISR
	LXI	H,NSVec 	; else use NS ISR
RDS22	DAD	D
	MOV	E,M
	INX	H
	MOV	D,M
	XCHG
	SHLD	SINT		; bail-out point
	POP	PSW
	STA	DSCT		; save number sectors to do
	POP	B
	POP	D
	POP	H		; unwind
	SHLD	DSMA		; memory address to use
	IN	DST		; clear any trouble pending.
	LXI	H,DSTAT		; We'll be looking at this a 
	MVI	A,0FFH		; lot
	MOV	M,A		; set status up
	STA	DONT		; set disable escape entries
;
; Now wait for it to be done.
; Note that when DSTAT goes to other than FF, if it does not 
; go to 0 we have an error, with error code in DSTAT, passed 
; as non-zero condition code back to caller....
;
RDS3	EI
	HLT
	DI			; provide small window
	MOV	A,M		; see what status looks like.
	INR	A
	JZ	RDS3		; jmp/still working.
	XRA	A
	STA	DONT		; allow escapes to work again
	MOV	A,M		; get status
	ORA	A		; provide condition codes
	RET			; to caller.
;
; Vector of processing routines
;
NSVec	DW	NSWrite 	; 0=write	NorthStar
	DW	NSRdVer 	; 1=read
	DW	NSRdVer 	; 2=verify
	;
PMVec	DW	PMWrite 	; 0=write	PolyMorphics
	DW	PMRead		; 1=read
	DW	PMVerify 	; 2=verify
;
; CWSUB is called to wait for TBE to go high. Saves me
; memory, because 8+3n < 7n for n>2.
;
CWSUB	IN	USTAT
	ANI	TBE		; is it up yet?
	RNZ			; jmp/yes, split
	JMP	CWSUB
;
; CURST is called to reset the cursed USART.
;
CURST	LHLD	DSMA
	MOV	B,H
	MOV	C,L
CURS1	MVI	A,0CBH
	OUT	USTAT
	MVI	A,0C0H
	OUT	USTAT
	RET
;
; The Write routine is entered at the
; interrupt level. We have DSMA containing the memory
; address, DSCT conaining the number of sectors to
; write. When we finish, set DSTAT to 0 and Ioret.
;
PMWrite:
	CALL	CURST
	MVI	A,CWG
	OUT	DCO		; set write on
PMWrt1	MVI	L,5
PMWrt2	CALL	CWSUB		; wait for TBE
	XRA	A
	OUT	UDAT
	OUT	UDAT
	DCR	L
	JNZ	PMWrt2		; write 10 zero bytes
	MOV	H,L		; the sneeky way to zero H.
	CALL	CWSUB		; wait for TBE
	MVI	A,PMSync
	OUT	UDAT
	OUT	UDAT
	CALL	CWSUB
	IN	DST		; get sector number
	ANI	0FH		; mask to 0-9
	ORI	80H		; tag
	OUT	UDAT		; shovel out
	LDA	DTRK		; first shovel out sector+80H
	OUT	UDAT		; then output track number
	MVI	A,80H
PMWrt3	PUSH	PSW		; byte count
	CALL	CWSUB		; wait for TBE and then shovel.
	LDAX	B
	INX	B
	OUT	UDAT
	MOV	E,A
	LDAX	B
	INX	B
	OUT	UDAT
	MOV	D,A
	DAD	D
	POP	PSW
	DCR	A
	JNZ	PMWrt3		; still going in record
	CALL	CWSUB		; wait for TBE before cksum...
	MOV	A,L
	CMA
	OUT	UDAT
	MOV	A,H
	CMA
	OUT	UDAT		; send out comp checksum
	LXI	H,DSCT
	DCR	M
	MVI	A,CWH
	OUT	DCO		; start writing ones
	JZ	CWRTX		; jmp/ all done.
	MVI	A,CWG
	OUT	DCO		; start write again,
	IN	DST		; clear magic bit
PMWrt4	IN	DCI
	ANI	CSF
	JZ	PMWrt4		; Wait till next sector comes
	JMP	PMWrt1		; around before writing again.
;
;
; Combined Read-Verify routine
;
PMRead:
	MVI	L,77H		; mov m,a routine
	DB	11H		; skip 2 bytes
PMVerify:
	MVI	L,0BEH		; cmp m routine
	MVI	H,0C9H
	SHLD	Doit		; code to do for each byte
	LHLD	DSMA		; since this code is common,
	XCHG			; put MA into DE
PMRV1	CALL	CURS1
	MVI	A,CRG
	OUT	DCO		; read go
	MVI	A,80H		; number of bytes to read
	PUSH	PSW		; bury the 80
	LXI	H,0		; clear checksum counter
	MOV	B,H
	MOV	C,L		; copy for state flags
;
; The timeout code provided by John Stephenson
;
	IN	DST		; reset CSF bit in controller
PMRV11	IN	DCI
	ANI	CSF		; seen a hole yet?
	JZ	PMRV11		; wait for the hole
	MVI	C,40H		; timeout value
PMRV12	DCR	C		; dcr timeout
	JZ	PPreErr 	; jmp/no synch in time!
	IN	USTAT
	RAR
	JNC	PMRV12		; wait for data
	IN	UDAT
	MOV	B,A		; junk or sector number
	IN	UDAT
	MOV	C,A		; track number
	LDA	DTRK
	CMP	C
	JNZ	PPreErr 	; jmp/oops, wrong track
	CMP	B
	JZ	PMRV13		;jmp/old disk nas trk/trk on it
	IN	DST
	ANI	0FH		; new disk has sctr+80H on it
	ORI	80H
	CMP	B
	JNZ	PPreErr 	; jmp/wrong sector!!!
PMRV13	IN	USTAT
	ANI	RBF
	JZ	PMRV13
	XCHG			; put MA in HL, chksum in DE
	IN	UDAT
	MOV	C,A
	CMP	A		; preset cc's for read case
	CALL	Doit		; do store or compare
	JNZ	PVerErr 	; and barf if bad
	INX	H
	IN	UDAT
	MOV	B,A
	CMP	A
	CALL	Doit
	JNZ	PVerErr
	INX	H
	XCHG			; ma to DE, chksum to HL
	DAD	B		; update chksum
	POP	PSW
	DCR	A		; done yet?
	PUSH	PSW
	JNZ	PMRV13		; jmp/nope
	POP	PSW
;
; the tail code
;
PMRV2	IN	USTAT
	ANI	RBF
	JZ	PMRV2		; this is read cleanup
	IN	UDAT		; we check checksum and such
	MOV	C,A
	IN	UDAT
	MOV	B,A		; remember, ma in DE...
	DAD	B
	INX	H
	MOV	A,H
	ORA	L
	JNZ	PChkErr 	; oops...checksum stinks
	MVI	A,CRH
	OUT	DCO		; read stop
	LXI	H,DSCT
	DCR	M
	JNZ	PMRV1
	JMP	CWRTX
;
;	Poly Read/Verify error logic.
;
PVerErr POP	PSW
	MVI	A,4		; verify error
	JMP	CRVErr
PPreErr POP	PSW
	MVI	A,2		; preamble error
	JMP	CRVErr
PChkErr MVI	A,3		; checksum error
	JMP	CRVErr
;
;	The North Star disk format is totally hard sectored:
;  Each sector contains the following byte sequence:
;	Zeros		16 bytes
;	Sync Char (FB)	1 byte
;	Data		256 bytes
;	Check Char	1 byte
;
;  The check character is computed iteratively by setting it
;  to zero and then exclusive ORing each successive data byte
;  value with its current value and left cycling the result.
;
;	The hardware bit protocol (FM encoding) is identical 
;  on the two controller boards but unfortunately the bits 
;  are output in opposite order (MSB first on North Star, LSB 
;  first on Poly).
;
;	Another hardware compatibility problem is the North 
;  Star controller board generates a "window" of 96 usec after
;  the sector pulse before writing the data.  Since our USRT 
;  starts synching up immediately after the sector pulse, we 
;  must reset it (to lose synch) in the middle of the North 
;  Star is 16 zero bytes.  The delay should be approximately 
;  600 usec. (96 msec window + 8 sync char * 64 msec/char).
;
flip	MACRO			; Flip the bits in A, using BC
#L	LXI	B,FlipTab
	ADD	C
	MOV	C,A
	ADC	B
	SUB	C
	MOV	B,A
	LDAX	B
	ENDM
;
; The Write routine is entered at the
; interrupt level. We have DSMA containing the memory
; address, DSCT conaining the number of sectors to
; write. When we finish, set DSTAT to 0 and Ioret.
;
NSWrite:
	CALL	CURST
	MVI	A,CWG
	OUT	DCO		; set write on
	LHLD	DSMA
NSWrt1	MVI	E,8
NSWrt2	CALL	CWSUB		; wait for TBE
	XRA	A
	OUT	UDAT
	OUT	UDAT
	DCR	E
	JNZ	NSWrt2		; write 16 zero bytes
	CALL	CWSUB		; wait for TBE
	XRA	A
	OUT	UDAT		; one more zero
	MVI	A,NSSync
	OUT	UDAT		; and then the sync bye
	LXI	D,80H		; Zero check byte, set count
NSWrt3	CALL	CWSUB		; wait for TBE and then shovel.
	MOV	A,M
	INX	H
	MOV	B,A
	XRA	D		; XOR data byte & check byte
	RLC			; left rotate result
	MOV	D,A		; update check byte
	MOV	A,B
	flip			; Flip the bits around
	OUT	UDAT		; and shovel to USRT
	;
	MOV	A,M		; Repeat for second byte
	INX	H
	MOV	B,A
	XRA	D
	RLC
	MOV	D,A		; Update weird check byte in D
	MOV	A,B
	flip
	OUT	UDAT
	;
	DCR	E
	JNZ	NSWrt3		; still going in record
	CALL	CWSUB		; wait for TBE before cksum...
	MOV	A,D
	flip
	OUT	UDAT		; send out check byte
	OUT	UDAT		; twice
	MVI	A,CWH
	OUT	DCO		; start writing ones
	LDA	DSCT
	DCR	A
	STA	DSCT
	JZ	CWRTX		; jmp/ all done.
	MVI	A,CWG
	OUT	DCO		; start write again,
	IN	DST		; clear magic bit
NSWrt4	IN	DCI
	ANI	CSF
	JZ	NSWrt4		; Wait for next sector to come
	JMP	NSWrt1		; around before writing again.
;
; Combined Read-Verify routine for North Star diskettes.
;
NSRdVer LHLD	DSMA		; Put memory addr in HL
NSRV1	CALL	CURS1
	MVI	A,CRG
	OUT	DCO		; read go
	;
	IN	DST		; reset CSF bit in controller
NSRV2	IN	DCI
	ANI	CSF		; seen a hole yet?
	JZ	NSRV2		; wait for the hole
	;
	MVI	A,85		; Found sector hole, now delay
NSRV3	DCR	A		; 85*14=1190 (~600 uS)
	JNZ	NSRV3
	CALL	CURS1		; Reset USRT to lose sync.
	MVI	C,40H		; timeout value
NSRV4	DCR	C		; dcr timeout
	JZ	NPreErr 	; jmp/ no synch in time!
	IN	USTAT
	RAR
	JNC	NSRV4		; wait for USRT to sync up
	;
	LXI	D,80H		; Clear check byte, set count
	LDA	DioOp
	CPI	2		; Verify (2)?
	JNZ	NSRead		; Jmp/ assume Read (1)
	;
NSVer	IN	UDAT
	flip			; Flip the bits around
	CMP	M		; Compare with memory
	JNZ	NVerErr 	; Jmp/ verify error
	INX	H		; Data byte still in A!
	XRA	D		; XOR data byte & check byte
	RLC			; left rotate result
	MOV	D,A		; update check byte
	;
	IN	UDAT		; Repeat for second byte
	flip
	CMP	M
	JNZ	NVerErr
	INX	H
	XRA	D
	RLC
	MOV	D,A		; Update wierd check char in D
	;
NSVer1	IN	USTAT
	RAR
	JNC	NSVer1		; wait for RBF
	DCR	E		; Done with the sector?
	JNZ	NSVer		; Jmp/ nope
	JMP	NSRV5		; Finish up with tail code
;
NSRead	IN	UDAT
	flip			; Flip the bits around
	MOV	M,A		; Move byte to memory
	INX	H		; Data byte still in A!
	XRA	D		; XOR data byte & check byte
	RLC			; left rotate result
	MOV	D,A		; update check byte
	;
	IN	UDAT		; Repeat for second byte
	flip
	MOV	M,A
	INX	H
	XRA	D
	RLC
	MOV	D,A		; Update wierd check char in D
	;
NSRd1	IN	USTAT
	RAR
	JNC	NSRd1		; wait for RBF
	DCR	E		; Done with the sector?
	JNZ	NSRead		; Jmp/ nope
	;
	; the tail code
	;
NSRV5	IN	UDAT		; Check checksum and such
	flip
	CMP	D
	JNZ	NCksErr 	; oops...checksum stinks
	;
	IN	UDAT		; Just throw away second byte
	MVI	A,CRH
	OUT	DCO		; read stop
	LDA	DSCT
	DCR	A
	STA	DSCT
	JNZ	NSRV1		; Jmp/ more sectors to go
;
; All done. Split.
;
CWRTX	XRA	A
CRDX	STA	DSTAT		; store status
	LXI	H,Return
	SHLD	SINT		; clear the bail-out
Return	RET
;
;	North Star Read/Verify error logic.
;
NVerErr MVI	A,4
	JMP	CRVErr
NPreErr MVI	A,2
	JMP	CRVErr
NCksErr MVI	A,3
;
CRVErr	PUSH	PSW		; Common error reporting code
	MVI	A,CRH
	OUT	DCO		; turn off read
	POP	PSW
	JMP	CRDX		; and split reporting error.
;
RHD	EI			; for use by DIO returning!
	POP	H		; end pointer
	POP	D		; junk
	RET			; nice doing business with you.
;
hex	MACRO
#L	DB	0#1H,0#2H,0#3H,0#4H,0#5H,0#6H,0#7H,0#8H
	ENDM
;
;	The following is a 256 byte lookup table to reverse 
;  the bits of a byte.  It sure takes a lot of memory, but 
;  it's the only way to do it fast enough!
;
;		0/8  1/9  2/A  3/B  4/C  5/D  6/E  7/F
FlipTab	
	hex	00,  80,  40,  C0,  20,  A0,  60,  E0	;0
	hex	10,  90,  50,  D0,  30,  B0,  70,  F0
	hex	08,  88,  48,  C8,  28,  A8,  68,  E8	;1
	hex	18,  98,  58,  D8,  38,  B8,  78,  F8
	hex	04,  84,  44,  C4,  24,  A4,  64,  E4	;2
	hex	14,  94,  54,  D4,  34,  B4,  74,  F4
	hex	0C,  8C,  4C,  CC,  2C,  AC,  6C,  EC	;3
	hex	1C,  9C,  5C,  DC,  3C,  BC,  7C,  FC
	hex	02,  82,  42,  C2,  22,  A2,  62,  E2	;4
	hex	12,  92,  52,  D2,  32,  B2,  72,  F2
	hex	0A,  8A,  4A,  CA,  2A,  AA,  6A,  EA	;5
	hex	1A,  9A,  5A,  DA,  3A,  BA,  7A,  FA
	hex	06,  86,  46,  C6,  26,  A6,  66,  E6	;6
	hex	16,  96,  56,  D6,  36,  B6,  76,  F6
	hex	0E,  8E,  4E,  CE,  2E,  AE,  6E,  EE	;7
	hex	1E,  9E,  5E,  DE,  3E,  BE,  7E,  FE
	hex	01,  81,  41,  C1,  21,  A1,  61,  E1	;8
	hex	11,  91,  51,  D1,  31,  B1,  71,  F1
	hex	09,  89,  49,  C9,  29,  A9,  69,  E9	;9
	hex	19,  99,  59,  D9,  39,  B9,  79,  F9
	hex	05,  85,  45,  C5,  25,  A5,  65,  E5	;A
	hex	15,  95,  55,  D5,  35,  B5,  75,  F5
	hex	0D,  8D,  4D,  CD,  2D,  AD,  6D,  ED	;B
	hex	1D,  9D,  5D,  DD,  3D,  BD,  7D,  FD
	hex	03,  83,  43,  C3,  23,  A3,  63,  E3	;C
	hex	13,  93,  53,  D3,  33,  B3,  73,  F3
	hex	0B,  8B,  4B,  CB,  2B,  AB,  6B,  EB	;D
	hex	1B,  9B,  5B,  DB,  3B,  BB,  7B,  FB
	hex	07,  87,  47,  C7,  27,  A7,  67,  E7	;E
	hex	17,  97,  57,  D7,  37,  B7,  77,  F7
	hex	0F,  8F,  4F,  CF,  2F,  AF,  6F,  EF	;F
	hex	1F,  9F,  5F,  DF,  3F,  BF,  7F,  FF
;
CodeLen EQU	$-Code		; Length of Code
	PAGE
;
;	***** DATA SECTION *****
;
Data	  EQU	$		; Start of data area.
;
;********
;
; CDRIV is the currently active drive, MTO is a 
; counter that tells us when to time out the motor. 
; After getting MTIME sector interrupts with no data
; transfer operations, we'll turn off the motor.
; Each time we do a data transfer op, we set MTO to
; the equated value of MTIME.
;
MTO	DS	1		; motor time-out counter
CDRIV	DS	1		; current drive number
;
; TRK1,2,3 are pretty much obvious- the current track
; positions for drives 1, 2, and 3.
;
TRK1	DS	1
TRK2	DS	1
TRK3	DS	1
;
; MUNG1,2,3,4 are scratch pairs. Likely to be anything.
; I'll clean up the documentation as I need these things.
;
MUNG1	DS	2
MUNG2	DS	2
MUNG3	DS	2
MUNG4	DS	2
;
; TRIES is the number of retries we will attempt on an
; operation before deciding to call it a hard error. It is
; initially set to 10 or so.
;
TRIES	DS	1
;
; SINT is the interrupt-level exit for sector pulses.
; When SSEC equals the sector number we're at, we bail
; out to contents of SINT, disabled, and at the 
; interrupt level.
;
SRA2	EQU	0C12H		; sector pulse interrupt
SINT	DS	2
SSEC	DS	2		; bail out and sector number
;
; DSTAT, DSMA, and DSCT are for the device drivers, that
; work at the interrupt level performing disk i/o. DSTAT
; is the I/O status: FF while in progress, 0 for normal
; completion, and <>0 for error completion. DSMA is the
; memory address, and DSCT is the number of sectors.
;
DSTAT	DS	1		; I/O completion status
DTRK	DS	1		; track number we're to be on
DSMA	DS	2		; memory address
DSCT	DS	1		; number of sectors
;
; BUGS counts the number of 102, 103, and 104 errors that have
; been reported from DIO.  BUGS itself is the last code given.
;
BUGS	DS	4		; error counters
;
; STEPTIME is number of msec between step pulses to
; the disk.  SETTLETIME is msec after last step to wait
; before read or write.
;
STEPTIME   DS	1		; Drive step time and
SETTLETIME DS	1		; settle time in mS.
;
Doit	  DS	2		; Two bytes CALLed by DIO
DioOp	  DS	1		; Dio operation code.
;
LogDrive  DS	1		; Map LogDrive into
PhyDrive  DS	1		; PhyDrive in
NPFlag	  DS	1		; NPFlag disk format.
;
DataLen EQU	$-Data
Length	EQU	CodeLen+DataLen 	; Exact length of sssd
Size	EQU	(Length+255)/256	; Size rounded up
TAIL	EQU	$			; Points at Rel block
;
;	End of DIO.
;
	END
