$PAGINATE
$title(Arnold 5 test)
$subtitle(Various disk tests based on the MEJ DTEST code)
$copyright(Copyright (c) 1989, 1990, Amstrad plc.)
$pagewidth=131

        DEFSEG  FloppyDiskTest, CLASS=CODE

        SEG     FloppyDiskTest
;
; Because the lower ROM is near its 16K capacity and "Floppy" is a real biggy
; this is assembled with a 4000h start address (so it can run there) but will
; actually be blown into the upper ROM that lives at 0C000h. The module
; "FloppyGo" contains a simple routine that copies it into place and enters
; it.
;
        PUBLIC  UpperFloppytest

        PUBLIC  setdrAonly              ;in FLOPPY
        PUBLIC  setdrBonly              ;in FLOPPY
        PUBLIC  setbothdrives           ;in FLOPPY
        PUBLIC  setnpattern             ;in FLOPPY
        PUBLIC  setipattern             ;in FLOPPY
        PUBLIC  setronly                ;in FLOPPY
        PUBLIC  setrafterw              ;in FLOPPY
        PUBLIC  setverdata              ;in FLOPPY
        PUBLIC  clearverdata            ;in FLOPPY
        PUBLIC  testsequential          ;in FLOPPY
        PUBLIC  testrandom              ;in FLOPPY
        PUBLIC  quicktest               ;in FLOPPY
        PUBLIC  spintest                ;in FLOPPY
        PUBLIC  format                  ;in FLOPPY

        PUBLIC  chkbreak                ;used in FLOPCMND
        PUBLIC  outnewline              ;used in FLOPCMND
        PUBLIC  outchar                 ;used in FLOPCMND
        PUBLIC  printmessage            ;used in FLOPCMND
        PUBLIC  sysmessage              ;used in FLOPCMND


        extern         cmndproc,cpabandon
        extern         printerror


        EXTERN  ScreenSetMode           ;SUPPORT
        EXTERN  SetCursorPos            ;SUPPORT
        EXTERN  ScreenPrintChar         ;SUPPORT
        EXTERN  PrintStringHL           ;SUPPORT
        EXTERN  HighlightLine           ;SUPPORT
        EXTERN  KeyboardRead            ;SUPPORT

        EXTERN  .NoDiskControllerMess   ;MESSAGES

        extern  messtable               ;FLOPMESS
        extern  sysmestable             ;FLOPMESS
        extern  meshello                ;FLOPMESS
        extern  mesbyebye               ;FLOPMESS
        extern  mesremovediscs          ;FLOPMESS
        extern  mesAdisc                ;FLOPMESS
        extern  mesAandBdiscs           ;FLOPMESS
        extern  meshelp                 ;FLOPMESS
        extern  mesnormdata             ;FLOPMESS
        extern  mesinvdata              ;FLOPMESS
        extern  mesrdonly               ;FLOPMESS
        extern  mesrdwrite              ;FLOPMESS
        extern  mesenver                ;FLOPMESS
        extern  mesdisver               ;FLOPMESS
        extern  messeq                  ;FLOPMESS
        extern  mesrandom               ;FLOPMESS
        extern  mesquick                ;FLOPMESS
        extern  messpin                 ;FLOPMESS
        extern  mesformat               ;FLOPMESS
        extern  mestrack                ;FLOPMESS
        extern  mesformatreadonly       ;FLOPMESS
        extern  mesclrAdrive            ;FLOPMESS
        extern  mesclrBdrive            ;FLOPMESS
        extern  mesAdrive               ;FLOPMESS
        extern  mesBdrive               ;FLOPMESS
        extern  mes2sided               ;FLOPMESS
        extern  mes40tracks             ;FLOPMESS
        extern  mes80tracks             ;FLOPMESS
        extern  mes80tracks             ;FLOPMESS
        extern  messysformat            ;FLOPMESS
        extern  mesarnformat            ;FLOPMESS
        extern  mesbadformat            ;FLOPMESS
        extern  mesdataformat           ;FLOPMESS
        extern  errtrkcount             ;FLOPMESS
        extern  errbadformat            ;FLOPMESS
        extern  errverify               ;FLOPMESS
        extern  errreverify             ;FLOPMESS
        extern  errvok                  ;FLOPMESS
        extern  errvabandon             ;FLOPMESS
        extern  errread                 ;FLOPMESS
        extern  errreread               ;FLOPMESS
        extern  errrok                  ;FLOPMESS
        extern  errrabandon             ;FLOPMESS
        extern  errwrite                ;FLOPMESS
        extern  errrewrite              ;FLOPMESS
        extern  errwok                  ;FLOPMESS
        extern  errwabandon             ;FLOPMESS
        extern  errokspin               ;FLOPMESS
        extern  errslowspin             ;FLOPMESS
        extern  errfastspin             ;FLOPMESS
        extern  errspin                 ;FLOPMESS
        extern  errdrready              ;FLOPMESS
        extern  errformat               ;FLOPMESS
        extern  cmndtable               ;FLOPMESS
        extern  sectortable             ;FLOPMESS
        extern  fmattable               ;FLOPMESS
        extern  initdriveparas          ;FLOPMESS
        extern  leninitparas            ;FLOPMESS

        extern  ?PrintCharXORMask       ;TESTVARS

        extern  ?TempSPStore            ;TESTVARS

        extern  ?sectorbuffer           ;TESTVARS
        extern  ?verifybuffer           ;TESTVARS

        extern  ?formatbuffer           ;TESTVARS

        extern  ?roflag                 ;TESTVARS
        extern  ?verifyflag             ;TESTVARS
        extern  ?driveflag              ;TESTVARS
        extern  ?quicksector            ;TESTVARS
        extern  ?paratable              ;TESTVARS
        extern  ?monto                  ;TESTVARS
        extern  ?moffto                 ;TESTVARS
        extern  ?wrtoff                 ;TESTVARS
        extern  ?hdsettle               ;TESTVARS
        extern  ?steprate               ;TESTVARS
        extern  ?headuld                ;TESTVARS
        extern  ?headld                 ;TESTVARS
        extern  .paralen                ;TESTVARS
        extern  ?rcount                 ;TESTVARS
        extern  ?motorrunning           ;TESTVARS
        extern  ?messdisable            ;TESTVARS
        extern  ?tickerrunning          ;TESTVARS
        extern  ?xpb                    ;TESTVARS
        extern  ?motortimeout           ;TESTVARS
        extern  ?intflag                ;TESTVARS
        extern  ?nmiflag                ;TESTVARS
        extern  ?statusbuffer           ;TESTVARS
        extern  ?bufferaddr             ;TESTVARS
        extern  ?savehl                 ;TESTVARS
        extern  ?savesp                 ;TESTVARS
        extern  ?messtab                ;TESTVARS
        extern  ?usertable              ;TESTVARS
        extern  ?savedbc                ;TESTVARS
        extern  ?savedde                ;TESTVARS


        include         "equates.inc"

using765        equ     -1              ;the disk controller is the ubiquitous
testformat      equ     -1              ;we do want format option.
fdcinterrupts   equ     0               ;fdc cannot interrupt
arnold          equ     -1              ;you know who !
motorcontrol    equ     -1              ;yes, we can control it
tccontrol       equ     0               ;no terminal count

;
; I know the following code must begin at 04000h
;
UpperFloppyTest:
        ld      (?TempSPStore),sp       ;save SP in case we want to abandon

        ld      a,sensedrive
        ld      bc,fdcdata              ;0FB7F
        out     (c),a
        ld      b,255
_DtestWait1:
        djnz    _DtestWait1
        ld      bc,fdcstatus
        in      a,(c)
        cp      90h
        jr      nz,_DtestNoController
        ld      bc,fdcdata
        in      a,(c)
        push    bc
        ld      b,255
_DtestWait2:
        djnz    _DtestWait2
        pop     bc
        in      a,(c)                   ;get the two result bytes
        ld      b,255
_DtestWait3:
        djnz    _DtestWait3
        ld      bc,fdcstatus
        in      a,(c)
        cp      80h
        jr      nz,_DtestNoController

_DtestGotController:
        ld      a,2
        call    ScreenSetMode           ;need wide screen

        call    dtest

_DtestVeryEnd:
        xor     a                       ;clear carry
        ret

_DtestNoController:
        ld      a,1
        call    ScreenSetMode

        ld      de,00A01h               ;(10,4)
        call    SetCursorPos

        ld      hl,.NoDiskControllerMess
        ld      b,1
        call    PrintStringHL

        ld      b,100                   ;length to wait for key press
_DtestNoControlWaitKey:
        push    bc
        call    KeyboardRead
        cp      0FFh
        pop     bc
        jr      nz,_DtestVeryEnd        ;leave early if key pressed
        djnz    _DtestNoControlWaitKey

        jp      _DtestVeryEnd

;******************************************************************************
;*                                                                            *
;*                                                                            *
;*                  Joyce #3 - Disc Diagnostic #3   23 FEB 87                 *
;*                                                                            *
;*                                                                            *
;******************************************************************************
;
; Joyce #3 - Disc Diagnostic comprises of:
;
;
;
; ----------
; ram layout
; ----------
;
; ram variables atart at 8000h because the self test rom add-on uses
; the first 32k of memory space for proms
;
; miscellaneous
; -------------
;
retrycount      teq     1       ;no BIOS retrys
;
errretrys	equ	2	;max number of retry before giving up disc IO
;

;=========
cleardisc:
;=========
;
; for the load & go version of dtest we must wait for all discs to be removed before
; running the default tests.  this is so that the user can remove the
; disc based version from the drive before running dtest
;
; entry: no conditions
;  exit: af bc de hl corrput
;	 all other regs preserved
;
	call	readydtest	;initialise dtest
	call	losediscs	;wait for all discs ro vanish
	jr	dodtest		;run dtest proper

;=====
dtest:
;=====
;
; global entry point to dtest
;
;confirm $.eq.entry

; ====
entry:
; ====
;
; entry point of transient program - set up standard teq areas and call program
;
; entry: no conditions
;  exit: warm boot
;
        call    ddinitialise    ;CJL - important missing item !
        call    readydtest      ;initialise dtest
;confirm $.eq.dodtest		;run dtest proper

;=======
dodtest:
;=======
;
; run dtest
; assumes dtest is initialised
;
; entry: no conditions
;  exit: warm boot
;
; run program
;
	call	dtestmain
;
; exit program
;
; say bye bye
;
	ld	a,mesbyebye	;bye bye message
	call	printmessage
;
;        jp      utclose         ;close utility package
;
; The following is the main RET out of DTEST. In MEJ terms it was a jump to
; UTCLOSE but we don't have any of that utility stuff. (cliff)
;
        xor     a
        ld      (?PrintCharXORMask),a
        ld      bc,0FA7Eh
        out     (c),a           ;switch off disk motor
        ret


; console 'main    .asm:11 - 16 dec 86 - DTEST high level'
;
;==========
readydtest:
;==========
;
; initialise dtest
;
; entry: no conditions
;  exit: af bc de hl corrupt
;        all other regs preserved
;
; God alone knows what the next call did in the MEJ stuff but I have removed
; it cos I haven't got the utility stuf (Cliff)
;
;        call    utwarmstart     ;wake up utility package
;
; output sign on message
;
; The following (*) is initialisation for the MEJ message handling utility stuff
; that I haven't got (Cliff)
;
        ld      hl,messtable
        call    setmesstab      ;tell message handler addr of message table

        ld      b,3
        ld      d,0
DTESTInvert:
        push    bc
        push    de
        call    HighlightLine   ;invert top 3 lines
        pop     de
        inc     d
        pop     bc
        djnz    DTESTInvert
;
	ld	A,meshello
	call	printmessage	;say hello
;
; set retry count to 1 (so we report all errors)
;
	ld	a,retrycount
	call	ddsetretry	;kill dd retrys
;
; setup default parameters
;
	call	setnpattern	;default to normal worst case data pattern
;        call    setrafterw      ;read after write testing
        call    setronly        ;MAC asked for it to default to Read Only
;        call    clearverdata      ;enable data verification - CJL
;
; see how many drives we have & then ask user to insert scratch discs
;
	jp	ddonmotor	;start the motors

;=========
dtestmain:
;=========
;
; DTEST high level
;
; entry: no conditions
;  exit: af bc de hl corrupt
;        all other regs preserved
;
	ld	a,driveB
	call	ddgetdrstatus	;get drive B status
	and	st3wprotect + st3drready ;zero true if drive not fitted
;
	ld	d,0		;want to wait for drive readyso dont invert status
	ld	bc,mesAdisc*256 + Aonly ;assume drive A only fitted
        jr      z,_10m          ;jump if drive B not fitted
;
	ld	bc,mesAandBdiscs*256 + Aonly + Bonly ;prompt for both drive .cc. default to test both
_10m:   call    getalldrives    ;wait for all drives ready
	call	c,getalldrives	;and again (for debouncing)
	call	c,getalldrives	;& again to make absolutly sure
        jr      nc,_10m         ;loop if any drive not ready
;
; establish format of drives
;
	ld	a,c
        ld      (?driveflag),a   ;set default drives to test
	dec	a		;zero false if drive B fitted
; confirm Aonly.eq.1
; confirm Aonly+Bonly.gt.1
	call	nz,ddautoselect	;if fitted get format of drive B
	dec	c		;& again
	call	nz,printdrformat ;display format of drive B if fitted
;
	ld	e,driveA
	call	ddautoselect	;get format of drive A
	call	printdrformat
;
	call	ddstopmotor	;start motor off timeout
;
; run the command processor
;
	ld	hl,cmndtable	;table of cmnds
	call	cmndproc	;process cmnds
;
; get user to remove all discs
;
	call	ddonmotor	;no use looking for drive ready if motors are off
	call	losediscs	;wait for user to remove all discs
	jp	ddstopmotor	;start motor off timeout

;=========
losediscs:
;=========
;
; wait for user to remove all discs
;
; entry: no conditions
;  exit: af bc de hl corrupt
;	 all other regs preserved
;
	ld	b,mesremovediscs ;'Remove all discs from drives'
;
_10ld:  ld      d,st3drready    ;wait for drive not ready
	ld	c,Aonly + Bonly	;must wait till both drives not ready
; confirm $.eq.getalldrives	;wait for user to remove all discs
;

;============
getalldrives:
;============
;
; wait for all drives to go either ready or not ready
;
; entry: b = message number, zero if already printed
;	 c = 1 if drive A only or 3 if A and B
;	 d = status bit to invert
;  exit: if output prompt message then B = 0
;	 all af corrupt
;	 all other regs preserved
;
; see if drive A is ready
;
_10gad:
        ld      e,driveA        ;test drive A
	call	getdrive	;check drive A ready status
	inc	e		;drive B number
; confirm driveB.eq.(driveA+1)
	bit	1,c		;is drive B fitted
; confirm Bonly.eq.bit1
	scf			;assume no drive B
	call	nz,getdrive	;if drive B fitted then check ready status
        jr      nc,_10gad       ;if had to wait on B check we still have A
;
	ret

;========
getdrive:
;========
;
; wait for drive ready
; if not ready print drive prompt only if not already printed
; then wait for drive to become ready
;
; entry: b = message number, zero if already printed
;	 e = drive to test
;	 d = status bit to invert
;  exit: if carry false
;	    then had to wait for drive to become ready
;		 b = 0
;	 if carry true
;	    then drive was already ready
;		 b preserved
;	 always
;	 all other flags A corrupt
;	 all other regs preserved
;
        call    _20getdrive     ;test the status bit
	scf			;if so then return with carry true
	ret	nz		;fin if really ready
;
; tell user what to do
;
	ld	a,b
	or	a		;zero true if message already printed
	ld	b,0		;mark prompt issued
	call	nz,printmessage	;issue prompt if not already printed
;
; wait till drive is in correct state
;
_10gd:
        push    af
        push    bc
        push    de
        push    hl
        call    KeyboardRead
        cp      0FFh
        jp      z,GetDrvNoQuit      ;if a key pressed leave disk test early
        ld      a,0
        ld      bc,0FA7Eh
        out     (c),a
        xor     a
        ld      sp,(?TempSPStore)       ;restore stack to entry state
        ret                             ;then exit from DTEST

GetDrvNoQuit:
        pop     hl
        pop     de
        pop     bc
        pop     af
        call    _20getdrive     ;test ready state
        jr      z,_10gd         ;loop if notready
;
	ret			;return carry false

;===========
_20getdrive:
;===========
;
; test the state of the ready signal
;
	ld	a,e		;drive number
	call	ddgetdrstatus
	and	st3drready	;is drive ready now?
	xor	d		;get it the right way round
;
	ret



; console 'options .asm:10 - 15 dec 86 - command options'
;
;=============
setbothdrives:
;=============
;
; set both drives A  and  B for testing
;
; entry: no conditions
;  exit: psw bc de hl corrupt
;	 all other regs preserved
;
	ld	hl,0		;dont change number of tracks on either drive
	call	setdrAonly	;establish format of drive A
	ld	e,driveB
	call	estabformat	;establish format of drive B
;
	ld	a,Aonly + Bonly	;set drive A and B
        ld      (?driveflag),a
;
	ret

;==========
setdrAonly:
;==========
;
; set drive A only for testing
;
; entry: hl = optional number tracks on drive (40, 80 or 0 to leave unchanged)
;  exit: psw bc de hl corrupt
;	 all other regs preserved
;
	ld	b,mesclrBdrive
	ld	de,Aonly * 256 + driveA ;set drive A only .cc. drive A select code
	jr	setdrcommon	;common code

;==========
setdrBonly:
;==========
;
; set drive B only for testing
;
; entry: hl = optional number tracks on drive (40, 80 or 0 to leave unchanged)
;  exit: psw bc de hl corrupt
;	 all other regs preserved
;
	ld	b,mesclrAdrive
	ld	de,Bonly * 256 + driveB;set drive B only .cc. drive B select code
; confirm $.eq.setdrcommon

;===========
setdrcommon:
;===========
;
; common code to establish drive format
;
; entry: hl = optional number tracks on drive (40, 80 or 0 to leave unchanged)
;	  b = message to clear 'other' drive status line
;	  e = drive number
;	  d = drive bit code
;  exit: af hl corrupt
;	 all other regs preserved
;
; check we have a valid track count
;
	ld	a,h
	or	a
        jr      nz,_30sdc       ;jump if track count too big
;
	or	l		;zero true if track count to remain the same
        jr      z,_20sdc        ;jump if no need to change the track count
;
	cp	trackcount / 2	;is it 40 tracks
; confirm (trackcount/2).eq.40
	ld	c,false		;assume 40 track drive
        jr      z,_10sdc        ;jump if so setting 40 track drive
;
	cp	trackcount 	;is it 80 tracks
        jr      nz,_30sdc       ;jump if not (invalid parameter)
;
	dec	c		;want to set xpb80tracks flag true
; confirm ((false-1).and.0FFFFh).eq.true
;
; set new track count
;
_10sdc: ld      a,xpb80tracks
	call	ddpointxpb	;ref relevant track flag
	ld	(hl),c		;set new state of 80 track flag
;
; establish the format of the drive
;
_20sdc: call    estabformat     ;set up format parameters for drive
	ld	a,d
        ld      (?driveflag),a   ;save drive select code
;
	ld	a,b
        jp      jmppmessage     ;clear stataus area of other drive
;
; report invalid track count
;
_30sdc: ld      a,errtrkcount   ;'You may only specify 40 or 80 tracks per drive
	call	printmessage	;report error
	jp	cpabandon	;& abandon all tests

; ==========
setnpattern:
; ==========
;
; fill the sector buffer with the normal worst case data pattern
; worst case data pattern is a contineous bit stream of 110
; thus we use the three hex bytes DB 6D B6
; note as 3 does not go into 512 we write 2 extra bytes beyond the end of the buffer
;
; entry: no conditions
;  exit: psw corrupt
;	 all other regs preserved
;
	push	hl
	push	bc
;
        ld      hl,?sectorbuffer
	ld	b,sectorsize/3 + 1 ;number of groups of 3 bytes to put into buffer
				;nb: overflows 512 by 2 bytes
;
_10snp: ld      (hl),0DBh       ;insert DB
	inc	hl

	ld	(hl),06Dh	;insert 6D
	inc	hl
;
	ld	(hl),0B6h	;insert B6
	inc	hl
;
        djnz    _10snp          ;loop if buffer not full
;
	pop	bc
	pop	hl
;
        exx
        push    de
        ld      de,0100h        ;line 1 column 0
        exx

        ld      a,mesnormdata
        call    printmessage

        exx
        pop     de
        exx

;        jr      jmppmessage     ;report using normal data pattern

        ret

;===========
setipattern:
;===========
;
; fill the sector buffer with the inverse worst case data pattern
; inverse worst case data pattern is a contineous bit stream of 001
; thus we use the three hex bytes 24 92 49
; note as 3 does not go into 512 we write 2 extra bytes beyond the end of the buffer
;
; entry: no conditions
;  exit: psw corrupt
;	 all other regs preserved
;
	push	hl
	push	bc
;
        ld      hl,?sectorbuffer
	ld	b,sectorsize/3 + 1 ;number of groups of 3 bytes to put into buffer
				;nb: overflows 512 by 2 bytes
;
_10sip: ld      (hl),024h       ;insert 24
	inc	hl

	ld	(hl),092h	;insert 92
	inc	hl
;
	ld	(hl),049h	;insert 49
	inc	hl
;
        djnz    _10sip          ;loop if buffer not full
;
	pop	bc
	pop	hl
;
        exx
        push    de
        ld      de,0100h        ;line 1 column 0
        exx

	ld	a,mesinvdata
;        jr      jmppmessage     ;report using inverse data pattern
        call    printmessage

        exx
        pop     de
        exx

        ret

;========
setronly:
;========
;
; set read only testing
;
; entry: no conditions
;  exit: psw corrupt
;	 all other regs preserved
;
        ld      a,true
        ld      (?roflag),a
;
        exx
        push    de
        ld      de,0128h
        exx

        ld      a,mesrdonly
;        jr      jmppmessage     ;report read only testing
        call    printmessage

        exx
        pop     de
        exx
        jp      clearverdata

;==========
setrafterw:
;==========
;
; set read after write testing
;
; entry: no conditions
;  exit: psw corrupt
;	 all other regs preserved
;
	xor	a
; confirm: false equ 0
        ld      (?roflag),a      ;set read after write
;
        exx
        push    de
        ld      de,0128h
        exx

	ld	a,mesrdwrite
;        jr      jmppmessage     ;report read after write testing
        call    printmessage

        exx
        pop     de
        exx
        jp      setverdata      ;and switch on data verification

;==========
setverdata:
;==========
;
; enable data verification
;
; entry: no conditions
;  exit: psw corrupt
;	 all other regs preserved
;
        ld      a,true
        ld      (?verifyflag),a
;
        exx
        push    de
        ld      de,0228h
        exx

	ld	a,mesenver
;        jr      jmppmessage     ;report data verification enabled
        call    printmessage

        exx
        pop     de
        exx
        ret

;============
clearverdata:
;============
;
; disable data verification
;
; entry: no conditions
;  exit: psw corrupt
;	 all other regs preserved
;
	xor	a
        ld      (?verifyflag),a
;
        exx
        push    de
        ld      de,0228h
        exx

        ld      a,mesdisver
; confirm $.eq.jmppmessage	;report data verification disabled
        call    printmessage

        exx
        pop     de
        exx
        ret

;===========
jmppmessage:
;===========
;
; relative jump label to jump to printmessage
;
	jp	printmessage	;report data verification disabled


; console 'tests   .asm:8  - 15 dec 86 - test options'
;
;==========
testrandom:
;==========
;
; total random disc test
; test all sectors of all tracks in random track order
;
; entry: no conditions
;  exit: af bc de hl corrupt
;	 all other regs preserved
;
	ld	a,mesrandom
	call	printmessage	;report running random track test
;
; test all tracks psuedo randomly
;
	ld	d,0		;first track to test
;
_10tr:
        ld      a,d
        cp      40
        jr      nc,randomnoprint
        call    printtrack
randomnoprint:
        call    testtrack       ;test this track
	call	randomnumber	;move to next track
	ld	a,d
	or	a		;are we back on track zero yet
        jr      nz,_10tr        ;loop if not
;
	ret

;==============
testsequential:
;==============
;
; sequential total disc test
; test all sectors of all tracks in sequential track order, starting from track 0
;
; entry: no conditions
;  exit: af bc de hl corrupt
;	 all other regs preserved
;
	ld	a,messeq
	call	printmessage	;report running sequential track test
;
; test all tracks sequentially
;
	ld	d,firsttrack	;first track to test
;
_10ts:
        call    printtrack
        call    testtrack       ;test this track
	inc	d		;move to next track
	ld	a,d
        cp      40              ;CJL
;        cp      lasttrack + 1   ;have we tested all tracks yet
        jr      c,_10ts         ;loop if not
;
	ret

;=========
testtrack:
;=========
;
; test all sectors on current track
;
; entry: d = track to test (0FFh if no valid track number)
;  exit: af e hl corrupt
;	 all other regs preserved
;
	ld	hl,trackreadwrite ;test to run
;confirm	<$.eq.testdrives> ;run test on currently selected drives

;==========
testdrives:
;==========
;
; run a test on all selected drives
;
; entry: hl = test to run
;	  d = track to test
;  exit: af corrupt
;	 all other regs preserved
;
	push	de
	push	bc
;
; test drive A if enabled
;
        ld      a,(?driveflag)
	rrca			;test drive A if bit0 none zero
	ld	e,driveA	;assume drive A enabled
	push	af
        call    c,_05testdrives ;test drive A if enabled
	pop	af
;
	rrca			;test drive B if bit1 none zero
	inc	e		;e = driveB
        call    c,_05testdrives ;test drive B if enabled
;
	pop	bc
	pop	de
	jp	chkbreak	;check for break

;=============
_05testdrives:
;=============
;
; if the current track number is greater then 39 then only
; run the test if the current drive is an 80 track one
;
; see if the track number is greater than 39
;
	ld	a,d
	cp	trackcount / 2	;is track number less than 40
        jr      c,_10testdrives ;run test if track number valid for 40 track drive
;				;(implict return)
;
; check that the track number is valid
;
	ld	a,d
	inc	a
; confirm ((true+1).and.0FFFFh).eq.0
        jr      z,_10testdrives ;run test if drive number does not matter
				;(implicit return)
;
; we are about to test a track in the range 40 to 79 - only do this
; if we have an 80 track drive
;
	push	hl
	ld	a,xpb80tracks
	call	ddpointxpb	;ref relevant entry in xpb
	ld	a,(hl)
	or	a		;zero false if 80 track drive
; confirm true
	pop	hl
	ret	z		;fin if 40 track drive
;
; confirm $.eq._10testdrives    ;we have 80 track drive (implicit return)

;=============
_10testdrives:
;=============
;
; test side 0 of current drive
;
	push	hl
;
	res	2,e		;clear side 1 bit
; confirm headselect.eq.bit2
        call    _20testdrives   ;test side 0
;
	ld	a,xpbside
	call	ddpointxpb	;ref side flag for drive
	ld	a,(hl)
	or	a		;zero false if 2 sided
;
	pop	hl
	ret	z		;fin if not 2 sided
;
; test side 1 of current drive
;
	set	2,e		;set side 1 bit
; confirm headselect.eq.bit2
; confirm $.eq._20testdrives

;=============
_20testdrives:
;=============
;
; run a test on a specific side of a drive
;
	push	hl
	push	de
	push	bc
	push	af
;
	call	pchlinstruction	;run the test
;
	pop	af
	pop	bc
	pop	de
	pop	hl
	ret

;===============
pchlinstruction:
;===============
;
; not another ...
;
	jp	(hl)

;==============
trackreadwrite:
;==============
;
; test current track on current drive
;
; entry: d = track to test
;	 e = drive & side to test
;  exit: af bc corrupt
;	 all flags & other regs preserved
;
	call	getformat	;get drive format parameters
;
; write the track if enabled
;
        ld      a,(?roflag)
	or	a		;zero => write enable
	call	z,writetrack	;write the track
; confirm $.eq.verifytrack	;check track is ok

;===========
verifytrack:
;===========
;
; verify all sectors on the current track
;
; entry: e = drive & side numbers
;	 d = track number
;        c = first sector on track
;        b = number of sectors on track
;  exit: af bc corrupt
;        all other regs preserved
;
; verify the sectors on this track
;
vt10:	call	diverify	;verify a sector
	inc	c		;next sector
	djnz	vt10		;loop till all sectors verified
;
	ret

;==========
writetrack:
;==========
;
; write a track to the destination disc
;
; entry: e = drive & side numbers
;	 d = track number
;        c = first sector on track
;        b = number of sectors on track
;  exit: af corrupt
;        all other regs preserved
;
	push	bc
;
; write the sectors on this track
;
wt10:	call	diwrite		;write a sector
	inc	c		;next sector
	djnz	wt10		;loop till all sectors written
;
	pop	bc
	ret


; console 'quick   .asm:4  - 15 dec 86 - quick disc test'
;
;=========
quicktest:
;=========
;
; quick test
; all tracks are tested first from track 0 to the last track then
; from the last track to 0.  for each pass of the test the procedure is:
;
; for first 2 tracks & last 2 tracks
;	 read & check the last, first & second sectors
;	 write inverse worst case data to last & first sectors
;	 read & check last, first & second sectors
;	 write normal worst case data to last & first sectors
;	 read & check last, first & second sectors
;
; for middle 36 tracks
;	read sector n + 1, where  is the sector read on the previous track
;
; note that for 80 track drives the middle to track (38 & 39)
; get treated as if they were outer 2 tracks
;
; joyce discs are formatted as follows
;
; track    sector order
;   0      1 6 2 7 3 8 4 9 5
;   1	   9 5 1 6 2 7 3 8 4
;   2      8 4 9 5 1 6 2 7 3
;   3      7 3 8 4 9 5 1 6 2
;   ...etc
;
; entry: no conditions
;  exit: af bc de hl corrupt
;	 all other regs preserved
;
	ld	a,mesquick
	call	printmessage	;report running quick test
;
; test drive moving head inwards
;
	ld	hl,1*256 + 0	;offset to next sector .cc. first sector to test
	ld	de,1		;first track to test .cc. offset to next track
        call    _10quicktest
;
	ld	hl,3*256 + 0	;offset to next sector .cc. first sector to test
	ld	h,3		;offset in opposite direcction
	dec	d		;d = last track
	ld	e,-1		;offset to next track
; confirm $ eq _10quicktest>

;============
_10quicktest:
;============
;
; quick test, moving head in specific direction
;
        ld      (?quicksector),hl ;save first sector & starting sector
	ld	b,trackcount	;number of tracks to test
;
_20qt:  ld      hl,_40quicktest ;test this track
	call	testdrives
;
; move to next track
;
_30qt:  ld      a,d
	add	a,e		;add offset to next track
	ld	d,a
        djnz    _20qt           ;loop till all tracks tested
;
	ret

;============
_40quicktest:
;============
;
; test current track
; if track is one of the outer 2 then run read/write test on it
; otherwise simply read the current sector
;
; entry: e = current drive & side
;	 d = current track
;  exit: af hl corrupt
;	 all other regs preserved
;
        call    printtrack
        call    getformat       ;c = first sector on track
;
; see if testing 1 of outer 2 tracks
;
	ld	hl,sectortable	;look up table
	ld	a,d		;current track
	call	caselookup	;get string of sector numbers
        jr      c,_60qt         ;jump if found track in table
;
; track is not on outer edge so simply read the current sector
;
	call	nextqsector	;get next sector to test
	add	a,c
	ld	c,a		;c = required sector to read
	jp	diverify	;read it
;
; outer track so run special read/write test
; check for normal worst case data
;
_60qt:  call    setnpattern
	ld	b,3
	call	readsectors	;read & check the last, first & second sectors
;
; fin if read only
;
        ld      a,(?roflag)
	or	a
	ret	nz		;fin if drive read only
;
; write & check inverse worst case
;
	call	setipattern	;set inverse data
	ld	b,2		;read last & first sectors
        call    _70quicktest    ;write & read check inverse data
;
; write & check normal worst case
;
	call	setnpattern	;set normal worst case data pattern
	ld	b,3		;want to read last, first & second sectors
; confirm $ eq _70quicktest>    ;write & read check
;
;============
_70quicktest:
;============
;
; write & read check last, first & second sectors
;
	call	writesectors	;write normal worst case data to last & first sectors
; confirm $ eq readsectors>	;read & check last, first & second sectors

;===========
readsectors:
;===========
;
; read & check last, first & second sectors on current track
;
; entry: d = track number
;	hl = ref list of 3 sectors to read
;	 e = drive number
;	 c = first logical sector on disc
;  exit: af b corrupt
;	 all flags & other regs preserved
;
	push	hl
;
_10rs:  push    bc
	ld	a,(hl)		;next sector to read
	add	a,c		;offset to first sector
	ld	c,a
	call	diverify	;read & check sector
	pop	bc
;
	inc	hl
        djnz    _10rs           ;loop till all read
;
	pop	hl
	ret

;============
writesectors:
;============
;
; write last & first sectors on current track
; *** assumes drive is not read only ***
;
; entry: d = track number
;	hl = ref list of 2 sectors to read
;	 e = drive number
;	 c = first logical sector on disc
;  exit: af corrupt
;	 all other regs preserved
;
	push	hl
	push	bc
;
	ld	b,2		;only write last & first, never second
;
_10ws:  push    bc
	ld	a,(hl)		;next sector to read
	add	a,c		;offset to first sector
	ld	c,a
	call	diwrite		;read & check sector
	pop	bc
;
	inc	hl
        djnz    _10ws           ;loop till all read
;
	pop	bc
	pop	hl
	ret

;===========
nextqsector:
;===========
;
; calculate the next sector number for the quick test
;
; entry: no conditions
;  exit: a = next sector number
;	 af hl corrupt
;	 all other regs preserved
;
        ld      hl,(?quicksector) ;l = current sector, H = offset to next
	ld	a,l
	add	a,h		;next sector
	cp	jfsecpertrack	;is sector number still valid
        jr      c,_10nqs        ;jump if so
;
	sub	jfsecpertrack	;force into range
_10nqs: ld      l,a
        ld      (?quicksector),hl ;save for next time
;
	ret


; console 'spin    .asm:4  - 15 dec 86 - disc spin timing test'
;
motoffretrys	equ	10	;number of times to turn off motor to make drive not ready (5 seconds)
;
maxspintime	equ	205 * (1000/10) ;max spin time in 10us units
minspintime	equ	195 * (1000/10) ;min spin time in 10us units

;========
spintest:
;========
;
; check disc is spinning at the correct rate
;
; entry: no conditions
;  exit: af bc de hl corrupt
;	 all other regs preserved
;
	ld	a,messpin
	call	printmessage	;print test name
;
; run spin speed test on all drives
;
        ld      hl,_10spintest  ;spin speed test routine
        ld      d,true        ;mark track number not used (by testdrives routine)
	call	testdrives
;
; run drive not ready test on all drives
;
	call	ddoffmotor	;turn off the motors to make the drives not ready
        ld      hl,_30spintest  ;drive not ready test
	jp	testdrives

;===========
_10spintest:
;===========
;
; run test on first sector of current track
;
	ld	a,xpbtrack
	call	ddpointxpb
	ld	d,(hl)		;D = current track
;
	call	ddspeed		;run spin speed test
        jr      nc,_20sp        ;jump if hardware error
;
; deal with results
;
	push	de		;save drive number (e)
	push	af		;save code
;
	ex	de,hl		;results in de
	ld	hl,maxspintime	;assume running too slowly
	add	hl,de		;hl = spin time if running too slowly
	dec	a
; confirm spinslow.eq.1
        jr      z,_30spt        ;jump if spinning too slowly
;
; if spin ok then subtrack results from max time to give spin speed
; if spin too fast the subtrack results from min time to give speed
;
	ld	hl,minspintime	;assume spin too fast
	dec	a
; confirm spinfast.eq.2
        jr      z,_20spt        ;jump if too fast
;
	ld	hl,maxspintime	;spin ok so subtrack result from max time
;
_20spt: or      a               ;carry false
	sbc	hl,de		;subtrack result from time in hl
;
; print result & spin time
; note that the spin time is in 20us units but this is for 2 spins of
; the disc, which means they can be treated as 10us units for 1 spin
;
; we print the number add a zero afterwards to put in in 1us units
;
_30spt: ld      b,h
	ld	c,l		;result in bc
;
	pop	af		;recover error code
	pop	de		;recover drive number
;
; print results of test
;
	add	a,errokspin	;add offset to message number
; confirm spinok.eq.0
; confirm errslowspin-errokspin.eq.spinslow
; confirm errfastspin-errokspin.eq.spinfast
	cp	errokspin	;zero true if ok
	jp	z,printmessage	;not an error if ok
;
	jp	printerror	;report error
;
; report hardware error
;
_20sp:  ld      a,errspin       ;'FDC Timedout in spin speed test'
	jp	printerror

;===========
_30spintest:
;===========
;
; check drive ready signal goes false
;
	ld	b,motoffretrys-1;number of times to turn off motor
;
_40sp:  call    ddgetdrstatus   ;get drive status
	and	st3drready	;drive must not be ready
        push    af              ;CJL
        call    z,ddonmotor     ;CJL wind the motor up again
        pop     af              ;CJL
        ret     z               ;fin if drive not ready
;
	call	ddoffmotor	;turn off motors again (waits .5 second)
        djnz    _40sp           ;loop til 5 seconds is up
;
; report drive failed to go not ready
;
	ld	a,errdrready	;'Failed to clear drive ready status'
	jp	printerror	;report error & exit


; console 'format  .asm:4  - 15 dec 86 - disc formatting'

        %if      testformat

;======
format:
;======
;
; format all sides of current drives
;
; entry: hl = number of sides to format
;  exit: af bc de hl corrupt
;	 all other regs preserved
;
	ld	a,mesformat
	call	printmessage	;print current test
;
;        ld      c,l             ;c = count of sides to format
        ld      c,0             ;always just one side CJL
;
        ld      hl,_10format
        ld      d,true        ;mark track number not used (by testdrives routine)
	jp	testdrives	;format each side of each drive in turn

;=========
_10format:
;=========
;
; format current side of current drive
;
; entry: c = 0 if formatting side 0 only
;	 e = current drive & side to test
;  exit: af bc d hl corrupt
;	 alll other regs preserved
;
; set up side flag in xpb
;
	ld	a,xpbside
	call	ddpointxpb	;ref side select flag in xpb
	ld	(hl),c		;set up side flag
	bit	2,e		;if side 0 update formatstatus
; confirm headselect.eq.bit2
	call	z,printdrformat	;report formatting of 1 or 2 sides
;
        ld      a,(?roflag)
	or	a
        push    af
        ld      a,mesformatreadonly
        call    nz,printmessage
        pop     af
	ret	nz		;fin if read only
;
	ld	d,0		;first track to format
;
; get number of tracks to format (40 or 80)
;
	ld	a,xpb80tracks
	call	ddpointxpb	;ref 80 track flag
	ld	a,(hl)
	or	a		;zero false if 80 tracks
; confirm true
	ld	b,trackcount	;number of tracks to format
        jr      nz,_15f         ;jump if 80 track drive
;
	ld	b,trackcount/2	;else must be 40 track drive
;
_15f:   ld      hl,fmattable    ;interleave table
;
; format current track
; set up pointer to sector interleave table
;
_20f:   call    chkbreak        ;look for & process break
	push	bc
;
	call	getformat	;b = sector count, c = first physical sector
	push	hl
;
; setup format buffer
;
        call    printtrack

        ld      hl,?formatbuffer
;
; track number
;
_30f:   ld      (hl),d          ;set the track number
	inc	hl
;
; head number
;
	ld	a,e
	and	headselect	;mask head select
	rrca
	rrca			;into bit 0
; confirm headselect.shr.2.eq.bit0
	ld	(hl),a		;head number
	inc	hl
;
; sector number
;
	ld	a,c		;get first physical sector
	ex	(sp),hl		;hl = ref interleave table
	add	a,(hl)		;add offset
	inc	hl		;ref next sector number
	ex	(sp),hl		;hl ref format buffer
	ld	(hl),a		;physical sector number
	inc	hl
;
; sector size
;
	ld	(hl),2		;always 512 byte sectors
	inc	hl
;
; next sector
;
        djnz    _30f            ;loop till buffer set up
;
; format the track
;
        ld      hl,?formatbuffer ;reset pointer to buffer
	call	ddformat	;format the track
;
; report any error
;
	ld	a,errformat	;'track format error'
	call	nc,printerror	;report error
;
	pop	hl		;hl = ref next sector on current track !!
	dec	hl
	dec	hl		;hl = first sector number on next track
	ld	bc,fmattable + jfsecpertrack
;
; CPHLBC was in the MEJ utility so I have just tagged it onto the end of this
; file. (cliff)
;
        call    cphlbc          ;are we still ref first half of table
        jr      c,_40f          ;jump if so
;
	ld	bc,-jfsecpertrack
	add	hl,bc		;bring table pointer back into first half of table
;
_40f:   pop     bc
	inc	d		;next to format
        djnz    _20f            ;loop till all formatted
;
	ret

        %endif   ;testformat


;==========
printtrack:
;==========
;
; A CJL Production !
;
; This stores the current DE' cursor pos, moves the cursor to (70,2) and
; prints the current track value that is held in D. The old DE' is then
; restored.
;
        exx
        push    de              ;save old cursor
        ld      de,0246h        ;move to 70,2
        exx

        ld      a,mestrack
        call    printmessage

        exx
        pop     de
        exx
        ret


; console 'random  .asm:4  - 15 dec 86 - random number generator'
;
;============
randomnumber:
;============
;
; produce a random number in the range 0 to 39
; formula:
;	  for i = 1 to 8 do
;		A = A .sl.1 + ((bit 5 XNOR bit 0) into bit 0)
;	  od
;
; entry: d = last random number
;  exit: d = new random number
;        flags b corrupt
;        all other regs preserved
;
rn10:	ld	b,8		;go round loop 8 times
;
rn20:	ld	a,d
	and	21h
	jp	po,rn30		;jump if not even parity
;
	scf			;must add a 1
;
rn30:	ld	a,d
	rla
	ld	d,a
;
	djnz	rn20
;
	and	3Fh		;mask out 64 & 128
	ld	d,a
	cp	trackcount
	jr	nc,rn10		;loop if number too big
;
	ret


; console 'discio  .asm:6  - 15 dec 86 - disc read,write & verify routines'
;
;========
diverify:
;========
;
; verify that a sector has been written correctly
; report error if failed
;
; entry: e = drive number
;        d = track number
;        c = sector number
;  exit: af corrupt
;        all other regs preserved
;
	call	doverify	;verify the sector
	ret	c		;fin if ok (or read failed)
;
	push	bc
;
; report error
;
	ld	a,errverify	;'Failed to verify sector correctly'
	call	printerror	;report error
;
; try to re-verify sector
;
	ld	b,errretrys	;max number of re-trys
;
div10:	call	doverify	;try to verify sector
	ld	a,errvok	;'re-verified sector successfully'
        jr      c,_20div        ;jump if ok this time
;
; report error
;
	ld	a,errreverify	;'Failed to re-verify sector correctly'
	call	printerror	;report error
	djnz	div10		;loop till all retrys done
;
; report hard error
;
	ld	a,errvabandon	;'Sector verify abandoned'
;
_20div: call    printmessage
;
	pop	bc
	ret

;========
doverify:
;========
;
; read sector  and  verify data is correct
;
; entry: e = drive number
;        d = track number
;        c = sector number
;  exit: carry true => sector verified ok
;        carry false => sector verified failed
;        a corrupt
;        all other regs preserved
;
	call	diread		;read the sector (return carry false if read error)
        ld      a,(?verifyflag)  ;zero true if verify disabled
	adc	a,0		;carry true read ok & verify enabled
	ccf
	ret	c		;fin if read failed or verify disabled - no need to verify
;
	push	hl
	push	de
	push	bc
;
; sector read ok - verify the data is what we wrote
;
        ld      hl,?verifybuffer ;data read back from disc
        ld      de,?sectorbuffer ;data written to disc
	ld	bc,sectorsize	;number of bytes to verify
;
_10dv:  ld      a,(de)          ;get byte read back
	xor	(hl)		;is it what we wrote? (carry false)
        jr      nz,_20dv        ;jump if not
;
	inc	hl
	inc	de
	dec	bc
	ld	a,b
	or	c
        jr      nz,_10dv        ;loop till all bytes compared
;
; all bytes compared ok
;
	scf			;mark verify ok
;
_20dv:  pop     bc
	pop	de
	pop	hl
	ret

;======
diread:
;======
;
; read a sector into the verify buffer
; report error if failed
;
; entry: e = drive number
;        d = track number
;	 c = sector number
;  exit: carry true => sector read ok
;        carry false => sector read failed
;        a corrupt
;        all other regs preserved
;
	push	hl
;
        ld      hl,?verifybuffer ;temp buffer to read sector into
	call	ddreadsector	;read the sector
        jr      c,_30dir        ;jump if read ok
;
	push	bc
;
; report read error
; nb: readsector returns the addr of the status buffer in HL - so remember hl is corrupt
;
	ld	a,errread	;'Failed to read sector correctly'
	call	printerror	;report error
;
; try to re-read sector
;
	ld	b,errretrys	;max number of re-trys
;
_10dir: ld      hl,?verifybuffer ;temp buffer to read sector into
	call	ddreadsector	;try to read sector
	ld	a,errrok	;'re-read sector successfully'
        jr      c,_20dir        ;jump if read ok this time
;
; report re-read error
;
	ld	a,errreread	;'Failed to re-read sector correctly'
	call	printerror	;report error
;
	dec	b		;any more retrys
        jr      nz,_10dir       ;loop if so
;
; report hard error
;
	ld	a,errrabandon	;'Sector read abandoned'
;
_20dir: call    printmessage
;
	pop	bc
;
_30dir: pop     hl
	ret

;=======
diwrite:
;=======
;
; write a sector - report error if failed
;
; entry: e = drive number
;        d = track number
;	 c = sector number
;  exit: carry true => sector written ok
;        carry false => sector write failed
;        a corrupt
;        all other regs preserved
;
	push	hl
;
        ld      hl,?sectorbuffer ;buffer to write to sector
	call	ddwritesector	;write the sector
        jr      c,_30diw        ;jump if ok
;
	push	bc
;
; report error
; nb: writesector returns the addr of the status buffer in HL - so remember hl is corrupt
;
;
	ld	a,errwrite	;'Failed to write sector correctly'
	call	printerror	;report error
;
; try to re-write sector
;
	ld	b,errretrys	;max number of re-trys
;
_10diw: ld      hl,?sectorbuffer ;buffer to write to sector
	call	ddwritesector	;try to write sector
	ld	a,errwok	;'re-wrote sector successfully'
        jr      c,_20diw        ;jump if ok this time
;
; report re-write error
;
	ld	a,errrewrite	;'Failed to re-write sector correctly'
	call	printerror	;report error
;
	dec	b		;any more retrys
        jr      nz,_10diw       ;loop if so
;
; report hard error
;
	ld	a,errwabandon	;'Sector write abandoned'
;
_20diw: call    printmessage
;
	pop	bc
;
_30diw: pop     hl
	ret


; console 'getform .asm:6  - 16 dec 86 - establish format of a disc'

;=========
getformat:
;=========
;
; get the format of a drive
;
; entry: e = drive number
;  exit: b = number of sectors per track
;	 c = first sector number
;	 psw corrupt
;	 all other regs preserved
;
	push	hl
;
; get the format parameters from the xpb
;
	ld	a,xpbfirstsector
	call	ddpointxpb		;point to first sector parameter
	ld	c,(hl)
;confirm: xpbsecpertrack=xpbfirstsector+1
	inc	hl			;point to sectors per tack parameter
	ld	b,(hl)
;
	pop	hl
	ret

;===========
estabformat:
;===========
;
; update the XPB format parameters for the specified drive
;
; entry: e = drive to update
;  exit: if disc error then hl corrupt else hl preserved
;	 always af corrupt
;	 all other regs preserved
;
; call the ddautoselect disc function so that the format parameters
; in the xpb are updated
;
	call	ddautoselect	;do the select
	ld	a,errbadformat	;assume failed
	call	nc,printerror	;output error if failed (assume previous format)
;confirm <$ eq printdrformat>	;tell user what new format is

;=============
printdrformat:
;=============
;
; print format of current drive
;
; entry: e = drive to print
;  exit: af hl corrupt
;	 all other regs preserved
;
; print drive
;
        exx
        push    de              ;save cursor pos
        ld      de,0200h        ;move to start of line 2
        exx

        ld      a,mesAdrive
	add	a,e
	call	printmessage	;print drive number
;
; print side if 2 sided
;
	ld	a,xpbside
	call	ddpointxpb	;ref side flag
	ld	a,(hl)
	or	a		;zero false if 2 sided
	ld	a,mes2sided
	call	nz,printmessage	;print 2 sided if it is
;
; print number of tracks
;
	ld	a,xpb80tracks
	call	ddpointxpb	;ref 80 track flag
	ld	a,(hl)
	and	bit0		;a = 0 if 40 tracks or 1 if 80 tracks
; confirm true.and.bit0.eq.1
	add	a,mes40tracks	;convert to message number
; confirm mes80tracks.eq.(mes40tracks+1)
	call	printmessage	;print number of tracks on drive
;
; print format
;
	ld	a,xpbfirstsector
	call	ddpointxpb	;point to first sector parameter
;
; convert first sector number into message number
;
	ld	a,(hl)		;get first sector number
	and	0C0h		;mask format code
	rlca
	rlca			;format code in ls 2 bits, dr code in bit 2
	add	a,messysformat	;add offset to first message
        call    printmessage

        exx
        pop     de              ;restore curosr posn.
        exx
        ret

;============
ddinitialise:
;============
;
; initialise disc driver package
;
; entry: no conditions
;  exit: psw bc de hl corrupt
;
; set up the fdc
;
	ld	hl,initdriveparas
	ld	bc,leninitparas	;number of parameters to initialise
	jr	dosetup		;set up parameters & initialise fdc

;=======
ddsetup:
;=======
;
; set the motor on, motor off, timeout
; write current time  and  head settle delay  and  step rate
;
; parameter block format:
;
; bytes 0, 1 : motor on timeout (20 ms units)
; bytes 2, 3 : motor off timeout (20 ms units)
; byte 4     : write current off time (10 us units)
; byte 5     : head settle time (1 ms units)
; byte 6     : step rate time (1 ms units)
; byte 7     : head unload delay
; byte 8     : (head load delay .sl. 1)+non-dma mode
;
; entry: hl = ref parameter block
;  exit: af bc de hl corrupt
;        all other regs preserved
;
        ld      bc,.paralen      ;number of bytes to copy
; confirm $.eq.dosetup

;=======
dosetup:
;=======
;
; set up some parameters
;
        ld      de,?paratable    ;where to put details
	ldir			;copy the data
;
; configure fdc
;
	jp	fdcsetup

;==========
ddsetretry:
;==========
;
; set the number of retrys before a disc io cmnd is abandoned
;
; entry: a = number of retrys
;  exit: a = previous state
;	 all flags  and  other regs preserved
;
	push	hl
        ld      hl,(?rcount)     ;get previous value
        ld      (?rcount),a      ;set new value
	ld	a,l		;return previous value in A
	pop	hl
;
	ret

;============
ddsetmessage:
;============
;
; enable/disable messages
; if disabled, missing disc  and  write protected disc errors will result
; in the current cmnd failing
;
; entry: a = true/false <=> disable/enable messages
;  exit: a = previous state
;	 all flags  and  other regs preserved
;
	push	hl
        ld      hl,(?messdisable) ;get previous value
        ld      (?messdisable),a ;set new value
	ld	a,l		;return previous value in A
	pop	hl
;
	ret


; console 'select  .asm:7  - 19 feb 87 - select a standard format'

;============
ddautoselect:
;============
;
; we got a new disc, determine its type
;
; entry: e = drive number
;  exit: carry true if ok
;	   a corrupt
;	   hl preserved
;	 carry false if failed
;	   a = error status
;	   hl = ref fdc status buffer
;	 always all other regs preserved
;
	call	enterdisc	;save stack, start motor etc
;
; where are we?
;
	ld	a,xpbtrack
	call	fetchxpb
	ld	d,a		;track
;
	set	2,e		;insist read is on side 1
; confirm headselect.eq.bit2
	ld	hl,fdcidread	;routine to run
	call	trycommand
	ret	nc		;exit if failed
;
; read the id header ok so set up the xpb according to
; sector number read from the disc
;
; add the side select bit to the drive code
;
        ld      hl,(?statusbuffer+5) ;get side number
	bit	0,l		;zero true if single sided
        jr      nz,_10as        ;jump if 2 sided
;
	res	2,e		;clear side select bit for single sided drive
; confirm headselect.eq.bit2
;
_10as:  ld      a,h              ;sector number
;confirm <$.eq.ddselectformat

;==============
ddselectformat:
;==============
;
; select a standard format
;
; ** completely re-initializes the xpb
; ** except track, aligned and freeze fields
; ** ignores freeze field
; ** sets xpbside flag to value of side bit in E
;
; entry: a = any sector number of format
;	 e = drive (+ side) number
;  exit: carry true
;	 psw bc de hl corrupt
;	 all other registers preserved
;
; see which format
;
	and	fmtbits		;mask format code bits
        cp      sffirstsector and fmtbits ;is it the system format
	ld	hl,sfsecpertrack*256+sffirstsector ;assume so
        jr      z,_10dsf        ;jump if so
;
        cp      dffirstsector and fmtbits ;is it the data only format
	ld	hl,dfsecpertrack*256+dffirstsector ;assume so
        jr      z,_10dsf        ;jump if so
;
	ld	hl,jfsecpertrack*256+jffirstsector ;must be joyce format
;
_10dsf: bit     0,e             ;drive 1?
        jr      nz,_20dsf       ;jump if so
;
; set drive 0 xpb parameters
;
        ld      (?xpb),hl        ;set sectors per track & first sector
; confirm xpbfirstsector.eq.0
; confirm xpbsecpertrack.eq.1
        jr      _30dsf
;
; set drive 1 xpb parameters
;
_20dsf: ld      (?xpb+xpbsize),hl ;set sectors per track & first sector
; confirm xpbfirstsector.eq.0
; confirm xpbsecpertrack.eq.1
;
; set up side flag
;
_30dsf: bit     2,e             ;do we have side 0
; confirm headselect.eq.bit2
	scf			;assume so
	ret	z		;fin if side 0
;
	ld	a,xpbside
	call	pointxpb	;ref side flag in xpb
        ld      (hl),true       ;mark 2 sided drive
;
	scf
	ret

;========
fetchxpb:
;========
;
; fetch a byte from the xpb for the specified drive
;
; entry: a = offset into xpb
;	 e = drive number (+ side)
;  exit: a = byte from xpb
;	 al flags corrupt
;	 all other regs preserved
;
	push	hl
	call	pointxpb
	ld	a,(hl)
	pop	hl
	ret

;==========
ddpointxpb:
;==========
;
; point to the required field in the xpb for the specified drive
;
; entry: a = offset into xpb
;	 e = drive number (+ side bit)
;  exit: hl = ref xpb for drive
;	 psw corrupt
;	 all other regs preserved
;
;confirm: * = pointxpb

;========
pointxpb:
;========
;
; point to the required field in the xpb for the specified drive
;
; entry: a = offset into xpb
;	 e = drive number (+ side)
; exit:	hl = ref field in xpb
;	psw corrupt
;	all other regs preserved
;
	push	bc
;
	ld	c,a
	ld	b,0
        ld      hl,?xpb  ;ref drive A xpb
	ld	a,e
	rrca		;shift ls bit into carry
	jr	nc,px10	;j if drive A
;
        ld      hl,?xpb+xpbsize ;else must be drive B
;
px10:	add	hl,bc	;offset into xpb
;
	pop	bc
	ret


; console 'motor   .asm:11 - 23 feb 87 - motor control'

; ========
enterdisc:
; ========
;
; save stack
; save registers
; set the return addr to exitdisc
; turn on the motor
;
; entry: no conditions
;  exit: af corrupt
;	 all other regs preserved
;
	call	ddonmotor	;start motor if its not running
;confirm <$ eq doenterdisc>

; ==========
doenterdisc:
; ==========
;
; save stack
; save registers
; set the return addr to exitdisc
; DO NOT turn on the motor
;
; entry: no conditions
;  exit: all flags  and  regs preserved
;
        ld      (?savehl),hl
	pop	hl		;pop ret addr
	push	de
	push	bc
;
        ld      (?savesp),sp     ;for crashing
;
	push	hl
	ld	hl,exitdisc	;routine to run after disc io routine
	ex	(sp),hl		;push exit disc routine on to stack
	push	hl		;push our local routine on to stack
;
	push	af
;
	call	killticker	;stop motor off timeout
;
; fin
;
	pop	af
        ld      hl,(?savehl)
	ret

;=========
ddonmotor:
;=========
;
; start the disc motor if its not already running
;
; entry: no conditions
;  exit: af corrupt
;	 all other regs preserved
;
	call	killticker	;stop motor off timeout
        ld      a,(?motorrunning)
	or	a
	ret	nz		;fin if motor already running
;
	push	hl
;
        %if      arnold
	push	bc
	ld	bc,fdcmotor
	ld	a,motoron
	out	(c),a		;turn on the motor
	pop	bc
        %endif
;
; start the motor on delay ticker & wait for it to go off
;
;        ld      hl,(?monto)      ;motor on time out period
;        call    domtimeout      ;start the ticker event
;
;stm10:  ld      a,(?motorrunning) ;has event gone off yet
;        or      a
;        jr      z,stm10         ;loop till timeout
;
        ld      a,(?motorrunning)
        cpl
        ld      (?motorrunning),a

	pop	hl
	ret

;========
exitdisc:
;========
;
; disc io finished so start the motor off timeout  and  restore the stack
;
; entry: if cmnd went ok then carry true
;  exit: if cmnd went ok then
;	   a = 0, hl restored
;        if cmnd failed then
;	   a = error status (non-zero)
;	   hl = ref fdc status buffer
;	 always sp bc de restorted
;		all other regs preserved
;
        ld      sp,(?savesp)     ;restore stack
	call	startoffmotor	;start motor off timeout
;
	pop	bc
	pop	de
        ld      hl,(?savehl)
	ret	c		;fin if all ok
;
; failed
;
        ld      hl,?statusbuffer+1 ;ref status reg 0
	ld	a,(hl)
	and	st0nready	;get drive not ready bit
	inc	hl
	or	(hl)		;add status reg 1
	or	bit6		;force non-zero
	dec	hl
	dec	hl		;ref status buffer
;
	ret


;==========
ddoffmotor:
;==========
;
; turn off the motor
;
; entry: no conditions
;  exit: af corrupt
;	 all other regs preserved
;
        call    killticker      ;stop the ticker
	call	offmotor	;turn off the motor
;
	xor	a
        ld      (?motorrunning),a ;mark motor no longer running
;
; wait about half a second
; the following instruction should load 50000/2 into HL but the old
; micro soft assembler gave a value of E1A8h so keep the source the
; same we load E1A8h into HL
;
;	ld	hl,50000/2	;approx 500ms wait
	ld	hl,0E1A8h	;** this creates a delay of about a second **
;
_10dom: ex      (sp),hl         ;(24)
	ex	(sp),hl		;(24)
	nop			;(4)
	dec	hl		;(8)
	ld	a,h		;(4)
	or	l		;(4)
        jr      nz,_10dom       ;(12)
;
;confirm <(24 + 24 + 4 + 8 + 4 + 4 + 12) / 4 eq 20>
;
	ret

;===========
ddstopmotor:
;===========
;
; start motor off timer & turn off motors after timeout
;
; entry: no conditions
;  exit: psw corrupt
;	 all other regs preserved
;
;confirm <$ eq startoffmotor>	;start motor off timer

;=============
startoffmotor:
;=============
;
; start the motor off timeout
;
; entry: no conditions
;  exit: hl corrupt
;	 all flags & other regs preserved
;
	push	hl
	push	af
;
	call	killticker	;stop the ticker incase it goes off
                                ;just after we read the ?motorrunning flag
        ld      hl,(?moffto)     ;motor off timeout period
        ld      a,(?motorrunning)
	or	a		;zero false if motor on
        call    nz,domtimeout   ;only start timeout if motor running
;
	pop	af
	pop	hl
	ret

;==========
domtimeout:
;==========
;
; common code for starting motor on/off timeout event
;
        ld      (?motortimeout),hl ;save timeout period
        ld      a,true
        ld      (?tickerrunning),a ;mark ticker running
;
	ret

;======
ddtick:
;======
;
; interrupt ticker routine to be called once every 20 ms second
; for motor timeouts
;
; entry: no conditions
;  exit: af corrupt
;	 all other regs preserved
;
        ld      a,(?tickerrunning)
	or	a		;is the ticker active
; confirm false.eq.0
	ret	z		;fin if ticker idle
;
; dec counter
;
	push	hl
        ld      hl,(?motortimeout)
	dec	hl
        ld      (?motortimeout),hl;save
	ld	a,h
	or	l		;zero true if timeout
	pop	hl
	ret	nz		;fin if not timeout
;
; the motor timeout has gone off so turn the motor on or off
; and kill the ticker
;
; invert the motor running flag
;
        ld      hl,?motorrunning
	ld	a,(hl)
	cpl
	ld	(hl),a		;save new state of motor running flag
	or	a		;zero true <=> turn motor off
	call	z,offmotor	;turn motor off if required
;
;confirm <$ EQ killticker>	;kill ticker

;==========
killticker:
;==========
;
; kill our ticker
;
; entry: no conditions
;  exit: af corrupt
;	 all other regs preserved
;
	xor	a
        ld      (?tickerrunning),a ;ticker no longer active
;
	ret

;========
offmotor:
;========
;
; turn off the motor
;
; entry: no conditions
;  exit: psw bc corrupt
;	 all other regs preserved
;
        %if      arnold
	push	bc
	ld	a,motoroff
	ld	bc,fdcmotor
	out	(c),a
	pop	bc
        %endif   ;arnold
;
	ret


; console 'discio  .asm:7  - 19 feb 87 - disc IO routines'
;
;=============
ddwritesector:
;=============
;
; write a sector to disc
; *** this routine disables interrupts whilst writing the sector
;     because we only have 27us between each byte transfer
;
; entry: c = sector number
;        d = track number
;        e = disc number (+ side select bit)
;       hl = address of data buffer
;  exit: if carry true
;          then write ok
;               a corrupt
;               hl preserved
;        if carry false
;          then write failed
;               a = error status (non zero)
;              hl = addr of fdc status buffer
;        always
;        all other regs preserved
;
	call	enterdisc	;save stack, start motor etc
;
        ld      (?bufferaddr),hl ;save address of buffer
;
	ld	hl,fdcwrite	;routine to run
	jr	wfcommon	;common code

;========
ddformat:
;========
;
; format a track
; *** this routine disables interrupts whilst writing the track
;     because we only have 27us between each byte transfer
;
; entry: d = track number
;        e = disc number (+ side select bit)
;       hl = address of format buffer
;  exit: if carry true
;          then format ok
;               a corrupt
;               hl preserved
;        if carry false
;          then format failed
;               a = error status (non zero)
;              hl = addr of fdc status buffer
;        always
;        all other regs preserved
;
	call	enterdisc	;save stack, start motor etc
;
        ld      (?bufferaddr),hl ;save address of buffer
;
	ld	hl,fdcformat	;routine to run
;confirm <$ eq wfcommon>	;common code

;========
wfcommon:
;========
;
; common code to sector write  and  track format
;
	call	trycommand	;do the write/format
;
; now wait for the write current to switch off
; carry preserved from here on
;
        ld      a,(?wrtoff)      ;delay in 10us units
;
wfc10:	dec	a		;(4)
	push	af		;(12)
	pop	af		;(12)
	jr	nz,wfc10	;(12)
;
; wait time
;confirm <(4 + 12 + 12 + 12) / 4 eq 10> ;micro seconds
;
	ret

;=======
ddspeed:
;=======
;
; test the rotaional speed of the disc
;
; entry: d = track to use
;	 e = drive to test
;  exit: if carry false
;	    then fdc timedout (proberbly no index pulses)
;		 hl corrupt
;	 if carry true
;	    then test complete
;		 a = results of test
;           then a = reults of test, 0 if ok, 1 if too slow, 2 if too fast
;		 if a <> then hl = amount of time in error
;		 if a = 0 then hl corrupt
;	 always
;	    all other regs preserved
;
	call	enterdisc	;save stack, start motor etc
;
	ld	hl,fdcspeedtest	;routine to test spin speed
	jr	trycommand	;do the test

;============
ddreadsector:
;============
;
; read a sector from disc
; *** this routine disables interrupts whilst reading the sector
;     because we only have 27us between each byte transfer
;
; entry: c = sector number
;        d = track number
;        e = disc number (+ side select bit)
;       hl = address of data buffer
;  exit: if carry true
;          then read ok
;               a = 0
;              hl preserved
;        if carry false
;          then read failed
;               a = error status (non zero)
;              hl = addr of fdc status buffer
;        always
;        all other regs preserved
;
	call	enterdisc	;save stack, start motor etc
;
        ld      (?bufferaddr),hl ;save address of buffer
;
	ld	hl,fdcread	;routine to read a sector
;confirm <$ eq trycommand>	;do the read

;==========
trycommand:
;==========
;
; try  and  retry disc command
; moves head to current track
;
; entry: c = sector number
;	 d = track number
;	 e = drive  and  head numbers
;	hl = ref routine to run
;  exit: carry true if cmnd ok
;	 carry false if cmnd failed
;	 always a b corrupt
;	 all other regs preserved
;
        ld      a,(?rcount)
	ld	b,a		;number of retrys
;
tc10:	call	trycmnd30	;try to read/write the disc
	ret	c		;fin if cmnd ok
	ret	z		;fin if no more retrys
;
	bit	2,b		;divide count by 4 cos we already done 4 retrys
	jr	z,tc20		;jump if even
;
; odd retry count so need to move to the inner track
;
	push	de
	ld	d,lasttrack	;track to move to
	call	seektrack	;go there
	pop	de
	jr	tc10		;loop
;
; even retry count so need to restore the drive
; this can be done simply by setting the drive track flag for this drive to FF
;
tc20:	push	hl
	ld	a,xpbtrack
	call	pointxpb	;point to track flag
	ld	(hl),0FFh	;mark drive must be aligned
	pop	hl
	jr	tc10		;loop

;=========
trycmnd30:
;=========
;
; try to read/write the disc
; if failure occurs then re-issue command
; if failure occurs again move in one track  and  try again
; if failure occurs again move out one track  and  try again
;
; do cmnd
;
        %if      fdcinterrupts
	call	fdcflush	;service any outstanding interrupts
        %endif
;
	call	trycmnd40	;try to read/write once
	ret	c		;fin if ok
	ret	z		;fin if no more retrys
;
; retry cmnd
;
	call	fdcflush	;cmnd failed so service any outstanding interrupts
	call	trycmnd40	;try again
	ret	c		;fin if ok
	ret	z		;fin if no more retrys
;
; move in one track  and  retry cmnd
;
	ld	a,d
	cp	lasttrack
	dec	b		;dec retry count incase on inner track (preserves carry)
	jr	nc,tc35		;jump if on inner track
;
	inc	b		;restore retry count
	inc	d		;inc track number by 1
	call	seektrack	;do the move
	dec	d		;restore original track number
;
	call	trycmnd40	;try the command again
	ret	c		;fin if succeeded
tc35:	ret	z		;fin if no more retrys
;
; move out one track  and  retry cmnd
;
	ld	a,d
	or	a
	jr	nz,tc36		;jump if not on outer track
;
	dec	b		;dec retry count
	ret			;fin (carry false)
;
tc36:	dec	d		;dec track number by 1
	call	seektrack	;do the move
	inc	d		;restore original track number
;
;confirm: $ = trycmnd40		;try the command again

;=========
trycmnd40:
;=========
;
; attempt a read/write
;
	call	seektrack	;move to the correct track
;
	push	hl
	push	bc
;
	call	pchlinstruction	;do operation
;
	pop	bc
	pop	hl
	ret	c		;fin if ok
;
	jr	nz,trycmnd40	;try again if disc missing or write protected
;
	dec	b		;retry count
	ret


;===========
ddseektrack:
;===========
;
; move to specified track
; this routine sets the crash level for disc errors  and  should not be
; called from within the bios
;
; entry: d = track number
;        e = disc number
;  exit: if carry true
;          then seek ok
;               a corrupt
;               hl preserved
;        if carry false
;          then seek failed
;               a = error status (non zero)
;              hl = addr of fdc status buffer
;        always
;        all other regs preserved
;
	call	enterdisc	;save stack, start motor etc
	call	fdcflush	;service any outstanding interrupts
;confirm: $ equ seektrack

;=========
seektrack:
;=========
;
; move to a given track
; crash the stack if move fails
;
; entry: d = track number
;        e = drive number
;  exit: carry true
;        a corrupt
;	 all other regs preserved
;
	push	hl
	push	de
	push	bc
;
        ld      a,(?rcount)
	ld	b,a		;max number of retrys
;
; re-align drive if required
;
st05:	ld	a,xpbalign
	call	pointxpb	;hl = ref align flag for this drive
	ld	a,(hl)
	or	a		;has the drive already been aligned
	jr	nz,st20		;jump if so
;
st10:	call	fdcrestore	;restore the drive
	call	nc,fdcrestore	;if restore failed try again (in case 80 track drive)
        jr      nc,_50st        ;jump if re-align failed
;
	ld	a,xpbtrack
	call	pointxpb	;ref track
	ld	(hl),0
;confirm: xpbalign equ xpbtrack+1
	inc	hl
        ld      (hl),true      ;mark drive aligned
;
; see if we are already on the required track
;
st20:	dec	hl		;point to track flag
; confirm xpbtrack.eq.xpbalign-1
	ld	a,(hl)		;a = track we are on
	sub	d		;does it equal the required track?
	jr	z,st60		;fin if so
;
	call	fdcseek		;seek required track
;
	jr	c,st60		;j if seek ok
_50st:  jr      nz,st05         ;try again if there was no disc in the drive
;
	call	fdcflush	;clear any outstanding interrupts
	dec	b		;any more retrys?
	jr	nz,st10		;loop if so
;
; seek failed so mark the drive needs aligning
;
	ld	a,xpbalign
	call	pointxpb
	ld	(hl),false	;mark drive needs aligning
;
	jp	exitdisc	;no more re-trys so abandon cmnd
;
st60:	pop	bc
	pop	de
	pop	hl
	scf
	ret


; console 'stat765 .asm:5  - 19 feb 87 - 765 fdc drive status routines'

;===============
ddtestavailable:
;===============
;
; see if drive is available
; motors should be explicitly turned on or off prior to calling this routine
;
;  exit: if carry true then
;	    drive available (ready)
;        else carry false
;	 always
;	    a b corrupt
;	    all other regs preserved
;
	ld	b,st3drready	;bit to test
	jr	testcommon	;common code

;==========
ddwprotect:
;==========
;
; see if drive is write protected
; motors should be explicitly turned on or off prior to calling this routine
;
; entry: a = drive number
;  exit: if carry true then
;	    drive is write protected
;        else carry false
;	 always
;	    a b corrupt
;	    all other regs preserved
;
	ld	b,st3wprotect	;bit to test
;confirm <$ eq testcommon>	;common code

;==========
testcommon:
;==========
;
	call	ddgetdrstatus	;get drive status
	and	b		;mask relavent bit
	ret	z		;fin if not set (carry false)
;
	scf
	ret

;=============
ddgetdrstatus:
;=============
;
; return the status of the requested drive
; motors should be explicitly turned on or off prior to calling this routine
;
; entry: a = drive number
;  exit: carry true
;	 a = drive status byte
;	 always
;	 all other regs preserved
;
	call	doenterdisc	;save stack but dont turn on the motor
	push	af
;
	call	fdcflush	;clear any outstanding interrupts
;
; issue the sense drive status command
;
	ld	a,sensedrive
	call	outfirstbyte	;issue sense command
;
	pop	af
	call	outdatareg	;send drive select
	call	results		;get the results
;
        ld      a,(?statusbuffer+1) ;get status reg 3 contents
	scf
	ret


; console 'rw765   .asm:17 - 19 feb 87 - 765 fdc read/write routines'

        %if      using765

;========
fdcsetup:
;========
;
; configure the 765
; sets up the restart 7 addr for interrupts
;
; entry: no conditions
;  exit: psw bc de hl corrupt
;	 all other regs preserved
;
; turn off the motor
;
        %if      motorcontrol
	call	offmotor	;turn off the drive motor
        %endif
;
; send the specify command to the fdc
;
	ld	a,specify
	call	outfirstbyte
;
; convert required step rate of 1 - 32 into a form the fdc understands
;
        ld      hl,?steprate     ;current step rate
	ld	a,(hl)
	inc	hl		;point to head unload delay
	dec	a
	rlca
	rlca
	rlca			;divide by 2  and  shift into top 4 bits
	cpl			;but fdc wants the inverse
	and	0F0h		;mask relevant bits
	or	(hl)		;add head unload delay
	call	outdatareg	;send to fdc
;
	inc	hl		;point to head load delay  and  non-dma mode
	ld	a,(hl)
 	jp	outdatareg	;send head load delay  and  non dma mode cmnd

;========
fdcwrite:
;========
;
; attempt to write a sector
; ** assumes we are on correct track
;
; entry: c = sector to write
;	 d = track number
;	 e = drive (and side) numbers
;  exit: carry true if sector written correctly
;	 carry false zero false if no disc in drive
;	carry false zero true if failure due to some other reason
;	always a bc hl corrupt
;	all other regs preserved
;
	ld	l,c		;save sector number
;
	ld	a,wrtsec+mf	;write sector command
	call	startrw		;initiate the write cmnd (returns last byte of cmnd in A)
;
	push	de
	ld	de,sectorsize	;number of bytes to transfer
	call	outputbytes	;send last byte of cmnd & data to fdc
	pop	de
	jp	dwresults	;deal with results

;=========
fdcformat:
;=========
;
; attempt to format a track
; ** assumes we are on correct track
;
; entry: d = track number
;	 e = drive (and side) numbers
;  exit: carry true if track formatted correctly
;	 carry false zero false if no disc in drive
;	carry false zero true if failure due to some other reason
;	always a bc hl corrupt
;	all other regs preserved
;
; send the format cmnd
;
	ld	a,formattk+mf	;format track command
	call	outfirstbyte	;drop terminal count signal & send command byte
;
	ld	a,e
	call	outdatareg	;send drive  and  head numbers
;
	ld	a,xpbencsecsize
	call	outxpbdatareg	;send n - bytes per sectors
;
	ld	a,xpbsecpertrack
	call	fetchxpb	;get sectors per track
	push	af
	call	outdatareg	;send sc - sectors per track
;
	ld	a,xpbgplformat	;gap length
	call	outxpbdatareg	;send gpl - gap length
;
	pop	af		;get back sectors per track
	push	de
	add	a,a
	add	a,a		;*4 = number of bytes to transfer for format
	ld	e,a
	ld	d,0
	ld	a,xpbfiller	;send d - filler byte
	call	fetchxpb	;fetch filler byte
	call	outputbytes	;send last byte of cmnd  and  data to fdc
	pop	de
;
	jr	dwresults	;deal with results

;=========
fdcidread:
;=========
;
; read any old id header
; the sector number will uniquely determine the disc format
;
; entry: d = track number
;	 e = drive (+ side) number
;  exit: carry true if ok
;	 carry false zero false if disc missing
;	 carry false zero true if error
;	 always a bc corrupt
;	 all other regs preserved
;
; read id command
;
	ld	a,readid+mf	;command
	call	outfirstbyte	;drop terminal count & send cmnd byte
;
; drive
;
	ld	a,e
	call	outdatareg
;
; execution phase
;
; result phase
;
	jp	resst0		;deal with missing disc

;=======
fdcread:
;=======
;
; attempt to read a sector
; ** assumes we are on correct track
;
; entry: c = sector to read
;	 d = track number
;	 e = drive (and side) numbers
;  exit: carry true if sector read correctly
;	 carry false zero false if no disc in drive
;	carry false zero true if failure due to some other reason
;	always a bc hl corrupt
;	all other regs preserved
;
	ld	l,c		;save sector number
;
	ld	a,readsec+sk+mf	;read sector command
	call	startrw		;initiate the read cmnd (returns last byte of cmnd in A)
;
; execution phase - read the bytes
; see if ready to read next byte
;
	di			;no time to process interrupts
;
        %if     not fdcinterrupts
;
; send the last byte of the command
;
        ld      hl,(?bufferaddr) ;hl = ref data buffer
	call	outdatareg
;
	jr	rd30		;into loop
;
rd20:	inc	c		;addr of data register port (4)
	in	a,(c)		;read data byte (16)
	ld	(hl),a		;save in buffer (8)
	dec	c		;(4)
	inc	hl		;bump pointer (8)
;
rd30:	in	a,(c)		;read main status reg (16)
	jp	p,rd30		;loop if data transfer not yet required (12)
	and	executionmode	;(8)
	jr	nz,rd20		;loop if execution phase not yet over (12)
;
; the software loop in the execution phase must not last more than 27us
;
; (16 + 12 + 8 + 12 + 4 + 16 + 8 + 4 + 8) / 4 = 22 < 27 ;us
;
        %endif   ;.not.fdcinterrupts
;
;
;confirm: $ equ	dwresults	;deal with results

;=========
dwresults:
;=========
;
; deal with results phase of io command
;
; entry: bc = addr of fdc status port
;  exit: if carry true then  no errors
;	 if carry false, zero false then cmnd failed due to missing or write protected disc
;	 if carry false, zero true then cmnd failed due to some other error
;        psw de hl corrupt
;	 all other regs preserved
;
; result phase - read in the status bytes
;
	call	resst0and1	;process results
	ret	c		;fin if no errors
	ret	nz		;exit if no disc or disc write protected
;
; we can ignore end of cylinder errors cos we are not driving the terminal count signal
;
        %if     not tccontrol  ;only if no tc control
        ld      a,(?statusbuffer+2) ;get status register 1
;confirm: st1ecylinder equ bit7
	add	a,a		;shift bit 7 into carry
	ret	z		;fin if all other status bits 0 (carry must be true if so)
;
	xor	a		;carry false, zero true => real hw error
        %endif   ;.not.tccontrol
;
	ret

;=======
startrw:
;=======
;
; start a sector read/write command
;
; entry:  a = cmnd byte
;	  d = track number
;	  e = drive number + side bit
;	  l = sector number
;  exit:  a = last byte of cmnd for fdc (dtl)
;	 bc = addr of fdc status reg
;	 all other regs preserved
;
	call	outfirstbyte	;drop terminal count & send command byte
;
	ld	a,e
	call	outdatareg	;send drive  and  head numbers
;
	ld	a,d
	call	outdatareg	;send c - track number
;
	ld	a,e
	and	headselect	;mask head select bit
	rrca
	rrca			;shift head select bit into bit 0
	call	outdatareg	;send h - head number
;
	ld	a,l		;sector number
	call	outdatareg	;send r - sector number
;
	ld	a,xpbencsecsize
	call	outxpbdatareg	;send n - sector length - 512 byte sectors
;
        %if     not tccontrol
	ld	a,l		;get back sector number
	call	outdatareg	;send eot - same sector that we want to read/write
        %endif
;
        %if      tccontrol
	ld	a,xpbsecpertrack
	call	outxpbdatareg	;for systems with tc control send real eot
        %endif
;
	ld	a,xpbgplrw
	call	outxpbdatareg	;send gpl - gap length
;
	ld	a,dtl		;send dtl - not used
;
	ret

;===========
outputbytes:
;===========
;
; output last byte of cmnd followed by the data bytes
;
; entry: bc = addr of fdc status port
;	 de = number of bytes to transfer (interrupt systems only)
;  exit: psw hl corrupt
;	 all other regs preseved
;        ** interrupts enabled **
;
	di
;
        %if     not fdcinterrupts
;
        ld      hl,(?bufferaddr) ;hl = ref data buffer
	call	outdatareg	;send last byte of cmnd
;
; execution phase - write the bytes
; see if ready to write next byte
;
	jr	opb20		;into loop
;
opb10:	inc	c		;address of data register (4)
	ld	a,(hl)		;get byte to output (8)
	out	(c),a		;send byte (16)
	dec	c		;(4)
	inc	hl		;bump pointer (8)
;
opb20:	in	a,(c)		;read main status reg (16)
	jp	p,opb20		;loop if data transfer not yet required (12)
	and	executionmode	;(8)
	jr	nz,opb10	;loop if execution phase over (12)
;
; the software loop in the execution phase must not last more than 27us
;
; loop = 16 + 12 + 8 + 12 + 4 + 8 + 16 + 4 + 8 = 88 / 4 = 22us < 27us
;
        %endif   ;.not.fdcinterrupts
;
;
        %endif   ;using765


; console 'spin765 .asm:2  - 19 feb 87 - 765 disc spin speed test'

; local definitions
; -----------------
;
spinok          equ     0       ;disc spin time ok
spinslow        equ     1       ;disc spinning too slowly
spinfast        equ     2       ;disc spinning too fast
;
; loop delays
; these delays are 'adjusted' to account for the time spent reading the
; results of the first read & issuing the cmnd bytes of the second read
; note that these delays are for 2 spins of the disc as the fdc times out after
; 2 index pulses
;
;mintime         equ     19435   ;195 ms for 20us loop
mintime         equ     16800   ;195 ms for 20us loop
maxtime         equ     17800   ;205 ms for 20us loop
;maxtime         equ     20435   ;205 ms for 20us loop
;
; the sector number used for the spin speed test must be a totally illegal sector
; number because this will result in the 765 timingout after 2 index pulses
; this means that the timing test times between 2 index pulses and not an arbitary
; sector on track, in addition it also insures that index pulses are happening
;
spinsector	equ	254	;a very undefined sector number

;============
fdcspeedtest:
;============
;
; check that the disc is spining at the correct speed (200ms)
; this is done by reading a sector twice and measuring the delay between the 2 reads
; note that the data field is not read so the read commands will fail due to over
; run errors
;
; entry: d = track to use
;	 e = drive to test
;  exit: if carry false
;	    then fdc timedout (proberbly no index pulses)
;	 if carry true
;	    then test complete
;		 a = results of test
;	 always
;	    af bc de hl corrupt
;	    all other regs preserved
;
; start the first read
;
_10fst: call    _110fdcspeedtest ;issue sector read
	ret	nc		;fin if hw timeout
;
        ld      a,(?headld)      ;a memory read for analyser to trigger on
	call	results		;read & discard results
        jr      nz,_10fst       ;restart test if there was no disc in the drive
;
; start second read & time it
;
        call    _110fdcspeedtest ;issue read cmnd
	ret	nc		;fin if hw timeout
;
; read the ?headld teq again - this allows us to time between these 2 reads & hence
; see how long it takes to set up the second read as this time must be subtracted from
; our loop times
;
        ld      a,(?headld)      ;a memory read for analyser to trigger on
;
; wait 195 milliseconds & check execution phase does not finished
;
	ld	hl,mintime	;execution phase must be true after this time
;
_20fst: in      a,(c)           ;get fdc status (12)
	and	executionmode	;are we still in execution mode (8)
        jr      z,_80fst        ;jump if not - disc spinning too fast (8,12)
;
	in	a,(c)		;time waster (12)
	in	a,(c)		;time waster (12)
	dec	hl		;(8)
	ld	a,h		;(4)
	or	l		;(4)
        jr      nz,_20fst       ;(12)
;
; loop time
;confirm <(12 + 8 + 8 + 12 + 12 + 8 + 4 + 4 + 12)/4 eq 20> ;us
;
; wait another 10ms & check execution phase does finish
;
	ld	hl,maxtime-mintime ;exection phase must be over after this time
; confirm maxtime-mintime.eq.(500*2)
;
_50fst: in      a,(c)           ;get fdc status (12)
	and	executionmode	;are we still in execution mode (8)
        jr      z,_90fst        ;jump if not - disc spin speed ok (8,12)
; confirm spinok.eq.0
;
	in	a,(c)		;time waster (12)
	in	a,(c)		;time waster (12)
	dec	hl		;(8)
	ld	a,h		;(4)
	or	l		;(4)
        jr      nz,_50fst       ;(12)
;
; loop time
; confirm (12 + 8 + 8 + 12 + 12 + 8 + 4 + 4 + 12)/4.eq.20> ;us
; disc spinning too slowly - see how slowly
;
	ld	hl,0		;initial count
;
_60fst: in      a,(c)           ;get fdc status (12)
	and	executionmode	;are we still in execution mode (8)
        jr      z,_65fst        ;jump if not (8,12)
;
	in	a,(c)		;time waster (12)
	in	a,(c)		;time waster (12)
	inc	hl		;(8)
        jr      nz,_60fst       ;loop till timeout (12)
;
	dec	hl		;fdc has timed out so return a very big number
        jr      _70fst          ;exit
;
; loop time
; confirm (12 + 8 + 8 + 12 + 12 + 8 + 4 + 4 + 12)/4.eq.20> ;us
;
; if hl = 0 then time is ok - ie its just on the border
;
_65fst: ld      a,h
	or	l		;zero true then time is ok
; confirm spinok.eq.0
        jr      z,_90fst        ;jump if time just ok
;
_70fst: ld      a,spinslow      ;mark disc spinning too slowly
        jr      _90fst          ;fin
;
_80fst: ld      a,spinfast      ;mark disc spinning too fast
;
; process results of read
;
_90fst: ld      (?savehl),hl     ;save results for exit - if error
	push	af
	call	results		;read & discard results
	pop	af		;recover error code
;
	scf			;assume disc read ok
;CJL        ei                      ;allow interrupts again
	ret

;================
_100fdcspeedtest:
;================
;
	in	a,(c)		;time waster (12)
;
	ld	a,h		;(4)
	or	l		;(4)
;
	ret			;(12)
;
; time = 12 + 4 + 4 + 12 = 32
;
;================
_110fdcspeedtest:
;================
;
; common code to start read
;
	ld	l,spinsector	;save sector number
;
	ld	a,readsec+sk+mf	;read sector command
	call	startrw		;initiate the read cmnd (returns last byte of cmnd in A)
	call	outdatareg	;send last byte of cmnd to start read
;
	di			;no time to process interrupts
;
; wait till results phase starts
;
	ld	hl,0		;a large timeout
;
_120st: in      a,(c)
	and	executionmode	;has execution phase started
	scf
	ret	nz		;fin if so
;
	dec	hl
	ld	a,h
	or	l
        jr      nz,_120st       ;loop till timeout
;
;CJL        ei                      ;allow interrupts again !
	ret			;timed out - return carry false
;

; console 'seek765 .asm:6  - 19 feb 87 - 765 fdc seek routines'

;==========
fdcrestore:
;==========
;
; restore the 765
;
; entry: e = drive (+ side) number
;  exit: carry true if restore done correctly
;	 carry false zero false if no disc in drive
;	 carry false zero true if seek error
;	 always a hl corrupt
;	 all other regs preserved

	push	bc
;
	ld	a,restore
	call	outfirstbyte	;send restore cmnd
;
	ld	a,e
	and	drivebits	;mask out side select bit
	call	outdatareg	;send drive  and  head numbers
;
	ld	a,lasttrack-firsttrack+1 ;must wait at least number of track
				;step times for a restore
;
	jr	seekcommon	;wait till the seek completes

;=======
fdcseek:
;=======
;
; seek to specified track
;
; entry: e = drive (+ side) number
;	 d = track to seek
;  exit: carry true if restore done correctly
;	 carry false zero false if no disc in drive
;	 carry false zero true if seek error
;	 always a hl corrupt
;	 all other regs preserved
;
	push	bc
;
; seek to the required track
;
	ld	a,seek
	call	outfirstbyte	;send restore cmnd
;
	ld	a,e
	call	outdatareg	;send drive number
;
	ld	a,d
	call	outdatareg	;send new track number
;
; calculate number of steps we are going to move
; so that we know how long to wait
;
        %if     not fdcinterrupts
	sub	(hl)		;a = number of steps needed
        jr      nc,_mt40         ;jump if no overflow
;
	ld	a,(hl)
	sub	d		;a = number of steps needed
;
_mt40:
        %endif   ;.not.fdcinterrupts
;
	ld	(hl),d		;save new track number
;
;confirm: $ equ seekcommon	;wait till the seek completes

;==========
seekcommon:
;==========
;
; local routine for seektrack
;
; for interrupts systems wait till the fdc interrupts
;
        %if      fdcinterrupts
;
	xor	a
        ld      (?intflag),a     ;mark we have not yet seen an interrupt
;
	ld	a,sysfdcint
	out	(syscontrol),a 	;enable disc interrupts
;
; wait for interrupt
;
;CJL        ei                      ;we wont get one with out this
;
sc10:   ld      a,(?intflag)
	or	a
	jr	z,sc10		;loop till interrupt goes off
;
        %endif   ;fdcinterrupts
;
; for non interrupt systems
; we are about to move (a) tracks so wait (a) times step rate time
;
        %if     not fdcinterrupts
;
sc10:	push	af
        ld      a,(?steprate)
	call	milliseconds
	pop	af
	dec	a
	jr	nz,sc10		;loop till timeout
;
        %endif   ;.not.fdcinterrupts
;
; now wait for the head settle time
;
        ld      a,(?hdsettle)
	call	milliseconds
;
; issue the sense interrupt status to get seek status
;
	ld	a,senseinterrupt
	call	outdatareg
	call	resst0		;process results
;
	pop	bc
	ret

; ===========
milliseconds:
; ===========
;
; wait milliseconds
;
; entry: a = milliseconds required
;  exit: psw corrupt
;	 all flags  and  regs preserved
;
wms10:	push	af		;(16)
        ld      a,looptime     ;(8)
; confirm (>looptime).eq.0
;
wms20:	dec	a		;(4)
	jr	nz,wms20	;(16)
;
	pop	af		;(12)
	dec	a		;(4)
	jr	nz,wms10	;(12)
;
;looptime equ    (4000-(16+8+12+4+12))/(16+4)
looptime equ    0F6h   ;ie C5 (above result) * 1.25 CJL
;
	ret


; console 'int765  .asm:9  - 19 feb 87 - 765 fdc interrupt routines'
;
        %if      using765

;===========
ddinterrupt:
;===========
;
; service an fdc interrupt
;
; entry: no conditions
;  exit: af hl corrupt
;	 all other regs preserved
;
; mark we have received an fdc interrupt
;
;
        ld      a,true         ;mark interrupt has happened
        ld      (?intflag),a
;
; disarm fdc interrupt
;
	ret

        %endif   ;fdcinterrupts


; console 'res765  .asm:8  - 19 feb 87 - process 765 fdc results'
;
        %if      using765

;======
resst0:
;======
;
; read status register 0 & check cmnd went ok
; deals with disc not available
;
; entry: bc = status reg port
;         e = drive number
;  exit: carry true if cmnd terminated correctly
;	 carry false, zero false then cmnd failed because there is no disc
;	 carry false zero true cmnd failed for some other reason
;	 always a corrupt
;	 all other regs preserved
;
	call	results		;get fdc results
	ret	c		;fin if no errors
	ret	nz		;fin if results not for current drive
;
; see if reason for failure is due to no disc
;
        ld      a,(?statusbuffer+1)
	and	st0nready	;is the drive ready
	ret	z		;fin if so
;
	ld	a,sysnodisc	;'insert disc'
	jr	discmessage	;deal with missing disc

;==========
resst0and1:
;==========
;
; read the status registers 0  and  1 and check cmnd went ok
; deals with disc not available and disc write-protected
;
; entry: bc = addr status reg port
;	  e = drive number
;  exit: carry true if cmnd terminated correctly
;	 carry false, zero false then cmnd failed because there is no disc
;	 carry false zero true cmnd failed for some other reason
;	 always a bc hl corrupt
;	 all other regs preserved
;
	call	resst0		;get status, see if drive ready
	ret	c		;fin if ok
	ret	nz		;fin if drive not ready
;
; see if cmnd failed cos the disc is write protected
;
        ld      a,(?statusbuffer+2) ;get status reg 1
	and	st1nwritable	;is the disc write protected?
	ret	z		;fin if not (carry false, zero true)
;
	ld	a,syswprot	;'Write protect disc'
;confirm <$ eq discmessage>	;deal with write protected disc

;===========
discmessage:
;===========
;
; output a disc error message
;
; entry: a = ref discc message
;	 e = drive number (+ side number)
;  exit: carry false zero false if message enabled
;	 carry false zero true if messages disabled
;	 a corrupt
;	 all other regs preserved
;
; abandon IO if messages are disabled  and  we are not booting
;
	push	af
;
        ld      a,(?messdisable)
	cpl
	or	a		;zero true if messages disabled
        jr      z,_10dm         ;fin if so (carry false)
;
	pop	af
;
; output error message & wait for user to press a key
;
	call	sysmessage	;print buffer
;CJL        ei                      ;make sure interrupts are armed
;        call    waitkey         ;wait for user to press a key (MEJ stuff)
;
; I've replaced that call with the following (cliff)
;
        push    af
        push    bc
        push    de
        push    hl
discmessWaitKey:
        call    KeyboardRead
        cp      0ffh
        jr      z,discmessWaitKey
        pop     hl
        pop     de
        pop     bc
;
; clear status line window
;
	ld	a,sysclrstatus
	call	sysmessage
	pop	af
;
	xor	'C'-40h		;zero true if user press control C
;
	ret			;(carry false)
;
_10dm:  xor     a               ;exit zero true, carry false
	ret

;=======
results:
;=======
;
; command completed so read the status registers  and  check it went ok
;
; entry: bc = address of status reg
;	  e = drive number (+ side bit)
;  exit: if carry true
;          then command terminated correctly
;        if carry false, zero true then command failed to terminate correctly
;	 if carry false, zero false then results are for other drive
;        always
;          a corrupt
;          all other regs preserved
;
	push	hl
	push	de
;
	ld	d,0		;no bytes input yet
        ld      hl,?statusbuffer + 1 ;buffer for fdc status bytes (first byte is count)
	push	hl
;
; wait till fdc ready with the next byte
;
_10res: in      a,(c)           ;read status
	add	a,a		;shift rqm into carry
; confirm rqm.eq.bit7
        jr      nc,_10res       ;loop till rqm
;
	add	a,a		;shift dio into carry
; confirm dio.eq.bit6
        jr      nc,_20res       ;fin if results phase over
;
; read byte into status buffer
;
	inc	c		;bc = address of data channel
	in	a,(c)		;read status byte
	dec	c		;(4)
;
	ld	(hl),a		;put into buffer (8)
	inc	hl		;bump pointer (8)
	inc	d		;inc count of bytes input (4)
;
; wait 24us for fdc to sort its self out
;
	ex	(sp),hl		;(24)
	ex	(sp),hl		;(24)
	ex	(sp),hl		;(24)
	ex	(sp),hl		;(24)
        jr      _10res          ;get next results bytes
;
; time elapsed since input 
; confirm (4+8+8+4+24+24+24+24)/4>24-1
;
; see if the operation was performed correctly
;
_20res: pop     hl
	ld	a,(hl)
	xor	e		;zero drive select bits if status for this drive
	and	st0icode+drivebits ;zero => normal termination
;
	dec	hl
	ld	(hl),d		;save count of bytes received
;
	pop	de
	pop	hl
	scf
	ret	z		;fin if ok
;
; cmnd failed - if drive select bits are non zero then the status
; is not for the current cmnd therefor the cmnd should be re-issued
;
	and	drivebits	;set zero accordingly
	ret			;(carry false)

;========
fdcflush:
;========
;
; service any outstanding fdc interrupts
;
; entry: no conditions
;  exit: psw corrupt
;	 all other regs preserved
;
; do nothing if no int pending
;
; service all outstanding interrupts by sending sense interrupt status commands
; untill one gets rejected as an illegal command
;
	push	bc
;
ff10:	ld	a,senseinterrupt
	call	outfirstbyte	;send the sense interrupt command
	call	results		;get the results
;
        ld      a,(?statusbuffer+1) ;get status
	and	st0icode	;mask interrupt code
	cp	st0iinvalid	;do we have the invalid cmnd status?
	jr	nz,ff10		;loop if not
;
	pop	bc
	ret

;============
outfirstbyte:
;============
;
; drop terminal count then send first byte of a cmnd
;
; entry: a = first byte to send
;  exit: bc = io addr of fdc status channel
;	 all flags & other regs preserved
;
	push	af
;
; drop terminal count signal
;
        %if      tccontrol
	ld	a,sysnfdctc
	out	(syscontrol),a
        %endif
;
	pop	af
	ld	bc,fdcstatus	;set up io addr of fdc
	jr	outdatareg

;=============
outxpbdatareg:
;=============
;
; fetch a byte from the xpb
; output it to the fdc
;
; entry:  a = offset into fdc
;	 bc = addr fdc status port
;	  e = drive number (+ side bit)
;
	call	fetchxpb	;fetch the byte
;confirm: $ = outdatareg

;==========
outdatareg:
;==========
;
; output a byte to the fdc
;
; entry:  a = byte to output
;        bc = io address of fdc status channel
;  exit: all flags  and  regs preserved
;
	push	af
;
; wait till nec is ready to take the byte
;
	push	af		;save byte to output
;
odr10:	in	a,(c)		;read fdc status reg
; confirm rqm.eq.bit7
	add	a,a		;shift rqm into carry
	jr	nc,odr10	;loop till fdc ready for io
;
;confirm: dio equ bit6
	add	a,a		;shift dio into carry
	jr	nc,odr15	;jump if ready for output
;
; fdc wants to send us some status so abort command out
;
	pop	af
	pop	af
	ret
;
odr15:	pop	af		;recover byte
;
	inc	c		;bc = address of data channel
	out	(c),a		;output the byte
	dec	c		;(4)
;
	ex	(sp),hl		;(24)
	ex	(sp),hl		;(24)
	ex	(sp),hl		;(24)
	ex	(sp),hl		;(24)
;
; time elapsed since input 
; confirm (4+24+24+24+24)/4>24-1
;
	pop	af
	ret
;
        %endif   ;using 765

;======
cphlbc:
;======
;
; compare hl and bc
; cpu flags set according to hl - bc
;
; entry: hl  and  de set up accordingly
;  exit: all cpu flags as per hl - bc
;	 all regs preserved
;
	push	de
	ld	e,a		;save A
;
	ld	a,h
	cp	b		;compare ls bytes
	jr	nz,chb10	;jump if ms bytes differ
;
	ld	a,l
	cp	c		;compare ls byte
;
chb10:	ld	a,e		;restore A
	pop	de
	ret


;==========
caselookup:
;==========
;
; lookup a byte in a table and return an associated 2 byte value
; table format:
;               byte 0        - number of entries + 1 in table
;		bytes 1  and  2   - default value to return if key not found
;               bytes 3 - 5   - entry 0
;		bytes n - n+2 - last entry in table
;
; each entry has the following format:
;		byte 0 - key value
;		byte 1 - lsb of associated field
;		byte 2 - msb of associated field
;
; entry: hl = addr of lookup table
;         a = key value
;  exit: if key found in table
;	   then carry true
;		hl = associated value from table
;        if key not found in table
;		carry false
;		hl = default value
;	 always
;		all other regs preserved
;
	push	bc
	ld	c,a		;save A
;
	ld	b,(hl)		;number of entries + 1
	inc	hl
	push	hl		;save pointer to default value
;
clu10:	or	a		;carry false
	dec	b		;any more entries
	jp	z,lookupcommon	;jump if not
;
	inc	hl		;point to key field
	inc	hl
	cp	(hl)		;is this the right entry
	inc	hl		;point to associated value
	jp	nz,clu10	;loop if not this entry
;
; return associated value
;
	ex	(sp),hl		;put pointer to byte on stack
	scf			;mark key found
	jr	lookupcommon	;common exit code


;============
lookupcommon:
;============
;
; common code to lookup routines
;
	pop	hl		;recover pointer to required byte
	ld	a,c		;restore A
	pop	bc
;confirm <$ eq ldhlvhl>		;get value

;=======
ldhlvhl:
;=======
;
; load hl via hl
;
; entry: (hl) = ref variable to load
;  exit: hl = variable contents
;	 all flags & other regs preserved
;
	push	af
;
	ld	a,(hl)		;lsb
	inc	hl
	ld	h,(hl)		;msb
	ld	l,a
;
	pop	af
	ret


;========
chkbreak:
;========
;
; Have a quick look to see if the user has pressed ESC. Return to the main menu
; if he has otherwise return with no corruption.
;
        push    af
        push    bc
        push    de
        push    hl
        call    KeyboardRead
        cp      0FFh
        jr      z,chkbreakNoKey
        cp      66                      ;is ESC pressed
        jr      nz,chkbreakNoKey
        jp      cpabandon
;        jp      0                       ;an easy way back to main menu !
chkbreakNoKey:
        pop     hl
        pop     de
        pop     bc
        pop     af
        ret

;==========
setmesstab:
;==========
;
; set the address of the message table
;
; entry: hl = addr of message table
;  exit: hl = addr ofprevious message table
;	 all flags and regs preserved
;
	push	hl
        ld      hl,(?usertable)  ;get previous addr
	ex	(sp),hl		;swap
        ld      (?usertable),hl  ;save addr of message table
	pop	hl		;return old addr
;
	ret

;============
printmessage:
;============
;
; output a message from the user message table
;
; message table format:
;	 byte 0 = count of message is table 
;	 byte 1 to n messages terminated by $
;
; entry: a = message number
;        bc de setup as required by message
;  exit: all flags & regs preserved
;
	push	hl
        ld      hl,(?usertable)  ;get user message table
        ld      (?messtab),hl    ;save for recursive calling of putmessage
	pop	hl
	jr	putmessage	;print required message

;==========
sysmessage:
;==========
;
; output a message from the system message table
;
; entry: a = message number
;        bc de setup as required by message
;  exit: all flags & regs preserved
;
	push	hl
	ld	hl,sysmestable
        ld      (?messtab),hl    ;set system message table for recursive calling of putmessage
	pop	hl
;confirm <$ eq putmessage>	;print required message

;==========
putmessage:
;==========
;
; output a message from the user message table
;
; message table format:
;	 byte 0 = count of message is table + 1
;	 byte 1 to n messages terminated by $
;
; entry: a = message number
;	hl = message table to use
;	previous hl pushed on stack
;        bc de setup as required by message
;  exit: all flags  and  regs preserved
;
	push	hl
	push	de
	push	bc
	push	af
;
        ld      (?savedbc),bc    ;save bc for escape
        ld      (?savedde),de    ;save de for escape
;
        call    _10putmessage   ;print the message
;
	pop	af
	pop	bc
	pop	de
	pop	hl
	ret

;=============
_10putmessage:
;=============
;
; print message from message table
;
; find the message to print
;
        ld      hl,(?messtab)    ;table of messages
	push	af
;
; check message number is in range
;
	dec	a
	cp	(hl)		;is message number in range
	ld	a,sysbadmessage	;'Illegal message number: Program abandoned'
	call	nc,sysmessage	;report any error
_10pm:  jr      nc,_10pm        ;crash program
;
; find message
;
	pop	bc		;recover message number (pushed as af)
	inc	hl		;step past message count
        jr      _25pm           ;jump in to loop
;
; move to next message
;
_20pm:  ld      a,(hl)
	inc	hl
        cp      '$'             ;end of message?
        jr      nz,_20pm        ;loop if not end of message
;
_25pm:  djnz    _20pm           ;jump till found message
;
; print required message
;
_30pm:  ld      a,(hl)
	inc	hl
	bit	7,a
; confirm ptescape.eq.bit7
        jr      nz,_40pm        ;jump if escape sequence
;
; check for end of message
;
	cp	'$'		;end of message ?
	ret	z		;fin if so
;
; deal with backslash 
;
        cp      '^'             ;is it back slash
	call	z,outnewline	;if zero the print new line
	call	nz,outchar	;else print char
        jr      _30pm           ;loop
;
; escape char
;
_40pm:  bit     6,a
; confirm ptnotmess.eq.bit6+bit7
        jr      z,_70pm         ;jump if submessage
;
; deal with escape
;
	cp	ptdrnum		;is it print drive number
        jr      z,_80pm         ;jump if so
;
	cp	ptsidenum	;is it print side number
        jr      z,_90pm         ;jump if so
;
	bit	3,a		;do we want to print a variables contents
; confirm ptvar.eq.bit3
        jr      z,_50pm         ;jump if not
;
	ld	e,(hl)		;lsb of variable addr
	inc	hl
	ld	d,(hl)		;msb of variable addr
	inc	hl
	push	hl		;save addr of next char in message
;
	ex	de,hl
	call	ldhlvhl		;get value to print
        jr      _55pm           ;print it
;
; get reg value to print
;
_50pm:  push    hl
;
	ld	b,a		;save escape char
	and	ptregmask	;mask reg number to print
	ld	e,a
	ld	d,0		;de = escape reg number
        ld      hl,?savedbc
; confirm savedde.eq.savedbc+2
	add	hl,de		;hl = ref lsb of reg to print (& only byte if 8 bit prin)
	call	ldhlvhl		;get value to print
	ld	a,b
;
_55pm:  cp      ptvarchar       ;is it print var char
        jr      z,_100pm        ;jump if so
;
	bit	5,a		;do we want value in hex
; confirm pthex.eq.bit5
        jr      nz,_60pm        ;jump if so
;
; print value in decimal
;
	bit	4,a		;do we want to print 8 or 16 bits
; confirm pt16bits.eq.bit4
	ld	a,l		;get value to print (assuming 8 bits)
	call	z,out8decimal
	call	nz,out16decimal
;
	pop	hl		;restore message pointer
        jr      _30pm           ;loop
;
; print value in hex
;
_60pm:  bit     4,a             ;do we want to print 8 or 16 bits
; confirm pt16bits.eq.bit4
	ld	a,l		;get value to print (assuming 8 bits)
	call	z,out8hex
	call	nz,out16hex
;
	pop	hl		;restore message pointer
        jr      _30pm           ;loop
;
; print sub message
;
_70pm:  push    hl
;
;        and     not low printsubmessage ;mask message number
        and     07Fh            ;CJL CJL CJL
        call    _10putmessage   ;print it
;
	pop	hl
        jr      _30pm           ;loop
;
; print drive number in E as letter A or B
;
_80pm:  ld      a,(?savedde)     ;get drive number
	and	ptdrivemask	;mask drive bits
	add	a,'A'		;convert to letter
        jr      _110pm          ;print char
;
; print side number in E as letter 0 or 1
;
_90pm:  ld      a,(?savedde)     ;get drive number
	and	ptsidemask	;mask side bit
	rrca
	rrca			;side bit in bit 0
; confirm ptsidemask.eq.bit2
	add	a,'0'		;convert to letter
        jr      _110pm          ;print char
;
; print a char in a variable addr
;
_100pm: ld      a,l             ;char to print
	pop	hl
;
_110pm: call    outchar         ;print it
        jp      _30pm           ;get next char in string

;==========
outnewline:
;==========
;
; output carriage return, line feed
;
; entry: no conditions
;  exit: all flags  and  regs preserved
;
	push	af
        push    bc
        push    de
        push    hl
;
        ld      hl,RAMLdirRoutine
        ld      de,09F00h
        ld      bc,040h
        ldir                    ;move screen LDIR routine out to "safe" RAM

        ld      a,cr
	call	outchar		;carriage return
	ld	a,lf
	call	outchar		;linefeed

        exx
        ld      a,d             ;get new row value
        exx

        cp      25              ;hit bottom of screen ?
        jr      nz,_outnlnoscroll
        ld      a,24
        exx
        ld      d,a             ;set cursor row to limit
        exx

        ld      b,8             ;moving 8 sections of scan
        ld      hl,0C140h
        ld      de,0C0F0h
scrollmove:
        push    bc
        ld      bc,06C0h
        call    09F00h
;        ldir                    ;move back a scans worth
        ld      bc,800h - 6C0h
        add     hl,bc           ;back HL up then down a scan
        ex      de,hl
        add     hl,bc           ;do the same for DE
        ex      de,hl
        pop     bc
        djnz    scrollmove
;
        ld      b,8             ;8 scans at bottom to be cleared
        ld      hl,0C780h       ;addr of first of those 8
scrollClear:
        push    bc
        push    hl
        pop     de              ;so de=hl
        inc     de              ;+1
        ld      (hl),0          ;clear first
        ld      bc,80
        call    09F00h
;        ldir                    ;and the rest
        ld      de,800h - 80    ;back 80 bytes and down a scan
        add     hl,de
        pop     bc
        djnz    scrollClear

_outnlnoscroll:
        pop     hl
        pop     de
        pop     bc
	pop	af
	ret


RAMLdirRoutine:
;
; This is moved to "safe" RAM at 9F00 and performs an LDIR from one bit of
; screen to another without accidentally reading the upper ROM contents.
;
        push    af
        exx                             ;BC' has current ROM state etc
        ld      b,07Fh
        ld      a,c
        or      8                       ;set the upper ROM off bit
        out     (c),a                   ;so the upper ROM is now off
        exx

        ldir                            ;do screen-->screen LDIR

        exx
        out     (c),c                   ;set ROM state back as it was
        exx

        pop     af
        ret

outchar:
        push    af
        push    bc
        push    de
        push    hl

        push    af
        ld      a,0
        ld      (?PrintCharXORMask),a
        ld      a,2
        exx
        cp      d               ;is cursor row below 3
        exx
        jr      c,_putnoinverttext
        ld      a,0FFh
        ld      (?PrintCharXORMask),a
_putnoinverttext:
        pop     af

        call    ScreenPrintChar
        pop     hl
        pop     de
        pop     bc
        pop     af
        ret

;
;========
out16hex:
;========
;
; output a 16 bit number in hexidecimal
;
; entry: hl = number to output
;  exit: all flags  and  regs preserved
;
	push	af
;
	ld	a,h
	call	out8hex		;output ms byte
	ld	a,l
	call	out8hex		;output ls byte
;
	pop	af
	ret

;=======
out8hex:
;=======
;
; output a 8 bit number in hexidecimal
;
; entry: a = number to output
;  exit: all flags  and  regs preserved
;
	push	af
;
	rrca
	rrca
	rrca
	rrca			;shift ms nibble into lower 4 bits
	call	hexnibble	;output ms nibble
;
	pop	af
;confirm: $ equ	hexnibble

;=========
hexnibble:
;=========
;
; output a hex nibble
;
; entry: a = nibble to output (ls 4 bits)
;  exit: all flags  and  regs preserved
;
	push	af
;
	or	0F0h
	daa
	add	a,0A0h
	adc	a,040h		;pure majic
;
	call	outchar
;
	pop	af
	ret

;============
out16decimal:
;============
;
; output a 16 bit number in decimal
;
; entry: hl = number to output
;  exit: all flags  and  regs preserved
;
	push	hl
	push	de
;
	ex	de,hl		;number to output in de
	ld	hl,pow16	;table to use
	jp	outdeccommon	;common code

;===========
out8decimal:
;===========
;
; output a 8 bit number in decimal
;
; entry: a = number to output
;  exit: all flags  and  regs preserved
;
	push	hl
	push	de
;
	ld	e,a
	ld	d,0		;number to output in de
	ld	hl,pow8		;table to use
;confirm: $ = outdeccommon	;common code

;============
outdeccommon:
;============
;
; common code for decimal output routines
;
	push	bc
	push	af
;
	ld	c,' '		;zero suppression char
;
; output this power of 10
;
odc10:	push	hl
	ld	a,(hl)
	inc	hl
	ld	h,(hl)
	ld	l,a
	call	printpower	;print digit
;
	pop	hl
	inc	hl
	inc	hl		;hl = ref next entry in table
	ld	a,(hl)
	or	a		;any more ?
	jp	nz,odc10	;loop if so
;
; all done
;
	pop	af
	pop	bc
;
	pop	de
	pop	hl
	ret
;
; table of powers to output
;
pow16:	dw	10000
	dw	 1000
pow8:	dw	  100
	dw	   10
	dw	    1
	dw	    0		;end marker

;==========
printpower:
;==========
;
; print a digit 
;
; entry: hl = power
;        de = number for output
;         c = zero suppression char
;  exit: de c updated
;        a b hl corrupt
;        all other regs preserved
;
	ld	b,'0'
	ex	de,hl
;
pp10:	or	a		;carry false
	sbc	hl,de		;subtrack de from hl
	jp	c,pp20		;jump if number gone negative
;
	inc	b		;add 1
	jp	pp10		;loop till overflow
;
; output digit
;
pp20:	add	hl,de		;make number positive again
	ex	de,hl		;put back in de
;
	ld	a,b
	cp	'0'		;is it zero ?
	jp	nz,pp30		;jump if not
;
; kill zero suppression if this is the last digit
;
	dec	hl		; if 1 then this is last digit
	ld	a,h
	or	l
	jp	nz,pp25		;j if not last digit
;
	ld	c,'0'		;last digit must not be suppressed
;
pp25:	cp	a		;zero true
	ld	a,c		;use zero suppression char
pp30:	call	outchar		;print char
	ret	z		;fin if output a zero
;
	ld	c,'0'		;output a digit so kill zero suppression
	ret



        END
