IDE interface for Altair 8800c

Discuss construction, troubleshooting, and operation of the Altair 8800c computer
Post Reply
Wayne Parham
Posts: 253
Joined: March 18th, 2022, 3:01 pm
Contact:

Re: IDE interface for Altair 8800c

Post by Wayne Parham »

Here's the code as it stands now. It is able to retrieve drive ID for drives or flash cards, both for drive 0 and drive 1.

It still needs some work, so we're not "cookin' with gas" yet but we definitely have the pilot light lit.

Code: Select all

;------------------------------------------------------------------------------
; Utility Program for IDE interface board
;	v2.9b   12/01/2022
;------------------------------------------------------------------------------

;------------------------------------------------------------------------------
;Build equates:
;------------------------------------------------------------------------------

FALSE		EQU	0
TRUE		EQU	NOT FALSE

CPM		EQU	TRUE	;TRUE if output via CPM, FALSE if hardware direct
DEBUG		EQU	TRUE	;TRUE for error messages
VERBOSE		EQU	FALSE	;TRUE for extended error messages
CPM$TRANSLATE	EQU	TRUE	;Translate Trk, Sec, Head to CPM TRACK# & SEC#

;------------------------------------------------------------------------------
;Drive number equates:
;------------------------------------------------------------------------------

IDE0		EQU	0	;Physical disk 0 drive number assignment
IDE1		EQU	1	;Physical disk 1 drive number assignment

;------------------------------------------------------------------------------
;Console equates:
;------------------------------------------------------------------------------

CONI		EQU	10H	;Console input port
CONO		EQU	11H	;Console output port

;------------------------------------------------------------------------------
;Display control equates:
;------------------------------------------------------------------------------

SCROLL		EQU	01H	;Set scroll direction UP
LF		EQU	0AH
CR		EQU	0DH
BS		EQU	08H	;Back space (required for sector display)
PERIOD		EQU	2EH
BELL		EQU	07H
SPACE		EQU	20H
QUIT		EQU	11H	;Turns off any screen enhancements
NO$ENHANCEMENT	EQU	17H	;Turns off whatever is on
FAST		EQU	10H	;High speed scrool
TAB		EQU	09H	;TAB ACROSS (8 SPACES FOR SD-BOARD)
ESC		EQU	1BH
CLEAR		EQU	1CH	;Clear line (Use 80 spaces if not available)

;------------------------------------------------------------------------------
;IDE Interface equates:
;------------------------------------------------------------------------------
;Ports for 8255 chip. Change these to specify where your 8255 is addressed,
;The first three control which 8255 ports have the control signals,
;upper and lower data bytes.  The last one (IDEportCtrl), is for mode setting
;for the 8255 to configure its actual I/O ports (A,B & C).  
;
;Note most drives these days don't use the old Head,Track, Sector terminology.
;Instead we use "Logical Block Addressing" or LBA. This is what we use below.
;LBA treats the drive as one continous set of sectors, 0,1,2,3,... 3124,...etc.
;However as seen below we need to convert this LBA to heads,tracks and sectors
;to be compatible with CPM & MSDOS.
;
;NOTE: If you have only one drive/CF card, be sure it is in drive #0.
;The IDE hardware gets confused if there is only a drive in slot #1.
;------------------------------------------------------------------------------

IDEportA	EQU	030H	;Lower 8 bits of IDE interface (8255)
IDEportB	EQU	031H	;Upper 8 bits of IDE interface
IDEportC	EQU	032H	;Control lines for IDE interface
IDEportCtrl	EQU	033H	;8255 configuration port
IDEDrive	EQU	034H	;Bit 0 - 0 for drive 0 and 1 for drive 1

READcfg8255	EQU	10010010b ;Set 8255 IDEportC to output, IDEportA/B input
WRITEcfg8255	EQU	10000000b ;Set all three 8255 ports to output mode

;------------------------------------------------------------------------------
;IDE control lines for use with IDEportC.  
;------------------------------------------------------------------------------

IDEa0line	EQU	01H	;direct from 8255 to IDE interface
IDEa1line	EQU	02H	;direct from 8255 to IDE interface
IDEa2line	EQU	04H	;direct from 8255 to IDE interface
IDEcs0line	EQU	08H	;inverter between 8255 and IDE interface
IDEcs1line	EQU	10H	;inverter between 8255 and IDE interface
IDEwrline	EQU	20H	;inverter between 8255 and IDE interface
IDErdline	EQU	40H	;inverter between 8255 and IDE interface
IDErstline	EQU	80H	;inverter between 8255 and IDE interface

;------------------------------------------------------------------------------
;Symbolic constants for the IDE drive registers
;------------------------------------------------------------------------------

REGdata		EQU	IDEcs0line
REGerr		EQU	IDEcs0line + IDEa0line
REGseccnt	EQU	IDEcs0line + IDEa1line
REGsector	EQU	IDEcs0line + IDEa1line + IDEa0line
REGcylinderLSB	EQU	IDEcs0line + IDEa2line
REGcylinderMSB	EQU	IDEcs0line + IDEa2line + IDEa0line
REGshd		EQU	IDEcs0line + IDEa2line + IDEa1line
REGcommand	EQU	IDEcs0line + IDEa2line + IDEa1line + IDEa0line
REGstatus	EQU	IDEcs0line + IDEa2line + IDEa1line + IDEa0line
REGcontrol	EQU	IDEcs1line + IDEa2line + IDEa1line
REGastatus	EQU	IDEcs1line + IDEa2line + IDEa1line + IDEa0line

;------------------------------------------------------------------------------
;IDE Command Constants.  These should never change.
;------------------------------------------------------------------------------

COMMANDrecal	EQU	10H
COMMANDread	EQU	20H
COMMANDwrite	EQU	30H
COMMANDinit	EQU	91H
COMMANDid	EQU	0ECH
COMMANDspindown	EQU	0E0H
COMMANDspinup	EQU	0E1H

;------------------------------------------------------------------------------
;IDE Status Register:
;------------------------------------------------------------------------------

;  bit 7: Busy	1=busy, 0=not busy
;  bit 6: Ready 1=ready for command, 0=not ready yet
;  bit 5: DF	1=fault occurred
;  bit 4: DSC	1=seek complete
;  bit 3: DRQ	1=data request ready, 0=not ready to xfer yet
;  bit 2: CORR	1=correctable error occurred
;  bit 1: IDX	vendor specific
;  bit 0: ERR	1=error occured
;------------------------------------------------------------------------------

;------------------------------------------------------------------------------
;Disk equates:
;------------------------------------------------------------------------------

SEC$SIZE	EQU	512	;Bytes per sector
MAXSEC		EQU	3DH	;Sectors per track
MAXTRK		EQU	0FFH	;CPM3 allows up to 8MG so 0-256 "tracks"
BUFFER$ORG	EQU	3000H	;<----- Will place all sector data here

CPM$BOOT$COUNT	EQU	12	;Allow up to 12 CPM sectors for CPMLDR
CPMLDR$ADDRESS	EQU	BUFFER$ORG

RDCON		EQU	1	;For CP/M I/O
WRCON		EQU	2
RESET$DISK	EQU	0DH	;Reset all CPM disks
PRINT		EQU	9
CONST		EQU	11	;CONSOLE STAT
BDOS		EQU	5

;------------------------------- INITIALIZATION -------------------------------	

	ORG	100H		;<--- For CPM

begin:
	LXI	SP, STACK
	LXI     D, SIGN$ON	;Print welcome message
	CALL	PSTRING
  IF VERBOSE
	LXI	D, SEL0MSG	;Print select drive 0 message
	CALL	PSTRING
  ENDIF
	MVI	A, IDE0
	STA	@CURRENT$DRIVE
	OUT	IDEDrive	;Select first drive
	
	CALL	CLEAR$ID$BUFFER	;Clear ID Buffer
  IF VERBOSE
	LXI	D, INITDRIVE	;Print initialization message
	CALL	PSTRING
  ENDIF
	CALL	IDEinit		;Initialize the board and first drive
	JZ	INIT$OK		;Continue on Zero
	
	LXI	D, INIT$0$ERROR	;Non-zero is error, probably no drive
	CALL	PSTRING
	JMP	ABORT
	
INIT$OK:			;Get drive 0 identification info			
	CALL	driveid
	JZ	INIT$OK1

	LXI	D, ID$ERROR	;End program on error
	CALL	PSTRING
	JMP	ABORT

INIT$OK1:			;Check sector count
	LXI     H, IDbuffer + 12
	MOV	A, M		;(High Byte)
	ORA	A
	JNZ	INIT$OK2
	INX	H
	MOV	A, M		;(Low Byte)
	ORA	A
	JNZ	INIT$OK2	;Looks like we have a valid IDE drive
	
	LXI	D, BAD$DRIVE	;Zero sectors means something's wrong
	CALL	PSTRING
	JMP	ABORT		;No drive #0 so abort

INIT$OK2:			;Print drive 0 info
	LXI	D, DRIVE0$INFO
	CALL	PSTRING
	LXI     D, msgmdl	;Drive name	
	CALL	PSTRING
	LXI     H, IDbuffer + 54
	MVI	B, 20		;Character count in words
	CALL	printSwap	;Print [HL], [B] X 2 characters
	CALL	ZCRLF

	LXI     D, msgsn	;Serial number
	CALL	PSTRING
	LXI     H, IDbuffer + 20
	MVI	B, 10		;Character count in words
	CALL	printText
	CALL	ZCRLF

	LXI     D, msgrev	;Firmware revision string
	CALL	PSTRING
	LXI     H, IDbuffer + 46
	MVI	B, 4		;Character count in words
	CALL	printSwap
	CALL	ZCRLF

	LXI     D, msgcy	;Drive specs (cyl/hd/sect)
	CALL	PSTRING
	LXI     H, IDbuffer + 2
	CALL	printparm
	LXI     D, msghd
	CALL	PSTRING
	LXI     H, IDbuffer + 6
	CALL	printparm
	LXI     D, msgsc
	CALL	PSTRING
	LXI     H, IDbuffer + 12
	CALL	printparm
	CALL	ZCRLF

INIT$OK3:			;Move to second drive
	CALL	ZCRLF
  IF VERBOSE
	LXI	D, SEL1MSG	;Print select drive 1 message
	CALL	PSTRING
  ENDIF
	MVI	A, IDE1		
	STA	@CURRENT$DRIVE
	OUT	IDEDrive	

	CALL	CLEAR$ID$BUFFER	;Clear ID Buffer
   IF VERBOSE
	LXI	D, INITDRIVE	;Print initialization message
	CALL	PSTRING
   ENDIF
	CALL	IDEinit		;Initialize the second drive
	JZ	INIT$OK4

	LXI	D, INIT$1$ERROR	;Non-zero is error, so print warning
	CALL	PSTRING
	JMP	MAINLOOP

INIT$OK4:			;Get drive 1 identification info			
	CALL	driveid
	JZ	INIT$OK5

	LXI	D, ID$ERROR	;On error, display message
	CALL	PSTRING
	JMP	MAINLOOP	;Continue to main menu

INIT$OK5:			;Check sector count
	LXI     H, IDbuffer + 12
	MOV	A, M		;(High Byte)
	ORA	A
	JNZ	INIT$OK6
	INX	H
	MOV	A, M		;(Low Byte)
	ORA	A
	JNZ	INIT$OK6	;Looks like we have a valid IDE drive
BAD$DR1:	
	LXI	D, BAD$DRIVE	;Zero sectors, so display error
	CALL	PSTRING
	JMP	MAINLOOP	;Continue to main menu

INIT$OK6:			;Print drive 1 info
	LXI	D, DRIVE1$INFO
	CALL	PSTRING
	LXI     D, msgmdl	;Drive name	
	CALL	PSTRING
	LXI     H,IDbuffer + 54
	MVI	B, 20		;Character count in words
	CALL	printSwap	;Print [HL], [B] X 2 characters
	CALL	ZCRLF

	LXI     D, msgsn	;Serial number
	CALL	PSTRING
	LXI     H, IDbuffer + 20
	MVI	B, 10		;Character count in words
	CALL	printText
	CALL	ZCRLF

	LXI     D, msgrev	;Firmware revision string
	CALL	PSTRING
	LXI     H, IDbuffer + 46
	MVI	B, 4		;Character count in words
	CALL	printSwap
	CALL	ZCRLF

	LXI     D, msgcy	;Drive specs (cyl/hd/sect)
	CALL	PSTRING
	LXI     H, IDbuffer + 2
	CALL	printparm
	LXI     D, msghd
	CALL	PSTRING
	LXI     H, IDbuffer + 6
	CALL	printparm
	LXI     D, msgsc
	CALL	PSTRING
	LXI     H, IDbuffer + 12
	CALL	printparm
	CALL	ZCRLF

INIT$DONE:			;Cleanup and enter main menu
	CALL	CLEAR$ID$BUFFER
	CALL	IDEinit		;Re-initialize drive 1
	MVI	A, IDE0
	STA	@CURRENT$DRIVE	;Select drive 0
	OUT	IDEDrive
	CALL	IDEinit		;Re-initialize drive 0
	LXI	H, 0
	SHLD	@SEC		;Default to track 0 and sector 0
	SHLD	@TRK
	LXI	H, buffer	;Set DMA address to buffer
	SHLD	@DMA
	JMP	MAINLOOP	;Display Main Menu


;------------------------------------------------------------------------------	


ABORT:				;Controlled termination
  IF CPM
	MVI	C, RESET$DISK	;Reset all disks
	JMP	0FF00H		;Reboot	CPM
  ELSE
	JMP	0F800H		;Transfer control to Monitor ROM	
  ENDIF


;--------------------------------- MAIN LOOP ----------------------------------	


MAINLOOP:			;Print main menu
	LDA	@CURRENT$DRIVE
	ORA	A
	JNZ	DRIVE$1$MENU
	LXI	D, DRIVE$0$MSG
	CALL	PSTRING
	JMP	Display0
DRIVE$1$MENU:
	LXI	D, DRIVE$1$MSG
	CALL	PSTRING
Display0:
	LDA	@DisplayFlag	;Sector data display flag on or off
	ORA	A		;NZ = on (Initially 0FFH so display on)
	JNZ     Display1
	LXI     D, CMD$STRING1	;List command options (Turn display option on)
	JP	Display2
Display1:
	LXI     D, CMD$STRING2	;List command options (Turn display option off)
Display2:
	CALL	PSTRING
	
	CALL	wrlba		;Update LBA on drive
	CALL	DISPLAYposition	;Display current Track,sector,head#
	
	LXI	D, Prompt	;'>'
	CALL	PSTRING
	
	CALL	GETCMD		;Simple UC character Input
	CPI	ESC		;Abort if ESC
	JZ	ABORT
	CALL	upper
	CALL	ZCRLF
	
	SBI	'@'		;Adjust to 0,1AH
	
	ADD	A		;X2
	LXI	H, TBL		;Get menu selection
	ADD	L
	MOV	L, A
	MOV	A, M
	INX	HL
	MOV	H, M
	MOV	L, A		;Jump to table pointer
	PCHL			;JMP (HL)
	
	
	
;-------------------------------- MENU OPTIONS --------------------------------	


READ$SEC:			;Read Sector @ LBA to the RAM buffer
	LXI	H,buffer	;Point to buffer
	SHLD	@DMA

	CALL	READSECTOR

	JZ	main1b		;Z means the sector read was OK
	CALL	ZCRLF
	JMP	MAINLOOP

main1b:	LXI     D, msgrd	;Sector read OK
	CALL	PSTRING

	LDA	@DisplayFlag	;Do we have display flag on or off
	ORA	A		;NZ = on 
	JZ	MAINLOOP
	LXI	H, buffer	;Point to buffer. Show sector data flag is on
	SHLD	@DMA
	CALL	HEXDUMP		;Show sector data
	JMP	MAINLOOP

WRITE$SEC:			;Write data in RAM buffer to sector @ LBA
	LXI     D, msgsure	;Are you sure?
	CALL	PSTRING
	CALL	ZCI
	CALL	upper
	CPI	'Y'
	JNZ	main2c
	CALL	ZCRLF

	LXI	H, buffer	;Point to buffer
	SHLD	@DMA

	CALL	WRITESECTOR

	JZ	main2b		;Z means the sector write was OK
	CALL	ZCRLF
	JMP	MAINLOOP
main2b:	LXI     D, msgwr	;Sector written OK
	CALL	PSTRING
main2c: JMP	MAINLOOP

SET$LBA:			;Set the logical block address
	LXI     D, GET$LBA	
	CALL	PSTRING
	CALL	ghex32lba	;Get CPM style Track & Sector, put in RAM 
	JC	main3b		;Ret C set if abort/error
	CALL	wrlba		;Update LBA on drive
main3b:	CALL	ZCRLF
	JMP	MAINLOOP

NEXT$SECT:
	LDA	@SEC
	INR	A		
	CPI	MAXSEC-1
	JNC	RANGE$ERROR
	STA	@SEC
	CALL	wrlba		;Update LBA on drive
	CALL	ZCRLF
	JMP	MAINLOOP

RANGE$ERROR:
	LXI     D, RANGE$MSG	
	CALL	PSTRING
	JMP	MAINLOOP
	
PREV$SEC:
	LDA	@SEC
	ORA	A
	JZ	RANGE$ERROR
	DCR	A
	STA	@SEC
	CALL	wrlba		;Update LBA on drive
	CALL	ZCRLF
	JMP	MAINLOOP
	
POWER$UP:			;Set the drive to spin up
	CALL	spinup
	JMP	MAINLOOP

POWER$DOWN:			;Set the drive to spin down
	CALL	spindown
	JMP	MAINLOOP

DISPLAY:			;Do we have display flag on or off
	LDA	@DisplayFlag	
	CMA			;flip it
	STA	@DisplayFlag
	JMP	MAINLOOP	;Update display and back to next menu command

SEQ$RD:				;Do sequential reads
	CALL	SequentialReads
	JMP	MAINLOOP

DRIVE$0:
	MVI	A, IDE0		;Select Drive 0:
	STA	@CURRENT$DRIVE
	OUT	IDEDrive
	LXI     D, SETA$MSG	
	CALL	PSTRING
	JMP	MAINLOOP

DRIVE$1:
	MVI	A, IDE1		;Select Drive 1:
	STA	@CURRENT$DRIVE
	OUT	IDEDrive
	LXI     D,SETB$MSG	
	CALL	PSTRING
	JMP	MAINLOOP

RAMCLEAR:			;Fill RAM buffer with 0's
	LXI	H, buffer	;Point to buffer
	LXI	D, 512
	MVI	A, 0		;Fill area with 0's
CLEAR1:	MOV	M, A
	INX	H
	DCX	D
	MOV	A, E
	ANA	D
	JNZ	CLEAR1
	LXI     D, FILL$MSG	
	CALL	PSTRING
	JMP	MAINLOOP
	
CPMBOOT:			;Boot CPM from IDE system tracks -- if present
	MVI	A, 0		;Load from track 0, sec 1, head 0 (always)
	STA	@SEC		;Remember sectors are numbered +1
	XRA	A
	STA	@TRK+1
	STA	@TRK

	MVI	A, CPM$BOOT$COUNT ;Count of CPMLDR sectors  (12)
	STA	@SECTOR$COUNT
	LXI	H, CPMLDR$ADDRESS ;DMA address where the CPMLDR resides in RAM
	SHLD	@DMA

NextRCPM:
	CALL	wrlba		;Update LBA on drive
	CALL	DISPLAYposition	;Display current track, sector, head
	CALL	ZCRLF
	
	LHLD	@DMA
	CALL	READSECTOR	;Read a sector
	SHLD	@DMA

	LDA	@SECTOR$COUNT
	DCR	A
	STA	@SECTOR$COUNT
	JZ	LOAD$DONE

	LHLD	@SEC
	INX	H
	SHLD	@SEC		;Stay on track 0 in this special case
	JMP	NextRCPM

LOAD$DONE:
	MVI	E, REGstatus	;Check the R/W status when done
	CALL	IDErd8D
	BIT	0, D
	JNZ	CPMLoadErr	;Zero if no errors
	LXI	H, CPMLDR$ADDRESS
	MOV	A, M
	CPI	31H		;EXPECT TO HAVE 31H @80H IE. LD SP,80H
	JNZ	CPMLoadErr1	;Zero if no errors
	
	LXI	D, MOVE$REQUEST	;Ask if we can move data to 100H
	CALL	PSTRING
	CALL	ZCI
	CALL	upper
	CPI	'Y'
	JNZ	MAINLOOP
	
	LXI	H, CPM$MOVE$CODE ;Need to move code out of the way.
	LXI	D, 0H
	LXI	B, (CPM$MOVE$CODE$END-CPM$MOVE$CODE)
	LDIR
	JMP	0H		;Now jump here to move the CPMLDR (@3000H) to 100H
	
CPMLoadErr1:
	LXI	D, CPM$ERROR1	;Drive data error
	CALL	PSTRING
	JMP	MAINLOOP
	
CPMLoadErr:
	LXI	D, CPM$ERROR	;Drive Read Error
	CALL	PSTRING
	JMP	MAINLOOP

N$RD$SEC:			;Read N sectors 
	LXI	D, ReadN$MSG	;No check for possible high RAM (CPM) overwrite
	CALL	PSTRING
	CALL	GETHEX
	JC	MAINLOOP	;Abort if ESC (C flag set)
	
	STA	@SECTOR$COUNT	;Store sector count
	LXI	H, buffer	;Point to buffer
	SHLD	@DMA

NextRSec:	
	LXI	D, ReadingN$MSG
	CALL	PSTRING
	CALL	wrlba		;Update LBA on drive
	CALL	DISPLAYposition	;Display current track, sector, head
	
	LHLD	@DMA
	CALL	READSECTOR
	SHLD	@DMA

	LDA	@SECTOR$COUNT
	DCR	A
	STA	@SECTOR$COUNT
	JZ	MAINLOOP
	
	LHLD	@SEC
	INX	H
	SHLD	@SEC	
	MOV	A, L		;0 to 62 CPM Sectors
	CPI	MAXSEC-1
	JNZ	NextRSec

	LXI	H, 0		;Back to CPM sector 0
	SHLD	@SEC
	LHLD	@TRK		;Bump to next track
	INX	H
	SHLD	@TRK
	MOV	A, L		;0-FFH tracks (only)
	JNZ	NextRSec
	
	LXI	D, AtEnd	;Tell us we are at end of disk
	CALL	PSTRING
	JMP	MAINLOOP

N$WR$SEC:			;Write N sectors 
	LXI     D, msgsure	;Are you sure?
	CALL	PSTRING
	CALL	ZCI
	CALL	upper
	CPI	'Y'
	JNZ	main2c

	LXI	D, WriteN$MSG
	CALL	PSTRING
	CALL	GETHEX
	JC	MAINLOOP	;Abort if ESC (C flag set)

	STA	@SECTOR$COUNT	;Store sector count
	LXI	H, buffer	;Point to buffer
	SHLD	@DMA

NextWSec:	
	LXI	D, WritingN$MSG
	CALL	PSTRING
	CALL	wrlba		;Update LBA on drive
	CALL	DISPLAYposition	;Display current track, sector, head
	
	LHLD	@DMA		;Actully, Sector/track values are already updated
	CALL	WRITESECTOR	;in wrlba, but WRITESECTOR is used in multiple places.
	SHLD	@DMA		;A repeat does no harm -- speed is not an issue here

	LDA	@SECTOR$COUNT
	DCR	A
	STA	@SECTOR$COUNT
	JZ	MAINLOOP
	
	LHLD	@SEC
	INX	H
	SHLD	@SEC	
	MOV	A, L		;0 to 62 CPM Sectors
	CPI	MAXSEC-1
	JNZ	NextWSec

	LXI	H, 0		;Back to CPM sector 0
	SHLD	@SEC
	LHLD	@TRK		;Bump to next track
	INX	H
	SHLD	@TRK
	MOV	A, L		;0-FFH tracks (only)
	ORA	A
	JNZ	NextWSec
	
	LXI	D, AtEnd	;Tell us we are at end of disk
	CALL	PSTRING
	JMP	MAINLOOP

FORMAT:				;Format (Fill sectors with E5)
	LXI	D, FORMAT$MSG
	CALL	PSTRING
	LXI     D, msgsure	;Are you sure?
	CALL	PSTRING
	CALL	ZCI
	CALL	upper
	CPI	'Y'
	JNZ	MAINLOOP
	LXI	H, buffer	;Fill buffer with 0E5's (512 of them)
	MVI	B, 0
Fill0:	MVI	A, 0E5H		;<-- Sector fill character (E5 for CPM)
	MOV	M, A
	INX	H
	MOV	M, A
	INX	H
	DCR	B
	JNZ	Fill0
	CALL	ZCRLF

NEXT$FORMAT:
	LXI	H, buffer
	SHLD	@DMA
	CALL	WRITESECTOR	;Will return error if there was one
	JZ	main9b		;Z means the sector write was OK
	CALL	ZCRLF
	JMP	MAINLOOP
main9b:	CALL	ZEOL		;Clear line cursor is on
	CALL	DISPLAYposition	;Display actual current Track,sector,head#
	CALL	ZCSTS		;Any keyboard character will stop display
	CPI	01H		;CPM Says something there
	JNZ	WRNEXTSEC1
	CALL	ZCI		;Flush character
	LXI	D, CONTINUE$MSG
	CALL	PSTRING
	CALL	ZCI
	CPI	ESC
	JZ	MAINLOOP
	CALL	ZCRLF
WRNEXTSEC1:
	LHLD	@SEC
	INX	H
	SHLD	@SEC		;0 to MAXSEC CPM Sectors
	MOV	A, L
	CPI	MAXSEC
	JNZ	NEXT$FORMAT

	LXI	H, 0		;Back to CPM sector 0
	SHLD	@SEC
	LHLD	@TRK		;Bump to next track
	INX	H
	SHLD	@TRK
	MOV	A, L		;0-FFH tracks (only)
	CPI	MAXTRK
	JNZ	NEXT$FORMAT	

	LXI	D, FormatDone	;Tell us we are all done.
	CALL	PSTRING
	JMP	MAINLOOP
				
BACKUP:				;Backup the CPM partition to another area
	LXI	D, CopyMsg
	CALL	PSTRING
	CALL	ZCI
	CALL	upper
	CPI	'Y'
	JNZ	MAINLOOP
	
	LXI	H, 0		;Start with CPM sector 0
	SHLD	@SEC
	SHLD	@SEC1
	SHLD	@SEC2		;and on second partition
	SHLD	@TRK		;and track 0
	SHLD	@TRK1
	LXI	H, MAXTRK+0200H+1
	SHLD	@TRK2 

	CALL	ZCRLF
	CALL	ZCRLF
	
NextCopy1:	
	CALL	ZEOL		;Clear line cursor is on
	LXI	D, RBackup$MSG	;for each track update display
	CALL	PSTRING
	LDA	@TRK1+1		;High TRK byte
	CALL	phex
	LDA	@TRK1		;Low TRK byte
	CALL	phex
	LXI	D, WBackup$MSG
	CALL	PSTRING
	LDA	@TRK2+1		;High TRK byte
	CALL	phex
	LDA	@TRK2		;Low TRK byte
	CALL	phex
	LXI	D, H$Msg
	CALL	PSTRING

NextCopy:	
	LDA	@SEC1
	STA	@SEC
	LHLD	@TRK1
	SHLD	@TRK
	CALL	wrlba		;Update LBA on "1st" drive

	LXI	H, buffer	;Point to buffer
	SHLD	@DMA
	CALL	READSECTOR	;Get sector data to buffer
	
	LDA	@SEC2
	STA	@SEC
	LHLD	@TRK2
	SHLD	@TRK
	CALL	wrlba		;Update LBA on "2nd" drive
	
	LXI	H,buffer	;Point to buffer
	SHLD	@DMA
	CALL	WRITESECTOR	;Write buffer data to sector
	
	CALL	ZCSTS		;Any keyboard character will stop display
	CPI	01H		;CPM Says something there
	JNZ	BKNEXTSEC1
	CALL	ZCI		;Flush character
	LXI	D, CONTINUE$MSG
	CALL	PSTRING
	CALL	ZCI
	CPI	ESC
	JZ	MAINLOOP

BKNEXTSEC1:
	LHLD	@SEC
	INX	H
	SHLD	@SEC1
	SHLD	@SEC2	
	MOV	A, L		;0 to 62 CPM Sectors
	CPI	MAXSEC-1
	JNZ	NextCopy

	LXI	H, 0		;Back to CPM sector 0
	SHLD	@SEC1
	SHLD	@SEC2
	
	LHLD	@TRK1		;Bump to next track
	INX	H
	SHLD	@TRK1
	
	LHLD	@TRK2		;Bump to next track
	INX	H
	SHLD	@TRK2
	
	LHLD	@TRK1		;Check if we are done
	MOV	A, L		;0-FFH tracks (only)
	CPI	MAXTRK
	JNZ	NextCopy1
	
	LXI	D, BackupDone	;Tell us we are all done.
	CALL	PSTRING
	JMP	MAINLOOP

RESTORE:			;Restore disk from backup partition
	LXI	D, RestoreMsg
	CALL	PSTRING
	CALL	ZCI
	CALL	upper
	CPI	'Y'
	JNZ	MAINLOOP
	
	LXI	H, 0		;Start with CPM sector 0
	SHLD	@SEC
	SHLD	@SEC1
	SHLD	@SEC2		;and on second partition
	SHLD	@TRK		;and track 0
	SHLD	@TRK1
	LXI	H, MAXTRK+0200H+1
	SHLD	@TRK2 

	CALL	ZCRLF
	CALL	ZCRLF
	
NextRestore1:	
	CALL	ZEOL		;Clear line cursor is on
	LXI	D, RBackup$MSG	;for each track update display
	CALL	PSTRING
	LDA	@TRK2+1		;High TRK byte
	CALL	phex
	LDA	@TRK2		;Low TRK byte
	CALL	phex
	LXI	D, WBackup$MSG
	CALL	PSTRING
	LDA	@TRK1+1		;High TRK byte
	CALL	phex
	LDA	@TRK1		;Low TRK byte
	CALL	phex
	LXI	D,H$Msg
	CALL	PSTRING

NextRestore:	
	LDA	@SEC2		;Point to backup partition
	STA	@SEC
	LHLD	@TRK2
	SHLD	@TRK
	CALL	wrlba		;Update LBA on "1st" drive

	LXI	H, buffer	;Point to buffer
	SHLD	@DMA
	CALL	READSECTOR	;Get sector data to buffer
	
	LDA	@SEC1
	STA	@SEC
	LHLD	@TRK1
	SHLD	@TRK
	CALL	wrlba		;Update LBA on "2nd" drive
	
	LXI	H,buffer	;Point to buffer
	SHLD	@DMA
	CALL	WRITESECTOR	;Write buffer data to sector
	
	CALL	ZCSTS		;Any keyboard character will stop display
	CPI	01H		;CPM Says something there
	JNZ	RESNEXTSEC1
	CALL	ZCI		;Flush character
	LXI	D, CONTINUE$MSG
	CALL	PSTRING
	CALL	ZCI
	CPI	ESC
	JZ	MAINLOOP

RESNEXTSEC1:
	LHLD	@SEC
	INX	H
	SHLD	@SEC1
	SHLD	@SEC2	
	MOV	A, L		;0 to 62 CPM Sectors
	CPI	MAXSEC-1
	JNZ	NextRestore

	LXI	H, 0		;Back to CPM sector 0
	SHLD	@SEC1
	SHLD	@SEC2
	
	LHLD	@TRK1		;Bump to next track
	INX	H
	SHLD	@TRK1
	
	LHLD	@TRK2		;Bump to next track
	INX	H
	SHLD	@TRK2
	
	LHLD	@TRK2		;Check if we are done
	MOV	A, L		;0-FFH tracks (only)
	CPI	MAXTRK
	JNZ	NextRestore1
	
	LXI	D, RestoreDone	;Inform user restore complete
	CALL	PSTRING
	JMP	MAINLOOP

ERROR:	LXI     D, msgErr	;CMD error msg
	CALL	PSTRING
	JMP	MAINLOOP

COPY$AB:			;Copy Drive 0: to Drive 1:
	LXI	D, DiskCopyMsg
	CALL	PSTRING
	CALL	ZCI
	CALL	upper
	CPI	'Y'
	JNZ	MAINLOOP
	
	LXI	H, 0		;Start with CPM sector 0
	SHLD	@SEC
	SHLD	@TRK		;and track 0
	
	CALL	ZCRLF
	CALL	ZCRLF
	
NextDCopy1:	
	CALL	ZEOL		;Clear line cursor is on
	LXI	D, CopyTrk$MSG	;for each track update display
	CALL	PSTRING
	LDA	@TRK+1		;High TRK byte
	CALL	phex
	LDA	@TRK		;Low TRK byte
	CALL	phex
	LXI	D, H$Msg
	CALL	PSTRING

NextDCopy:	
	MVI	A, IDE0		;Login drive 0:
	STA	@CURRENT$DRIVE
	OUT	IDEDrive

	CALL	wrlba		;Update LBA on "0:" drive

	LXI	H, buffer	;Point to buffer
	SHLD	@DMA
	CALL	READSECTOR	;Get sector data from 0: drive to buffer
	
	MVI	A, IDE1		;Login drive 1:
	STA	@CURRENT$DRIVE
	OUT	IDEDrive

	CALL	wrlba		;Update LBA on "1:" drive
	
	LXI	H, buffer	;Point to buffer
	SHLD	@DMA
	CALL	WRITESECTOR	;Write buffer data to sector on 1: drive
	
	CALL	ZCSTS		;Any keyboard character will stop display
	CPI	01H		;CPM says something is there
	JNZ	BK$D$NEXTSEC1
	CALL	ZCI		;Flush character
	LXI	D, CONTINUE$MSG
	CALL	PSTRING
	CALL	ZCI
	CPI	ESC
	JNZ	BK$D$NEXTSEC1
	MVI	A,IDE0		;Login drive 1:
	STA	@CURRENT$DRIVE
	OUT	IDEDrive
	JMP	MAINLOOP

BK$D$NEXTSEC1:
	LHLD	@SEC
	INX	H
	SHLD	@SEC
	MOV	A, L		;0 to 62 CPM Sectors
	CPI	MAXSEC-1
	JNZ	NextDCopy

	LXI	H, 0		;Back to CPM sector 0
	SHLD	@SEC
	
	LHLD	@TRK		;Bump to next track
	INX	H
	SHLD	@TRK
				;Check if we are done
	MOV	A, L		;0-FFH tracks (only)
	CPI	MAXTRK
	JNZ	NextDCopy1
	
	LXI	D, CopyDone	;Inform user copy complete
	CALL	PSTRING
	MVI	A, IDE0		;Login drive 0:
	STA	@CURRENT$DRIVE
	OUT	IDEDrive
	JMP	MAINLOOP

VERIFY$AB:			;Verify Drive 0: = 1:
	LXI	D, DiskVerifyMsg
	CALL	PSTRING
	
	LXI	H, 0		;Start with CPM sector 0
	SHLD	@SEC
	SHLD	@TRK		;and track 0
	
	CALL	ZCRLF
	CALL	ZCRLF
	
NextVCopy1:	
	CALL	ZEOL		;Clear line cursor is on
	LXI	D, VerifyTrk$MSG ;for each track update display
	CALL	PSTRING
	LDA	@TRK+1		;High TRK byte
	CALL	phex
	LDA	@TRK		;Low TRK byte
	CALL	phex
	LXI	D, H$Msg
	CALL	PSTRING

NextVCopy:	
	MVI	A, IDE0		;Login drive 0:
	STA	@CURRENT$DRIVE
	OUT	IDEDrive

	CALL	wrlba		;Update LBA on "0:" drive

	LXI	H, buffer	;Point to buffer
	SHLD	@DMA
	CALL	READSECTOR	;Get sector data from buffer 0: drive
	
	MVI	A, IDE1		;Login drive 1:
	STA	@CURRENT$DRIVE
	OUT	IDEDrive

	CALL	wrlba		;Update LBA on "1:" drive
	
	LXI	H, buffer2	;Point to buffer2
	SHLD	@DMA
	CALL	READSECTOR	;Read buffer data from sector of 1 drive
	
	LXI	BC, 512		;Now check both buffers are identical
	LXI	H, buffer
	LXI	D, buffer2
NEXTV:	LDAX	D
	CMP	M		;Is [DE] = [HL]
	JNZ	COMPARE$ERROR
	INX	H
	INX	D
	DCX	B
	MOV	A,C
	ANA	B
	JZ	VERIFY$OK
	JMP	NEXTV

COMPARE$ERROR:
	LXI	D, VERIFY$ERR	;Indicate an error
	CALL	PSTRING
	LDA	@TRK+1		;High TRK byte
	CALL	phex
	LDA	@TRK		;Low TRK byte
	CALL	phex
	LXI	D, SEC$Msg
	CALL	PSTRING
	LDA	@SEC		;Sector byte
	CALL	phex
	LXI	D, H$Msg
	CALL	PSTRING
	JMP	VER$OK1
	
VERIFY$OK:
	CALL	ZCSTS		;Any keyboard character will stop display
	CPI	01H		;CPM says something is there
	JNZ	BK$V$NEXTSEC1
	CALL	ZCI		;Flush character
VER$OK1:
	LXI	D, CONTINUE$MSG
	CALL	PSTRING
	CALL	ZCI
	CPI	ESC
	JNZ	BK$V$NEXTSEC1
	MVI	A,IDE0		;Login drive 0:
	STA	@CURRENT$DRIVE
	OUT	IDEDrive
	JMP	MAINLOOP

BK$V$NEXTSEC1:
	LHLD	@SEC
	INX	H
	SHLD	@SEC
	MOV	A, L		;0 to 62 CPM Sectors
	CPI	MAXSEC-1
	JNZ	NextVCopy

	LXI	H, 0		;Back to CPM sector 0
	SHLD	@SEC
	
	LHLD	@TRK		;Bump to next track
	INX	H
	SHLD	@TRK
				;Check if we are done
	MOV	A, L		;0-FFH tracks (only)
	CPI	MAXTRK
	JNZ	NextVCopy1
	
	LXI	D, VerifyDone	;Tell us we are all done.
	CALL	PSTRING
	MVI	A, IDE0		;Login drive 0:
	STA	@CURRENT$DRIVE
	OUT	IDEDrive
	JMP	MAINLOOP
	

;----------------------------- SUPPORT FUNCTIONS ------------------------------	

				
driveid:CALL	IDEwaitnotbusy	;Retrieve drive info
	RC
	MVI	D, COMMANDid
	MVI	E, REGcommand
	CALL	IDEwr8D		;Issue the ID command
  IF VERBOSE
	LXI	D, READING$ID
	CALL	PSTRING
	LXI	D, DISKSTATUS	;Print status message
	CALL	PSTRING
  ENDIF
	MVI	E, REGstatus	;Get status after ID command
	CALL	IDErd8D		;Check Status (info in [D])
  IF VERBOSE
	MOV	A, D
	CALL	PHEX		;Print status
	CALL	ZPERCRLF
  ENDIF
	CALL	IDEwaitdrq	;Wait for Busy=0, DRQ=1
	JC	SHOWerrors
  IF VERBOSE
	LXI	D, GETTING$ID
	CALL	PSTRING
  ENDIF
	MVI	B, 0		;256 words
	LXI     H, IDbuffer	;Store data here
	CALL	MoreRD16	;Get 256 words of data from REGdata port to [HL]
	RET

spinup:				;Start the drive
	MVI	D, COMMANDspinup
spup2:	MVI	E, REGcommand
	CALL	IDEwr8D
	CALL	IDEwaitnotbusy
	JC	SHOWerrors
	ORA	A		;Clear carry
	RET
				
spindown:			;Tell the drive to spin down
	CALL	IDEwaitnotbusy
	JC	SHOWerrors
	MVI	D,COMMANDspindown
	JMP	spup2

SequentialReads:		;Sequentially read sectors from current position
	CALL	IDEwaitnotbusy
	JC	SHOWerrors
	CALL	ZCRLF
NEXTSEC:
	LXI	H, buffer	;Point to buffer
	SHLD	@DMA

	CALL	READSECTOR	;Errors will show in READSECTOR
	JZ	SEQOK
	LXI	D, CONTINUE$MSG
	CALL	PSTRING
	CALL	ZCI
	CPI	ESC		;Abort if ESC
	RZ
	
SEQOK:	CALL	ZEOL		;Clear line cursor is on
	CALL	DISPLAYposition	;Display current track, sector, head

	LXI	H, buffer	;Point to buffer
	SHLD	@DMA

	LDA	@DisplayFlag	;Do we have display flag on or off
	ORA	A		;NZ = on 
	CNZ	HEXDUMP
	CALL	ZCRLF
	CALL	ZCRLF
	CALL	ZCRLF

	CALL	ZCSTS		;Any keyboard character will stop display
	CPI	01H		;CPM Says something there
	JNZ	NEXTSEC1
	CALL	ZCI		;Flush character
	LXI	D, CONTINUE$MSG
	CALL	PSTRING
	CALL	ZCI
	CPI	ESC
	RZ
	CALL	ZCRLF
NEXTSEC1:
	LHLD	@SEC
	INX	H
	SHLD	@SEC	
	MOV	A, L		;0 to 62 CPM Sectors
	CPI	MAXSEC-1
	JNZ	NEXTSEC

	LXI	H, 0		;Back to CPM sector 0
	SHLD	@SEC
	LHLD	@TRK		;Bump to next track
	INX	H
	SHLD	@TRK
	JMP	NEXTSEC		;Note will go to last sec on disk unless stopped

DISPLAYposition:		;Display current track, sector & head position
	LXI     D, msgCPMTRK	;Display in LBA format
	CALL	PSTRING		;---- CPM FORMAT ----
	LDA	@TRK+1		;High TRK byte
	CALL	phex
	LDA	@TRK		;Low TRK byte
	CALL	phex
		
	LXI     D, msgCPMSEC
	CALL	PSTRING		;SEC = (16 bits)
	LDA	@SEC+1		;High Sec
	CALL	phex
	LDA	@SEC		;Low sec
	CALL	phex
				;---- LBA FORMAT ----
	LXI     D, msgLBA
	CALL	PSTRING		;LBA = 00 ("Heads" = 0 for these drives)
	LDA	@DRIVE$TRK+1	;High "cylinder" byte
	CALL	phex
	LDA	@DRIVE$TRK	;Low "cylinder" byte
	CALL	phex	
	LDA	@DRIVE$SEC
	CALL	phex
	LXI     D, MSGBracket
	CALL	PSTRING		
	RET

printText:			;Print text up to [B] (16-bit word) byte-pairs
	MOV	C, M		;Text is contiguous byte array
	CALL	ZCO	
	INX	H
	MOV	C, M
	CALL	ZCO	
	INX	H
	DCR	B
	JNZ	printText
	RET

printSwap:			;Print text up to [B] (16-bit word) byte-pairs	
	INX	H		;Swap byte pairs - low byte, high byte
	MOV	C, M
	CALL	ZCO	
	DCX	H
	MOV	C, M
	CALL	ZCO
	INX	H
	INX	H
	DCR	B
	JNZ	printSwap
	RET

ZCRLF:
	PUSH	PSW
	MVI	C, CR
	CALL	ZCO
	MVI	C, LF
	CALL	ZCO
	POP	PSW
	RET

ZPERCRLF:
	PUSH	PSW
	MVI	C, PERIOD
	CALL	ZCO
	MVI	C, CR
	CALL	ZCO
	MVI	C, LF
	CALL	ZCO
	POP	PSW
	RET

ZEOL:				;CR and clear current line
	MVI	C, CR
	CALL	ZCO
	MVI	C, CLEAR	;Use 80 spaces if necessary
	CALL	ZCO
	RET

ZCSTS:
  IF CPM
	PUSH	B
	PUSH	D
	PUSH	H
	MVI	C, CONST
	CALL	BDOS		;Returns with 1 in [A] if character at keyboard
	POP	H
	POP	D
	POP	B
	CPI	1
	RET
  ELSE	
	IN	COMI		;Get Character in [A]
	ANI	02H
	RZ
	MVI	A, 01H
	ORA	A
	RET
  ENDIF

ZCO:				;Write character that is in [C]
  IF CPM
	PUSH	PSW
	PUSH	B
	PUSH	D
	PUSH	H
	MOV	E, C
	MVI	C, WRCON
	CALL	BDOS
	POP	H
	POP	D
	POP	B
	POP	PSW
	RET
  ELSE	
	PUSH	PSW	
ZCO1:	IN   	CONI		;Show Character
	ANI	04H
	JZ	ZCO1
	MOV	A, C
	OUT	CONO
	POP	PSW
	RET
  ENDIF

ZCI:				;Return keyboard character in [A]
  IF CPM
	PUSH	B
	PUSH	D
	PUSH	H
	MVI	C, RDCON
	CALL	BDOS
	POP	H
	POP	D
	POP	B
	RET
  ELSE
ZCI1:	IN	CONI		;Get Character in [A]
	ANI	02H
	JZ	ZCI1
	IN	CONO
	RET
  ENDIF

;------------------------------------------------------------------------------	
;Print a string in [DE] up to '$'
;------------------------------------------------------------------------------	

PSTRING:
  IF CPM
	MVI	C, PRINT
	JMP	BDOS		;PRINT MESSAGE, 
  ELSE
	PUSH	B
	PUSH	D
	PUSH	H
	XCHG
PSTRX:	MOV	A, M
	CPI	'$'
	JZ	DONEP
	MOV	C, A
	CALL	ZCO
	INX	H
	JMP	PSTRX
DONEP:	POP	H
	POP	D
	POP	B
	RET
  ENDIF

SHOWerrors:
  IF NOT DEBUG
	ORA	A		;Set NZ flag
	STC			;Set Carry Flag
	RET
  ELSE
	CALL	ZCRLF
	MVI	E, REGstatus	;Get status in status register
	CALL	IDErd8D
	MOV	A, D
	ANI	1H
	JNZ	MoreError	;Go to  REGerr register for more info
				;All OK if 01000000
	PUSH	PSW		;save for return below
	ANI	80H
	JZ	NOT7
	LXI	D, DRIVE$BUSY	;Drive Busy (bit 7) stuck high 
	CALL	PSTRING
	JMP	DONEERR
NOT7:	ANI	40H
	JNZ	NOT6
	LXI	D, DRIVE$NOT$READY ;Drive Not Ready (bit 6) stuck low 
	CALL	PSTRING
	JMP	DONEERR
NOT6:	ANI	20H
	JNZ	NOT5
	LXI	D, DRIVE$WR$FAULT ;Drive write fault
	CALL	PSTRING
	JMP	DONEERR
NOT5	LXI	D, UNKNOWN$ERROR
	CALL	PSTRING
	JMP	DONEERR

MoreError:			;Bit 0 of the status register indicates problem
	MVI	E, REGerr	;Get error code in REGerr
	CALL	IDErd8D
	MOV	A, D
	PUSH	PSW

	ANI	10H
	JZ	NOTE4
	LXI	D, SEC$NOT$FOUND
	CALL	PSTRING
	JMP	DONEERR

NOTE4:	ANI	80H
	JZ	NOTE7
	LXI	D, BAD$BLOCK
	CALL	PSTRING
	JMP	DONEERR
NOTE7:	ANI	40H
	JZ	NOTE6
	LXI	D, UNRECOVER$ERR
	CALL	PSTRING
	JMP	DONEERR
NOTE6:	ANI	4H
	JZ	NOTE2
	LXI	D, INVALID$CMD
	CALL	PSTRING
	JMP	DONEERR
NOTE2:	ANI	2H
	JZ	NOTE1
	LXI	D, TRK0$ERR
	CALL	PSTRING
	JMP	DONEERR
NOTE1:	LXI	D, UNKNOWN$ERROR1
	CALL	PSTRING
	JMP	DONEERR

DONEERR:POP	PSW
	PUSH	PSW
	CALL	ZBITS
	CALL	ZCRLF
	POP	PSW
	ORA	A		;Set Z flag
	STC			;Set Carry flag
	RET
   ENDIF

;------------------------------------------------------------------------------	
;Print a 16-bit number in RAM located @ [HL], low-byte first for Drive ID 
;------------------------------------------------------------------------------	

printparm:
	INX	H	;Index to high byte first
	MOV	A, M
	CALL	PHEX
	DCX	H	;Now low byte
	MOV	A, M
	CALL	PHEX
	RET

;------------------------------------------------------------------------------	
;Print an 8 bit number located in [A] 
;------------------------------------------------------------------------------	

PHEX:	PUSH	PSW
	PUSH	B
	PUSH	PSW
	RRC
	RRC
	RRC
	RRC
	CALL	ZCONV
	POP	PSW
	CALL	ZCONV
	POP	B
	POP	PSW
	RET

ZCONV:	ANI	0FH		;HEX to ASCII and print it
	ADI	90H
	DAA
	ACI	40H
	DAA
	MOV	C, A
	CALL	ZCO
	RET

;------------------------------------------------------------------------------	
;Display binary in [A] 
;------------------------------------------------------------------------------	

ZBITS:	PUSH	PSW
	PUSH	B
	PUSH	D
	MOV	E, A		
	MVI	B, 8
BQ2:	DB	0CBH, 23H	
	SLAR	E
	MVI	A, 18H
	ADC	A
	MOV	C, A
	CALL	ZCO
	DCR	B
	JNZ	BQ2
	POP	D
	POP	B
	POP	PSW
	RET

ghex32lba:			;Convert CPM Track & Sector to LBA format
	LXI     D,ENTER$SECL	;Enter sector number
	CALL	PSTRING
	CALL	GETHEX		;Get 2 HEX digits
	RC
	STA	@SEC
	CALL	ZCRLF

	LXI	D, ENTER$TRKH	;Enter high byte track number
	CALL	PSTRING
	CALL	GETHEX		;Get 2 HEX digits
	RC
	STA	@TRK+1
	CALL	ZCRLF

	LXI     D, ENTER$TRKL	;Enter low byte track number
	CALL	PSTRING
	CALL	GETHEX		;Get 2 more HEX digits
	RC
	STA	@TRK
	CALL	ZCRLF
	XRA	A
	ORA	A		;To return NC
	RET

;------------------------------------------------------------------------------	
;Get a HEX character from the keyboard and echo it 
;------------------------------------------------------------------------------	

GETHEX:
	CALL	GETCMD		;Get character
	CPI	ESC
	JZ	HEXABORT
	CPI	'/'		;check 0-9, A-F
	JC	HEXABORT
	CPI	'F'+1
	JNC	HEXABORT
	CALL	ASBIN		;Convert to binary
	RLC			;Shift to high nibble
	RLC
	RLC
	RLC
	MOV	B, A		;Store it
	CALL	GETCMD		;Get 2nd character from keyboard & ECHO
	CPI	ESC
	JZ	HEXABORT
	CPI	'/'		;check 0-9, A-F
	JC	HEXABORT
	CPI	'F'+1
	JNC	HEXABORT
	CALL	ASBIN		;Convert to binary
	ORA	B		;add in the first digit
	ORA	A		;To return NC
	RET
HEXABORT:
	STC			;Set Carry flag 
	RET

;------------------------------------------------------------------------------	
;Get a character from the keyboard, convert to uppercase and echo it 
;------------------------------------------------------------------------------	

GETCMD:	CALL	ZCI		;Get character
	CALL	UPPER
	CPI	ESC
	RZ			;Don't echo an ESC
  IF NOT CPM
	PUSH	PSW		;Save it
	PUSH	B
 	MOV	C, A
	CALL	ZCO		;Echo it
	POP	B
	POP	PSW		;get it back
  ENDIF
	RET

;------------------------------------------------------------------------------	
;Convert lowercase to uppercase 
;------------------------------------------------------------------------------	

UPPER:	CPI	'a'		;must be >= lowercase a
	RC			;else go back...
	CPI	'z'+1		;must be <= lowercase z
	RNC			;else go back...
	SUI	'a'-'A'		;subtract lowercase bias
	RET

ASBIN:	SUI	30H 		;ASCII to binary conversion
	CPI	0AH 
	RM
	SUI	07H 
	RET

;------------------------------------------------------------------------------	
;Print a hexdump of the data in the 512 byte buffer @[HL]
;------------------------------------------------------------------------------	

HEXDUMP:
	PUSH	PSW		;Save everything
	PUSH	B
	PUSH	D			 
	PUSH	H
	
	CALL	ZCRLF		;CR/LF first
	MVI	D, 32		;Print 32 lines total
	MVI	B, 16		;16 characters across
	SHLD	@StartLineHex	;Save buffer location for ASCII display below
	LXI	H, 0
	SHLD	@BYTE$COUNT
	
SF172:	CALL	ZCRLF
	LHLD	@BYTE$COUNT
	MOV	A, H
	CALL	PHEX		;Print byte count in sector
	MOV	A, L
	CALL	PHEX		
	PUSH	D
	LXI	D, 16
	DAD	D
	POP	D
	SHLD	@BYTE$COUNT	;Store for next time
	CALL	BLANK
	LHLD	@StartLineHex
	SHLD	@StartLineASCII	;Store for ASCII display below

SF175:	MOV	A, M
	CALL	LBYTE		;Display [A] on CRT/LCD
	INX	H
	DCR	B
	JNZ	SF175
	SHLD	@StartLineHex	;Save for next line later
	CALL	ShowAscii	;Now translate to ASCII and display
	MVI	B, 16		;16 characters across for next line
	DCR	D
	JNZ	SF172		;Have we done all 32 lines

	CALL	ZCRLF
	POP	H		;Get back original registers
	POP	D
	POP	B
	POP	PSW
	RET
	
ShowAscii:			;Show as ASCII info
	LHLD	@StartLineASCII
	MVI	B, 16		;16 ASCII characters across
XF172:	CALL	BLANK		;Send a space character
	CALL	BLANK
XF175:	MOV	A, M
	ANI	7FH
	CPI	' ' 		;Filter out control characters
	JNC	XT33
XT22:	MVI	A, '.'
XT33:	CPI	07CH
	JNC	XT22
	MOV	C, A		;Setup to send
	PUSH	B
	CALL	ZCO
	POP	B
	INX	H		;Next position in buffer
	DCR	B
	JNZ	XF175
	RET

BLANK:	PUSH	B
	PUSH	H
	MVI	C, ' '
	CALL	ZCO
	POP	H
	POP	B
	RET

LBYTE:	PUSH	PSW
	RRC
	RRC
	RRC
	RRC
	CALL	SF598
	POP	PSW

SF598:	CALL	ZCONV
	RET

;------------------------------------------------------------------------------	
;IDE Drive BIOS Routines written in a format that can be used directly with CPM
;------------------------------------------------------------------------------	

IDEinit:			;Initialize the 8255 and drive then do a hard reset
  IF VERBOSE
	LXI	D, INITDRIVE
	CALL	PSTRING
  ENDIF
	MVI	A, READcfg8255	;Config 8255 chip (10010010B)
	OUT	IDEportCtrl	;for READ mode
				
	MVI	A, IDErstline	;Hard reset the disk drive
	OUT	IDEportC	;Some CF cards are sensitive to reset pulse width
	MVI	B, 20H		;Symptom is incorrect data back from a sector read

ResetDelay:
	DCR	B
	JNZ	ResetDelay	;Delay (reset pulse width)

	XRA	A
	OUT	IDEportC	;No control lines asserted (just bit 7 of port C)
	CALL	DELAY$SHORT	;Short Delay

	MVI	D,11100000b	;Data for IDE SDH reg (512byte, LBA, single drive, hd 0)
				;For Trk, Sec, Head (non LBA) use 10100000

	MVI	E,REGshd	;00001110,(0EH) for CS0,A2,A1,  
	CALL	IDEwr8D		;Write byte to select the MASTER device

	MVI	B, 02H		;Delay time for hard disks to get up to speed (2s)

WaitInit:
  IF VERBOSE
	LXI	D, DISKSTATUS	;Print initialization status message
	CALL	PSTRING
  ENDIF
	MVI	E, REGstatus	;Get status after initilization
	CALL	IDErd8D		;Check Status (info in [D])
	MOV	A, D
  IF VERBOSE
	CALL	PHEX		;Print drive initialization status
	CALL	ZPERCRLF
  ENDIF
	ANI	80H
	RZ			;Return. We'll check for errors when we get back
	MVI	A, 2
	CALL	DELAY$LONG	;Long delay, drive has to get up to speed
	DCR	B
	JNZ	WaitInit
	XRA	A
	DCR	A
	RET			;Return NZ. We'll check for errors when we get back
	
DELAY$LONG:			;Long delay (Seconds)
	STA	@DELAYStore
	PUSH	B
	LXI	B, 0FFFFH
DELAY2:	LDA	@DELAYStore
DELAY1:	DCR	A
	JNZ	DELAY1
	DCX	B
	MOV	A, C
	ORA	B
	JNZ	DELAY2
	POP	B
	RET

DELAY$SHORT:			;Short delay (32ms)
	MVI	A, 40
DELAY3:	MVI	B, 0
M0:	DCR	B
	JNZ	M0
	DCR	A
	JNZ     DELAY3 
	RET

;------------------------------------------------------------------------------	
;Sector Read
;------------------------------------------------------------------------------	

READSECTOR:			;Read a sector, specified by the 3 bytes in LBA
				;Z on success, NZ call error routine if problem

	CALL	wrlba		;Tell which sector we want to read from.
				;Translate first in case of an error, otherewise 
				;we will get stuck on bad sector
 
	CALL	IDEwaitnotbusy	;Make sure drive is ready
	JC	SHOWerrors	;Returned with NZ set if error

	MVI	D, COMMANDread
	MVI	E, REGcommand
	CALL	IDEwr8D		;Send sec read command to drive.
	CALL	IDEwaitdrq	;Wait until it's got the data
	JC	SHOWerrors
		
	LHLD  	@DMA		;DMA address
	MVI	B, 0		;Read 512 bytes to [HL]

MoreRD16:
	MVI	A, REGdata	;REG register address
	OUT	IDEportC	

	ORI	IDErdline	;08H+40H, Pulse RD line
	OUT	IDEportC	

	IN	IDEportA	;Read the lower byte first
	MOV	M, A
	INX	H
	IN	IDEportB	;Then read the upper byte
	MOV	M, A
	INX	H
	
	MVI	A, REGdata	;Deassert RD line
	OUT	IDEportC
	DCR	B
	JNZ	MoreRD16

	MVI	E, REGstatus
	CALL	IDErd8D
	MOV	A, D
	ANI	1H
	CNZ	SHOWerrors	;If error display status
	RET

;------------------------------------------------------------------------------	
;Sector Write
;------------------------------------------------------------------------------	

WRITESECTOR:			;Write a sector, specified by the 3 bytes in LBA
				;Z on success, NZ to error routine if problem

	CALL	wrlba		;Tell which sector we want to read from.
				;Translate first in case of an error, otherewise 
				;we will get stuck on bad sector 

	CALL	IDEwaitnotbusy	;Make sure drive is ready
	JC	SHOWerrors

	MVI	D, COMMANDwrite
	MVI	E, REGcommand
	CALL	IDEwr8D		;Tell drive to write a sector
	CALL	IDEwaitdrq	;Wait unit it wants the data
	JC	SHOWerrors

	LHLD    @DMA
	MVI	B, 0

	MVI	A, WRITEcfg8255
	OUT	IDEportCtrl
	
WRSEC1:	MOV	A, M
	INX	H
	OUT	IDEportA	;Write the lower byte first
	MOV	A, M
	INX	H
	OUT	IDEportB	;Then high byte on B

	MVI	A, REGdata
	PUSH	PSW
	OUT	IDEportC	;Send write command
	ORI	IDEwrline	;Send WR pulse
	OUT	IDEportC
	POP	PSW
	OUT	IDEportC
	DCR	B
	JNZ	WRSEC1
	
	MVI	A, READcfg8255	;Set 8255 back to read mode
	OUT	IDEportCtrl	

	MVI	E, REGstatus
	CALL	IDErd8D
	MOV	A, D
	ANI	1H
	CNZ	SHOWerrors	;If error display status
	RET

;------------------------------------------------------------------------------	
;Write Logical Block Address (LBA) mode
;------------------------------------------------------------------------------	

wrlba:
	LDA	@SEC		;LBA mode low sectors go directly 
	INR	A		;Sectors are numbered 1 -- MAXSEC
	STA	@DRIVE$SEC	;For Diagnostic Display Only
	MOV	D, A
	MVI	E, REGsector	;Send info to drive
	CALL	IDEwr8D
				
	LHLD	@TRK		
	MOV	A, L
	STA	@DRIVE$TRK
	MOV	D, L		;Send Low TRK#
	MVI	E, REGcylinderLSB
	CALL	IDEwr8D

	MOV	A, H
	STA	@DRIVE$TRK+1
	MOV	D, H		;Send High TRK#
	MVI	E, REGcylinderMSB
	CALL	IDEwr8D

	MVI	D, 1		;For now, one sector at a time
	MVI	E, REGseccnt
	CALL	IDEwr8D
	RET

;------------------------------------------------------------------------------	
;Wait for drive to come ready
;------------------------------------------------------------------------------	

IDEwaitnotbusy:			;Drive READY if status = 01000000
	MVI	B, 0FFH
	MVI	A, 0FFH		;Delay must be above 80H, longer for slow drives
	STA	@DELAYStore

MoreWait:
	MVI	E, REGstatus	;Wait for RDY bit to be set
	CALL	IDErd8D
	MOV	A, D
	ANI	11000000B
	XRI	01000000B
	JZ	DoneNotbusy
	DCR	B	
	JNZ	MoreWait
	LDA	@DELAYStore	;Check timeout delay
	DCR	A
	STA	@DELAYStore
	JNZ	MoreWait

	STC			;Set carry to indicate an error
	ret
DoneNotBusy:
	ORA	A		;Clear carry it indicate no error
	RET

;------------------------------------------------------------------------------	
;Wait for drive to assert data request (DRQ) line is ready
;------------------------------------------------------------------------------	

IDEwaitdrq:
	MVI	B, 0FFH
	MVI	A, 0FFH		;Delay must be above 80H, longer for slow drives
	STA	@DELAYStore

MoreDRQ:
	MVI	E, REGstatus	;Wait for DRQ bit to be set
	CALL	IDErd8D
	MOV	A, D
	ANI	10001000B
	CPI	00001000B
	JZ	DoneDRQ
	DCR	B
	JNZ	MoreDRQ
	LDA	@DELAYStore	;Check timeout delay
	DCR	A
	STA	@DELAYStore
	JNZ	MoreDRQ
	STC			;Set carry to indicate error
	RET
DoneDRQ:
	ORA	A		;Clear carry
	RET			;Return drive status in A

;------------------------------------------------------------------------------	
;Clear the ID buffer
;------------------------------------------------------------------------------	

CLEAR$ID$BUFFER:
	LXI	H, IDBuffer
	LXI	B, 512
CLEAR2:	MVI	A, ' '
	MOV	M, A
	INX	H
	DCX	B
	MOV	A, C
	ORA	B
	JNZ	CLEAR2
	
	LXI	H, IDBuffer	;Zero for cylinder, heads, sectors
	LXI	B, 14
CLEAR3:	MVI	A, 0
	MOV	M, A
	INX	H
	DCX	B
	MOV	A, C
	ORA	B
	JNZ	CLEAR3
	RET

;------------------------------------------------------------------------------	
; Low Level 8 bit R/W to the drive controller. These are the routines that talk
; directly to the drive controller registers, via the 8255 chip.  
; Note the 16 bit I/O to the drive (which is only for SEC R/W) is done directly 
; in the routines READSECTOR & WRITESECTOR for speed reasons.
;------------------------------------------------------------------------------	

;------------------------------------------------------------------------------	
;Read One Byte
;------------------------------------------------------------------------------	

IDErd8D:				;Read 8 bits from IDE register in [E],
	MOV	A, E			;and return info in [D]
	OUT	IDEportC		;Drive address onto control lines

	ORI	IDErdline		;RD pulse pin (40H)
	OUT	IDEportC		;Assert read pin

	IN	IDEportA
	MOV	D, A			;Return with data in [D]

	MOV	A, E
	OUT	IDEportC		;Deassert RD pin

	XRA	A
	OUT	IDEportC		;Zero all port C lines
	RET

;------------------------------------------------------------------------------	
;Write One Byte
;------------------------------------------------------------------------------	

IDEwr8D:				;Write Data in [D] to IDE register [E]
	MVI	A, WRITEcfg8255		;Set 8255 to write mode
	OUT	IDEportCtrl

	MOV	A, D			;Get data put it in 8255 A port
	OUT	IDEportA

	MOV	A, E			;Select IDE register
	OUT	IDEportC

	ORI	IDEwrline		;Lower WR line
	OUT	IDEportC
	
	MOV	A, E			;Raise WR line
	OUT	IDEportC		;Deassert RD pin

	XRA	A			;Deselect all lines including WR line
	OUT	IDEportC

	MVI	A, READcfg8255		;Config 8255 chip, read mode on return
	OUT	IDEportCtrl
	RET

;------------------------------------------------------------------------------	
;This code is written to reside and run from 0H.  To re-introduce the CPMLDR,
;it must be copied from where it is stored in high memory and relocated to 100H
;in RAM, which overwrites this program.
;------------------------------------------------------------------------------	

CPM$MOVE$CODE
	LXI	H,BUFFER
	LXI	D,100H
	LXI	B,(12*512)
	LDIR
	JMP	100H
CPM$MOVE$CODE$END:

;------------------------------------------------------------------------------	
;
;COMMAND BRANCH TABLE
;
;------------------------------------------------------------------------------	

TBL:	DW  DRIVE$0   ; "A"  Select Drive 0
	DW  DRIVE$1   ; "B"  Select Drive 1
	DW  CPMBOOT   ; "C"  LOAD CPM (if present)
	DW  DISPLAY   ; "D"  Sector contents display:- ON/OFF
	DW  RAMCLEAR  ; "E"  Clear RAM buffer
	DW  FORMAT    ; "F"  Format current disk
	DW  RESTORE   ; "G"  Restore backup
	DW  BACKUP    ; "H"  Backup partition
	DW  NEXT$SECT ; "I"  Next Sector
	DW  PREV$SEC  ; "J"  Previous sector
	DW  ERROR     ; "K"  
	DW  SET$LBA   ; "L"  Set LBA value (Set Track, sector)  
	DW  ERROR     ; "M"  
	DW  POWER$DOWN; "N"  Power down hard disk command
	DW  ERROR     ; "O"  
	DW  ERROR     ; "P"  
	DW  ERROR     ; "Q"  
	DW  READ$SEC  ; "R"  Read sector to data buffer
	DW  SEQ$RD    ; "S"  Sequental sec read and display contents
	DW  ERROR     ; "T"  
	DW  POWER$UP  ; "U"  Power up hard disk command
	DW  N$RD$SEC  ; "V"  Read N sectors
	DW  WRITE$SEC ; "W"  Write data buffer to current sector
	DW  N$WR$SEC  ; "X"  Write N sectors
	DW  COPY$AB   ; "Y"  Copy Drive 0 to Drive 1
	DW  VERIFY$AB ; "Z"  Verify Drive 0:= Drive 1:

;------------------------------------------------------------------------------	
;
;String constants - Messages generated by this program
;
;------------------------------------------------------------------------------

SIGN$ON:	DB	CR,LF,'IDE Disk Drive Utility Program 12/01/2022 (v2.9b)',CR,LF,LF,'$'
SEL0MSG		DB	'Selecting first IDE drive.',CR,LF,'$'
SEL1MSG		DB	'Selecting second IDE drive.',CR,LF,'$'
INITDRIVE	DB	'Initializing drive.  $'
READING$ID	DB	'Reading drive ID.  $'
GETTING$ID	DB	'Getting drive ID...',CR,LF,'$'
DISKSTATUS	DB	'Status is $'
INIT$0$ERROR:	DB	'Initialization of First Drive failed. Aborting Program.',BELL,CR,LF,LF,'$'
INIT$1$ERROR	DB	'Initialization of Second Drive failed. (Possibly not present).',BELL,CR,LF,LF,'$'
ID$ERROR:	DB	'Error obtaining Drive ID.',BELL,CR,LF,'$'
INIT$DR$OK:	DB	'Drive Initialized OK.',CR,LF,LF,'$'
BAD$DRIVE:	DB	CR,LF,'First Drive ID Information appears invalid.',CR,LF
		DB	'Aborting program.',BELL,CR,LF,LF,'$'
DRIVE0$INFO:	DB	'------------ Drive 0 -------------',CR,LF,'$'
DRIVE1$INFO:	DB	'------------ Drive 1 -------------',CR,LF,'$'
msgmdl:		DB	'Model: $'
msgsn:		DB	'S/N:   $'
msgrev:		DB	'Rev:   $'
msgcy:		DB	'Cyl: $'
msghd:		DB	', Hd: $'
msgsc:		DB	', Sec: $'
msgCPMTRK:	DB	'CPM TRK = $'
msgCPMSEC:	DB	' CPM SEC = $'
msgLBA:		DB	'  (LBA = 00$'
MSGBracket	DB	')$'
DRIVE$0$MSG	DB	CR,LF,LF,'  >>> DRIVE #0 <<<$'
DRIVE$1$MSG	DB	CR,LF,LF,'  >>> DRIVE #1 <<<$'
CMD$STRING1: 	DB	'  IDE Board Diagnostic MAIN MENU',CR,LF,LF
		DB	'(L) Set LBA value    (R) Read Sector to Buffer (W) Write Buffer '
		DB	'to Sector',CR,LF
		DB	'(D) Set Display ON   (S) Sequental Sec Read    (F) Format Disk',CR,LF
		DB	'(V) Read N Sectors   (X) Write N Sectors       (H) Backup disk',CR,LF
		DB	'(G) Restore Backup   (I) Next Sector           '
		DB	'(J) Previous Sector',CR,LF
		DB	'(U) Power Up         (N) Power Down            (C) Boot CPM',CR,LF
		DB	'(A) Select Drive 0   (B) Select Drive 1        '
		DB	'(E) Clear Sector Buffer',CR,LF
		DB	'(Y) Copy d0 to d1    (Z) Verify d0 = d1        (ESC) Quit',CR,LF
		DB	LF,'Current settings: $'
CMD$STRING2: 	DB	'      IDE Board Diagnostic MAIN MENU',CR,LF,LF
		DB	'(L) Set LBA value    (R) Read Sector to Buffer (W) Write Buffer '
		DB	'to Sector',CR,LF
		DB	'(D) Set Display OFF  (S) Sequental Sec Read    (F) Format Disk',CR,LF
		DB	'(V) Read N Sectors   (X) Write N Sectors       (H) Backup disk',CR,LF
		DB	'(G) Restore Backup   (I) Next Sector           '
		DB	'(J) Previous Sector',CR,LF
		DB	'(U) Power Up         (N) Power Down            (C) Boot CPM',CR,LF
		DB	'(A) Select Drive 0   (B) Select Drive 1        '
		DB	'(E) Clear Sector Buffer',CR,LF
		DB	'(Y) Copy d0 to d1    (Z) Verify d0 = d1        (ESC) Quit',CR,LF
		DB	LF,'Current settings:- $'
Prompt:		DB	CR,LF,LF,'Please enter command > $'
msgsure:	DB	CR,LF,'Warning: this will change data on the drive, '
		DB	'are you sure? (Y/N)...$'
msgrd:		DB	CR,LF,'Sector Read OK',CR,LF,'$'
msgwr:		DB	CR,LF,'Sector Write OK',CR,LF,'$'
GET$LBA:	DB	'Enter CPM style TRK & SEC values (in hex).',CR,LF,'$'
SEC$RW$ERROR	DB	'Drive Error, Status Register = $'
ERR$REG$DATA	DB	'Drive Error, Error Register = $'
ENTER$SECL	DB	'Starting sector number,(xxH) = $'
ENTER$TRKL	DB	'Track number (LOW byte, xxH) = $'
ENTER$TRKH	DB	'Track number (HIGH byte, xxH) = $'
ENTER$HEAD	DB	'Head number (01-0F) = $'
ENTER$COUNT	DB	'Number of sectors to R/W = $'
DRIVE$BUSY	DB	'Drive Busy (bit 7) stuck high.   Status = $'
DRIVE$NOT$READY	DB	'Drive Ready (bit 6) stuck low.  Status = $'
DRIVE$WR$FAULT	DB	'Drive write fault.    Status = $'
UNKNOWN$ERROR	DB	'Unknown error in status register.   Status = $'
BAD$BLOCK	DB	'Bad Sector ID.    Error Register = $'
UNRECOVER$ERR	DB	'Uncorrectable data error.  Error Register = $'
READ$ID$ERROR	DB	'Error setting up to read Drive ID',CR,LF,'$'
SEC$NOT$FOUND	DB	'Sector not found. Error Register = $'
INVALID$CMD	DB	'Invalid Command. Error Register = $'
TRK0$ERR	DB	'Track Zero not found. Error Register = $'
UNKNOWN$ERROR1	DB	'Unknown Error. Error Register = $'
CONTINUE$MSG	DB	CR,LF,'To Abort enter ESC. Any other key to continue. $'
FORMAT$MSG	DB	'FORMAT DISK. Fill all sectors with E5'
		DB	60H,'s on the CURRENT drive/CF card.$'
ReadN$MSG	DB	CR,LF,'Read multiple sectors from current disk/CF card to RAM buffer.'
		DB	CR,LF,'How many 512 byte sectores (xx HEX):$'
WriteN$MSG	DB	CR,LF,'Write multiple sectors RAM buffer CURRENT disk/CF card.'
		DB	CR,LF,'How many 512 byte sectores (xx HEX):$'
ReadingN$MSG	DB	CR,LF,'Reading Sector at: $'
WritingN$MSG	DB	CR,LF,'Writing Sector at: $'
msgErr		DB	CR,LF,'Sorry, that was not a valid menu option!$'
FormatDone	DB	CR,LF,'Disk Format Complete.',CR,LF,'$'
BackupDone	DB	CR,LF,'Disk partition copy complete.',CR,LF,'$'
CopyMsg		DB	CR,LF,'Copy disk partition to a second area on disk (CF card).'
		DB	CR,LF,'>>> This assumes that tracks greater than MAXTRK '
		DB	'(for CPM, 0FFH) are unused <<<'
		DB	CR,LF,'>>> on this disk. Be sure you have nothing in this '
		DB	'"Backup partition area". <<<'
		DB	CR,LF,BELL,'Warning: This will change data in the partition area, '
		DB	'are you sure? (Y/N)...$ '
AtEnd		DB	CR,LF,'At end of disk partition!',CR,LF,'$'
RBackup$MSG	DB	'Reading track: $'
WBackup$MSG	DB	'H. Writing track: $'
H$Msg		DB	'H$'
RestoreMsg	DB	CR,LF,'Restore disk with data from backup partition on disk (CF card).'
		DB	CR,LF,BELL,'Warning: This will change data on disk, '
		DB	'are you sure? (Y/N)...$ '
RestoreDone	DB	CR,LF,'Restore of disk data from backup partition complete.',CR,LF,'$'
RANGE$MSG	DB	CR,LF,'Sector value out of range.',CR,LF,'$'
CPM$ERROR	DB	CR,LF,'Error reading CPMLDR.',CR,LF,'$'
CPM$ERROR1	DB	CR,LF,'Data error reading CPMLDR. (The first byte loaded was not 31H).',CR,LF,'$'
MOVE$REQUEST	DB	CR,LF,'The CPMLDR image is now at 3000H in RAM. '
		DB	'To boot CPM you will have to'
		DB	CR,LF,'overwrite this program at 100H. Do you wish to do so (Y/N)...$'
SETA$MSG	DB	CR,LF,'Current Drive is now #0 (Yellow LED)$'
SETB$MSG	DB	CR,LF,'Current Drive is now #1 (Green LED)$'
FILL$MSG	DB	CR,LF,'Sector buffer in RAM filled with 0',27H,'s$'		
DiskCopyMsg	DB	CR,LF,'Copy disk partition of Drive 0 to Drive 1 (CF card).'
		DB	CR,LF,BELL,'Warning: This will delete all data on Drive 1, '
		DB	'are you sure? (Y/N)...$ '
CopyDone	DB	CR,LF,'Disk copy of CPM disk 0 to 1 complete.',CR,LF,'$'
CopyTrk$MSG	DB	'Copying track: $'
DiskVerifyMsg	DB	CR,LF,'Verify disk partition Drive 0 = Drive 1 (CF card).$'
VerifyTrk$MSG	DB	'Verifying track: $'
VerifyDone	DB	CR,LF,'Verify CPM disk 0 = 1 complete.',CR,LF,'$'
Verify$ERR	DB	CR,LF,BELL,'Verify error on Track $'
SEC$Msg		DB	'H  Sector $'

;------------------------------------------------------------------------------	
;RAM usage
;------------------------------------------------------------------------------	

RAMAREA		DB	'           RAM STORE AREA -------->'
@DMA		DW	buffer
@DRIVE$SEC	DB	0H
@DRIVE$TRK	DW	0H
@DisplayFlag	DB	0FFH		;Display of sector data initially ON
@SEC		DW	0H
@TRK		DW	0H
@SEC1		DW	0H		;For disk partition copy
@TRK1		DW	0H
@SEC2		DW	0H
@TRK2		DW	0H
@StartLineHex	DW	0H
@StartLineASCII	DW	0H
@BYTE$COUNT	DW	0H
@SECTOR$COUNT	DW	0H
@DELAYStore	DB	0H
@CURRENT$DRIVE	DB	0H

		DB	'          Start of ID buffer-->'
IDbuffer	DS	512
		DB	'<--End of ID buffer            '

		ORG	BUFFER$ORG

BUFFER:		DB	76H
		DB	'<--Start buffer area' 
		DS	476
		DB	'End of buffer-->'

BUFFER2:	DB	'<--Start buffer2 area'
		DS	476
		DB	'End of buffer2-->'

		DS	100H
STACK		DW	0H


;END
toml_12953
Posts: 305
Joined: June 7th, 2013, 12:54 pm
Contact:

Re: IDE interface for Altair 8800c

Post by toml_12953 »

Wayne Parham wrote:That worked. I replaced the DJNZ instruction in MoreRD16 with the following:

DCR B
JNZ MoreRD16

The IDE identification struct defines a larger byte array for the name, so I'll probably increase that. But the function to iterate through the data buffer on the IDE device works. So sector reads should work too.

Next I'll find all other occurrences of DJNZ and change them to this two-instruction method. Probably do a little bit of cleanup too. Then I'll have IDE code that works on the Altair!

Once that's done, I'll be sure to post it here in this thread.
When converting from 8080 to Z-80 or vice-versa, don't forget about the conditional parity operations, if any. Here's an article about converting MITS 12K BASIC to run on the Z-80
https://www.dropbox.com/s/79ipadfgvy7qk ... .pdf?raw=1
TronDD
Posts: 40
Joined: November 20th, 2018, 7:51 pm
Contact:

Re: IDE interface for Altair 8800c

Post by TronDD »

Happy to see someone working with one of these cards. I've had one for over a year and haven't been able to get it working.

I'm trying to get caught up to where you are.
Wayne Parham
Posts: 253
Joined: March 18th, 2022, 3:01 pm
Contact:

Re: IDE interface for Altair 8800c

Post by Wayne Parham »

Your timing is good then, 'cause here's a version of the IDEutil program that works. We're cookin' with gas now.

But just hotdogs. No steaks yet. The program runs and the menu works. You can see the identification string and that will tell you if the IDE card is working or not. That's a good start. But we're not 100% yet.

I'm pretty sure Monahan must've gotten his code working, so the reason it didn't work for us is probably partially due to hardware differences, but I think it might have also been environmental. Not sure yet. I do know that the DJNZ problem was a Z80/8080 thing, but I think Monahan used a library that contained a macro for that instruction which might have translated it to the DCR/JNZ instruction pair.

I also found a problem in the jump vector code used to handle the command menu. What's in his "myIDE" program just doesn't work for me. Not sure what kind of environment and/or hardware differences explain that, but I replaced his code with what's in this version of my "IDEutil" program - which is just a modified version of myIDE - and fixed the jump table that responds to the command menu.

Next I'll test each of the commands and validate their corresponding functions. I think a few of 'em are working but I think probably some of 'em aren't. And lastly, I want to add a menu item that reads the ID information, just like what now happens when the program is initially run. I think it will be more natural to simply initialize the IDE controller, and start the menu if the drive responds. Showing drive info is useful, but I think it would be more attractive to have a menu item for it than to have it automatically displayed at startup.

Once we're satisfied that this utility program works on the Altair, we can make a BIOS that supports the IDE interface. We might actually be able to do that already, since the code that reads the identification string uses the low-level interface code that would be used in the BIOS. So it's possible we already have everything we need to write a BIOS. But I think I'm going to improve IDEutil a little more first. Baby steps.

Code: Select all

;------------------------------------------------------------------------------
; Utility Program for IDE interface board
;	v2.9c	12/04/2022
;------------------------------------------------------------------------------

;------------------------------------------------------------------------------
;Build equates:
;------------------------------------------------------------------------------

FALSE		EQU	0
TRUE		EQU	NOT FALSE

CPM		EQU	TRUE	;TRUE if using CPM, FALSE if loaded directly
DEBUG		EQU	TRUE	;TRUE for error messages
VERBOSE		EQU	FALSE	;TRUE for extended error messages
CPM$TRANSLATE	EQU	TRUE	;Translate Trk, Sec, Head to CPM TRACK# & SEC#

;------------------------------------------------------------------------------
;Drive number equates:
;------------------------------------------------------------------------------

IDE0		EQU	0	;Physical disk 0 drive number assignment
IDE1		EQU	1	;Physical disk 1 drive number assignment

;------------------------------------------------------------------------------
;Console equates:
;------------------------------------------------------------------------------

CONI		EQU	10H	;Console input port
CONO		EQU	11H	;Console output port

;------------------------------------------------------------------------------
;CP/M equates:
;------------------------------------------------------------------------------

RDCON		EQU	1	;For CP/M I/O
WRCON		EQU	2
RESET$DISK	EQU	0DH	;Reset all CPM disks
PRINT		EQU	9
CONST		EQU	11	;Console stat
BDOS		EQU	5

;------------------------------------------------------------------------------
;Display control equates:
;------------------------------------------------------------------------------

SCROLL		EQU	01H	;Set scroll direction UP
LF		EQU	0AH
CR		EQU	0DH
BS		EQU	08H	;Back space (required for sector display)
PERIOD		EQU	2EH
BELL		EQU	07H
SPACE		EQU	20H
QUIT		EQU	11H	;Turns off any screen enhancements
NO$ENHANCEMENT	EQU	17H	;Turns off whatever is on
FAST		EQU	10H	;High speed scrool
TAB		EQU	09H	;Tab across
ESC		EQU	1BH
CLEAR		EQU	1CH	;Clear line (Use 80 spaces if not available)

;------------------------------------------------------------------------------
;IDE Interface equates:
;------------------------------------------------------------------------------
;Ports for 8255 chip. Change these to specify where your 8255 is addressed,
;The first three control which 8255 ports have the control signals,
;upper and lower data bytes.  The last one (IDEportCtrl), is for mode setting
;for the 8255 to configure its actual I/O ports (A,B & C).  
;
;Note most drives these days don't use the old Head,Track, Sector terminology.
;Instead we use "Logical Block Addressing" or LBA. This is what we use below.
;LBA treats the drive as one continous set of sectors, 0,1,2,3,... 3124,...etc.
;However as seen below we need to convert this LBA to heads,tracks and sectors
;to be compatible with CPM & MSDOS.
;
;NOTE: If you have only one drive/CF card, be sure it is in drive #0.
;The IDE hardware gets confused if there is only a drive in slot #1.
;------------------------------------------------------------------------------

IDEportA	EQU	030H	;Lower 8 bits of IDE interface (8255)
IDEportB	EQU	031H	;Upper 8 bits of IDE interface
IDEportC	EQU	032H	;Control lines for IDE interface
IDEportCtrl	EQU	033H	;8255 configuration port
IDEDrive	EQU	034H	;Bit 0 - 0 for drive 0 and 1 for drive 1

READcfg8255	EQU	10010010b ;Set 8255 IDEportC to out, IDEportA/B input
WRITEcfg8255	EQU	10000000b ;Set all three 8255 ports to output mode

;------------------------------------------------------------------------------
;IDE control lines for use with IDEportC.  
;------------------------------------------------------------------------------

IDEa0line	EQU	01H	;direct from 8255 to IDE interface
IDEa1line	EQU	02H	;direct from 8255 to IDE interface
IDEa2line	EQU	04H	;direct from 8255 to IDE interface
IDEcs0line	EQU	08H	;inverter between 8255 and IDE interface
IDEcs1line	EQU	10H	;inverter between 8255 and IDE interface
IDEwrline	EQU	20H	;inverter between 8255 and IDE interface
IDErdline	EQU	40H	;inverter between 8255 and IDE interface
IDErstline	EQU	80H	;inverter between 8255 and IDE interface

;------------------------------------------------------------------------------
;Symbolic constants for the IDE drive registers
;------------------------------------------------------------------------------

REGdata		EQU	IDEcs0line
REGerr		EQU	IDEcs0line + IDEa0line
REGseccnt	EQU	IDEcs0line + IDEa1line
REGsector	EQU	IDEcs0line + IDEa1line + IDEa0line
REGcylinderLSB	EQU	IDEcs0line + IDEa2line
REGcylinderMSB	EQU	IDEcs0line + IDEa2line + IDEa0line
REGshd		EQU	IDEcs0line + IDEa2line + IDEa1line
REGcommand	EQU	IDEcs0line + IDEa2line + IDEa1line + IDEa0line
REGstatus	EQU	IDEcs0line + IDEa2line + IDEa1line + IDEa0line
REGcontrol	EQU	IDEcs1line + IDEa2line + IDEa1line
REGastatus	EQU	IDEcs1line + IDEa2line + IDEa1line + IDEa0line

;------------------------------------------------------------------------------
;IDE Command Constants.  These should never change.
;------------------------------------------------------------------------------

COMMANDrecal	EQU	10H
COMMANDread	EQU	20H
COMMANDwrite	EQU	30H
COMMANDinit	EQU	91H
COMMANDid	EQU	0ECH
COMMANDspindown	EQU	0E0H
COMMANDspinup	EQU	0E1H

;------------------------------------------------------------------------------
;IDE Status Register:
;------------------------------------------------------------------------------

;  bit 7: Busy	1=busy, 0=not busy
;  bit 6: Ready 1=ready for command, 0=not ready yet
;  bit 5: DF	1=fault occurred
;  bit 4: DSC	1=seek complete
;  bit 3: DRQ	1=data request ready, 0=not ready to xfer yet
;  bit 2: CORR	1=correctable error occurred
;  bit 1: IDX	vendor specific
;  bit 0: ERR	1=error occured

;------------------------------------------------------------------------------
;Disk equates:
;------------------------------------------------------------------------------

SEC$SIZE	EQU	512	;Bytes per sector
MAXSEC		EQU	3DH	;Sectors per track
MAXTRK		EQU	0FFH	;CPM3 allows up to 8MG so 0-256 "tracks"
BUFFER$ORG	EQU	3000H	;<----- Will place all sector data here

CPM$BOOT$COUNT	EQU	12	;Allow up to 12 CPM sectors for CPMLDR
CPMLDR$ADDRESS	EQU	BUFFER$ORG

;------------------------------- INITIALIZATION -------------------------------	

	ORG	100H		;<--- For CPM

begin:
	LXI	SP, STACK
	LXI     D, SIGN$ON	;Print welcome message
	CALL	PSTRING
  IF VERBOSE
	LXI	D, SEL0MSG	;Print select drive 0 message
	CALL	PSTRING
  ENDIF
	MVI	A, IDE0
	STA	@CURRENT$DRIVE
	OUT	IDEDrive	;Select first drive
	
	CALL	CLEAR$ID$BUFFER	;Clear ID Buffer
  IF VERBOSE
	LXI	D, INITDRIVE	;Print initialization message
	CALL	PSTRING
  ENDIF
	CALL	IDEinit		;Initialize the board and first drive
	JZ	INIT$OK		;Continue on Zero
	
	LXI	D, INIT$0$ERROR	;Non-zero is error, probably no drive
	CALL	PSTRING
	JMP	ABORT
	
INIT$OK:			;Get drive 0 identification info			
	CALL	driveid
	JZ	INIT$OK1

	LXI	D, ID$ERROR	;End program on error
	CALL	PSTRING
	JMP	ABORT

INIT$OK1:			;Check sector count
	LXI     H, IDbuffer + 12
	MOV	A, M		;(High Byte)
	ORA	A
	JNZ	INIT$OK2
	INX	H
	MOV	A, M		;(Low Byte)
	ORA	A
	JNZ	INIT$OK2	;Looks like we have a valid IDE drive
	
	LXI	D, BAD$DRIVE	;Zero sectors means something's wrong
	CALL	PSTRING
	JMP	ABORT		;No drive #0 so abort

INIT$OK2:			;Print drive 0 info
	LXI	D, DRIVE0$INFO
	CALL	PSTRING
	LXI     D, msgmdl	;Drive name	
	CALL	PSTRING
	LXI     H, IDbuffer + 54
	MVI	B, 20		;Character count in words
	CALL	printSwap	;Print [HL], [B] X 2 characters
	CALL	ZCRLF

	LXI     D, msgsn	;Serial number
	CALL	PSTRING
	LXI     H, IDbuffer + 20
	MVI	B, 10		;Character count in words
	CALL	printText
	CALL	ZCRLF

	LXI     D, msgrev	;Firmware revision string
	CALL	PSTRING
	LXI     H, IDbuffer + 46
	MVI	B, 4		;Character count in words
	CALL	printSwap
	CALL	ZCRLF

	LXI     D, msgcy	;Drive specs (cyl/hd/sect)
	CALL	PSTRING
	LXI     H, IDbuffer + 2
	CALL	printparm
	LXI     D, msghd
	CALL	PSTRING
	LXI     H, IDbuffer + 6
	CALL	printparm
	LXI     D, msgsc
	CALL	PSTRING
	LXI     H, IDbuffer + 12
	CALL	printparm
	CALL	ZCRLF

INIT$OK3:			;Move to second drive
	CALL	ZCRLF
  IF VERBOSE
	LXI	D, SEL1MSG	;Print select drive 1 message
	CALL	PSTRING
  ENDIF
	MVI	A, IDE1		
	STA	@CURRENT$DRIVE
	OUT	IDEDrive	

	CALL	CLEAR$ID$BUFFER	;Clear ID Buffer
   IF VERBOSE
	LXI	D, INITDRIVE	;Print initialization message
	CALL	PSTRING
   ENDIF
	CALL	IDEinit		;Initialize the second drive
	JZ	INIT$OK4

	LXI	D, INIT$1$ERROR	;Non-zero is error, so print warning
	CALL	PSTRING
	JMP	MAINLOOP

INIT$OK4:			;Get drive 1 identification info			
	CALL	driveid
	JZ	INIT$OK5

	LXI	D, ID$ERROR	;On error, display message
	CALL	PSTRING
	JMP	MAINLOOP	;Continue to main menu

INIT$OK5:			;Check sector count
	LXI     H, IDbuffer + 12
	MOV	A, M		;(High Byte)
	ORA	A
	JNZ	INIT$OK6
	INX	H
	MOV	A, M		;(Low Byte)
	ORA	A
	JNZ	INIT$OK6	;Looks like we have a valid IDE drive
BAD$DR1:	
	LXI	D, BAD$DRIVE	;Zero sectors, so display error
	CALL	PSTRING
	JMP	MAINLOOP	;Continue to main menu

INIT$OK6:			;Print drive 1 info
	LXI	D, DRIVE1$INFO
	CALL	PSTRING
	LXI     D, msgmdl	;Drive name	
	CALL	PSTRING
	LXI     H,IDbuffer + 54
	MVI	B, 20		;Character count in words
	CALL	printSwap	;Print [HL], [B] X 2 characters
	CALL	ZCRLF

	LXI     D, msgsn	;Serial number
	CALL	PSTRING
	LXI     H, IDbuffer + 20
	MVI	B, 10		;Character count in words
	CALL	printText
	CALL	ZCRLF

	LXI     D, msgrev	;Firmware revision string
	CALL	PSTRING
	LXI     H, IDbuffer + 46
	MVI	B, 4		;Character count in words
	CALL	printSwap
	CALL	ZCRLF

	LXI     D, msgcy	;Drive specs (cyl/hd/sect)
	CALL	PSTRING
	LXI     H, IDbuffer + 2
	CALL	printparm
	LXI     D, msghd
	CALL	PSTRING
	LXI     H, IDbuffer + 6
	CALL	printparm
	LXI     D, msgsc
	CALL	PSTRING
	LXI     H, IDbuffer + 12
	CALL	printparm
	CALL	ZCRLF

INIT$DONE:			;Cleanup and enter main menu
	CALL	CLEAR$ID$BUFFER
	CALL	IDEinit		;Re-initialize drive 1
	MVI	A, IDE0
	STA	@CURRENT$DRIVE	;Select drive 0
	OUT	IDEDrive
	CALL	IDEinit		;Re-initialize drive 0
	LXI	H, 0
	SHLD	@SEC		;Default to track 0 and sector 0
	SHLD	@TRK
	LXI	H, buffer	;Set DMA address to buffer
	SHLD	@DMA
	JMP	MAINLOOP	;Display Main Menu

;------------------------------------------------------------------------------	

TERMINATE:			;End program from ESC command
	ZCO
ABORT:				;Controlled termination
  IF CPM
	MVI	C, RESET$DISK	;Reset all disks
	JMP	0FF00H		;Reboot	CPM
  ELSE
	JMP	0F800H		;Transfer control to Monitor ROM	
  ENDIF

;-------------------------------- MENU OPTIONS --------------------------------	

MAINLOOP:			;Print main menu
	LDA	@CURRENT$DRIVE
	ORA	A
	JNZ	DRIVE$1$MENU
	LXI	D, DRIVE$0$MSG
	CALL	PSTRING
	JMP	Display0
DRIVE$1$MENU:
	LXI	D, DRIVE$1$MSG
	CALL	PSTRING
Display0:
	LDA	@DisplayFlag	;Sector data display flag on or off
	ORA	A		;NZ = on (Initially 0FFH so display on)
	JNZ     Display1
	LXI     D, CMD$STRING1	;List command options (Turn display option on)
	JP	Display2
Display1:
	LXI     D, CMD$STRING2	;List command options (Turn display option off)
Display2:
	CALL	PSTRING
	
	CALL	wrlba		;Update LBA on drive
	CALL	DISPLAYposition	;Display current track, sector, head
	
	LXI	D, Prompt	;'>'
	CALL	PSTRING

;--------------------------------- USER INPUT ---------------------------------	
	
	CALL	GETCMD		;Character Input
	CPI	ESC
	JZ	TERMINATE	;End program if ESC
	CALL	ZCRLF

	SBI	'A'		;Adjust to make 'A' = 0
	ADD	A

	LXI	H, TBL		;Offset into vector table
	ADD	L
	MOV	L, A
	MOV	A, M
	INX	HL

	MOV	E, M		;Get selected function address
	INX	H
	MOV	D, M
	XCHG

	PCHL			;Jump to command function address
	
;------------------------------ COMMAND FUNCTIONS -----------------------------	

READ$SEC:			;Read Sector @ LBA to the RAM buffer
	LXI	H, buffer	;Point to buffer
	SHLD	@DMA

	CALL	READSECTOR

	JZ	main1b		;Z means the sector read was OK
	CALL	ZCRLF
	JMP	MAINLOOP

main1b:	LXI     D, msgrd	;Sector read OK
	CALL	PSTRING

	LDA	@DisplayFlag	;Do we have display flag on or off
	ORA	A		;NZ = on 
	JZ	MAINLOOP
	LXI	H, buffer	;Point to buffer. Show sector data flag is on
	SHLD	@DMA
	CALL	HEXDUMP		;Show sector data
	JMP	MAINLOOP

WRITE$SEC:			;Write data in RAM buffer to sector @ LBA
	LXI     D, msgsure	;Are you sure?
	CALL	PSTRING
	CALL	ZCI
	CALL	UPPER
	CPI	'Y'
	JNZ	main2c
	CALL	ZCRLF

	LXI	H, buffer	;Point to buffer
	SHLD	@DMA

	CALL	WRITESECTOR

	JZ	main2b		;Z means the sector write was OK
	CALL	ZCRLF
	JMP	MAINLOOP
main2b:	LXI     D, msgwr	;Sector written OK
	CALL	PSTRING
main2c: JMP	MAINLOOP

SET$LBA:			;Set the logical block address
	LXI     D, GET$LBA	
	CALL	PSTRING
	CALL	ghex32lba	;Get CPM style Track & Sector, put in RAM 
	JC	main3b		;Ret C set if abort/error
	CALL	wrlba		;Update LBA on drive
main3b:	CALL	ZCRLF
	JMP	MAINLOOP

NEXT$SECT:
	LDA	@SEC
	INR	A		
	CPI	MAXSEC-1
	JNC	RANGE$ERROR
	STA	@SEC
	CALL	wrlba		;Update LBA on drive
	CALL	ZCRLF
	JMP	MAINLOOP

RANGE$ERROR:
	LXI     D, RANGE$MSG	
	CALL	PSTRING
	JMP	MAINLOOP
	
PREV$SEC:
	LDA	@SEC
	ORA	A
	JZ	RANGE$ERROR
	DCR	A
	STA	@SEC
	CALL	wrlba		;Update LBA on drive
	CALL	ZCRLF
	JMP	MAINLOOP
	
POWER$UP:			;Set the drive to spin up
	CALL	spinup
	JMP	MAINLOOP

POWER$DOWN:			;Set the drive to spin down
	CALL	spindown
	JMP	MAINLOOP

DISPLAY:			;Do we have display flag on or off
	LDA	@DisplayFlag	
	CMA			;flip it
	STA	@DisplayFlag
	JMP	MAINLOOP	;Update display and back to next menu command

SEQ$RD:				;Do sequential reads
	CALL	SequentialReads
	JMP	MAINLOOP

DRIVE$0:
	MVI	A, IDE0		;Select Drive 0:
	STA	@CURRENT$DRIVE
	OUT	IDEDrive
	LXI     D, SETA$MSG	
	CALL	PSTRING
	JMP	MAINLOOP

DRIVE$1:
	MVI	A, IDE1		;Select Drive 1:
	STA	@CURRENT$DRIVE
	OUT	IDEDrive
	LXI     D,SETB$MSG	
	CALL	PSTRING
	JMP	MAINLOOP

RAMCLEAR:			;Fill RAM buffer with 0's
	LXI	H, buffer	;Point to buffer
	LXI	D, 512
	MVI	A, 0		;Fill area with 0's
CLEAR1:	MOV	M, A
	INX	H
	DCX	D
	MOV	A, E
	ANA	D
	JNZ	CLEAR1
	LXI     D, FILL$MSG	
	CALL	PSTRING
	JMP	MAINLOOP

CPMBOOT:			;Boot CPM from IDE system tracks -- if present
	MVI	A, 0		;Load from track 0, sec 1, head 0 (always)
	STA	@SEC		;Remember sectors are numbered +1
	XRA	A
	STA	@TRK+1
	STA	@TRK

	MVI	A, CPM$BOOT$COUNT ;Count of CPMLDR sectors  (12)
	STA	@SECTOR$COUNT
	LXI	H, CPMLDR$ADDRESS ;DMA address where the CPMLDR resides in RAM
	SHLD	@DMA

NextRCPM:
	CALL	wrlba		;Update LBA on drive
	CALL	DISPLAYposition	;Display current track, sector, head
	CALL	ZCRLF
	
	LHLD	@DMA
	CALL	READSECTOR	;Read a sector
	SHLD	@DMA

	LDA	@SECTOR$COUNT
	DCR	A
	STA	@SECTOR$COUNT
	JZ	LOAD$DONE

	LHLD	@SEC
	INX	H
	SHLD	@SEC		;Stay on track 0 in this special case
	JMP	NextRCPM

LOAD$DONE:
	MVI	E, REGstatus	;Check the R/W status when done
	CALL	IDErd8D
	BIT	0, D
	JNZ	CPMLoadErr	;Zero if no errors
	LXI	H, CPMLDR$ADDRESS
	MOV	A, M
	CPI	31H		;EXPECT TO HAVE 31H @80H IE. LD SP,80H
	JNZ	CPMLoadErr1	;Zero if no errors
	
	LXI	D, MOVE$REQUEST	;Ask if we can move data to 100H
	CALL	PSTRING
	CALL	ZCI
	CALL	UPPER
	CPI	'Y'
	JNZ	MAINLOOP
	
	LXI	H, CPM$MOVE$CODE ;Need to move code out of the way.
	LXI	D, 0H
	LXI	B, (CPM$MOVE$CODE$END-CPM$MOVE$CODE)
	LDIR
	JMP	0H		;Now jump here to move the CPMLDR (@3000H) to 100H
	
CPMLoadErr1:
	LXI	D, CPM$ERROR1	;Drive data error
	CALL	PSTRING
	JMP	MAINLOOP
	
CPMLoadErr:
	LXI	D, CPM$ERROR	;Drive Read Error
	CALL	PSTRING
	JMP	MAINLOOP

N$RD$SEC:			;Read N sectors 
	LXI	D, ReadN$MSG	;No check for possible high RAM (CPM) overwrite
	CALL	PSTRING
	CALL	GETHEX
	JC	MAINLOOP	;Abort if ESC (C flag set)
	
	STA	@SECTOR$COUNT	;Store sector count
	LXI	H, buffer	;Point to buffer
	SHLD	@DMA

NextRSec:	
	LXI	D, ReadingN$MSG
	CALL	PSTRING
	CALL	wrlba		;Update LBA on drive
	CALL	DISPLAYposition	;Display current track, sector, head
	
	LHLD	@DMA
	CALL	READSECTOR
	SHLD	@DMA

	LDA	@SECTOR$COUNT
	DCR	A
	STA	@SECTOR$COUNT
	JZ	MAINLOOP
	
	LHLD	@SEC
	INX	H
	SHLD	@SEC	
	MOV	A, L		;0 to 62 CPM Sectors
	CPI	MAXSEC-1
	JNZ	NextRSec

	LXI	H, 0		;Back to CPM sector 0
	SHLD	@SEC
	LHLD	@TRK		;Bump to next track
	INX	H
	SHLD	@TRK
	MOV	A, L		;0-FFH tracks (only)
	JNZ	NextRSec
	
	LXI	D, AtEnd	;Tell us we are at end of disk
	CALL	PSTRING
	JMP	MAINLOOP

N$WR$SEC:			;Write N sectors 
	LXI     D, msgsure	;Are you sure?
	CALL	PSTRING
	CALL	ZCI
	CALL	UPPER
	CPI	'Y'
	JNZ	main2c

	LXI	D, WriteN$MSG
	CALL	PSTRING
	CALL	GETHEX
	JC	MAINLOOP	;Abort if ESC (C flag set)

	STA	@SECTOR$COUNT	;Store sector count
	LXI	H, buffer	;Point to buffer
	SHLD	@DMA

NextWSec:	
	LXI	D, WritingN$MSG
	CALL	PSTRING
	CALL	wrlba		;Update LBA on drive
	CALL	DISPLAYposition	;Display current track, sector, head
	
	LHLD	@DMA		;Actully, Sector/track values are already updated
	CALL	WRITESECTOR	;in wrlba, but WRITESECTOR is used in multiple places.
	SHLD	@DMA		;A repeat does no harm -- speed is not an issue here

	LDA	@SECTOR$COUNT
	DCR	A
	STA	@SECTOR$COUNT
	JZ	MAINLOOP
	
	LHLD	@SEC
	INX	H
	SHLD	@SEC	
	MOV	A, L		;0 to 62 CPM Sectors
	CPI	MAXSEC-1
	JNZ	NextWSec

	LXI	H, 0		;Back to CPM sector 0
	SHLD	@SEC
	LHLD	@TRK		;Bump to next track
	INX	H
	SHLD	@TRK
	MOV	A, L		;0-FFH tracks (only)
	ORA	A
	JNZ	NextWSec
	
	LXI	D, AtEnd	;Tell us we are at end of disk
	CALL	PSTRING
	JMP	MAINLOOP

FORMAT:				;Format (Fill sectors with E5)
	LXI	D, FORMAT$MSG
	CALL	PSTRING
	LXI     D, msgsure	;Are you sure?
	CALL	PSTRING
	CALL	ZCI
	CALL	UPPER
	CPI	'Y'
	JNZ	MAINLOOP
	LXI	H, buffer	;Fill buffer with E5's (512 of them)
	MVI	B, 0
Fill0:	MVI	A, 0E5H		;<-- Sector fill character (E5 for CPM)
	MOV	M, A
	INX	H
	MOV	M, A
	INX	H
	DCR	B
	JNZ	Fill0
	CALL	ZCRLF

NEXT$FORMAT:
	LXI	H, buffer
	SHLD	@DMA
	CALL	WRITESECTOR	;Will return error if there was one
	JZ	main9b		;Z means the sector write was OK
	CALL	ZCRLF
	JMP	MAINLOOP
main9b:	CALL	ZEOL		;Clear line cursor is on
	CALL	DISPLAYposition	;Display actual current track, sector and head
	CALL	ZCSTS		;Any keyboard character will stop display
	CPI	01H		;CPM Says something there
	JNZ	WRNEXTSEC1
	CALL	ZCI		;Flush character
	LXI	D, CONTINUE$MSG
	CALL	PSTRING
	CALL	ZCI
	CPI	ESC
	JZ	MAINLOOP
	CALL	ZCRLF
WRNEXTSEC1:
	LHLD	@SEC
	INX	H
	SHLD	@SEC		;0 to MAXSEC CPM Sectors
	MOV	A, L
	CPI	MAXSEC
	JNZ	NEXT$FORMAT

	LXI	H, 0		;Back to CPM sector 0
	SHLD	@SEC
	LHLD	@TRK		;Bump to next track
	INX	H
	SHLD	@TRK
	MOV	A, L		;0-FFH tracks (only)
	CPI	MAXTRK
	JNZ	NEXT$FORMAT	

	LXI	D, FormatDone	;Tell us we are all done.
	CALL	PSTRING
	JMP	MAINLOOP
				
BACKUP:				;Backup the CPM partition to another area
	LXI	D, CopyMsg
	CALL	PSTRING
	CALL	ZCI
	CALL	UPPER
	CPI	'Y'
	JNZ	MAINLOOP
	
	LXI	H, 0		;Start with CPM sector 0
	SHLD	@SEC
	SHLD	@SEC1
	SHLD	@SEC2		;and on second partition
	SHLD	@TRK		;and track 0
	SHLD	@TRK1
	LXI	H, MAXTRK+0200H+1
	SHLD	@TRK2 

	CALL	ZCRLF
	CALL	ZCRLF
	
NextCopy1:	
	CALL	ZEOL		;Clear line cursor is on
	LXI	D, RBackup$MSG	;for each track update display
	CALL	PSTRING
	LDA	@TRK1+1		;High TRK byte
	CALL	PHEX
	LDA	@TRK1		;Low TRK byte
	CALL	PHEX
	LXI	D, WBackup$MSG
	CALL	PSTRING
	LDA	@TRK2+1		;High TRK byte
	CALL	PHEX
	LDA	@TRK2		;Low TRK byte
	CALL	PHEX
	LXI	D, H$Msg
	CALL	PSTRING

NextCopy:	
	LDA	@SEC1
	STA	@SEC
	LHLD	@TRK1
	SHLD	@TRK
	CALL	wrlba		;Update LBA on "1st" drive

	LXI	H, buffer	;Point to buffer
	SHLD	@DMA
	CALL	READSECTOR	;Get sector data to buffer
	
	LDA	@SEC2
	STA	@SEC
	LHLD	@TRK2
	SHLD	@TRK
	CALL	wrlba		;Update LBA on "2nd" drive
	
	LXI	H,buffer	;Point to buffer
	SHLD	@DMA
	CALL	WRITESECTOR	;Write buffer data to sector
	
	CALL	ZCSTS		;Any keyboard character will stop display
	CPI	01H		;CPM Says something there
	JNZ	BKNEXTSEC1
	CALL	ZCI		;Flush character
	LXI	D, CONTINUE$MSG
	CALL	PSTRING
	CALL	ZCI
	CPI	ESC
	JZ	MAINLOOP

BKNEXTSEC1:
	LHLD	@SEC
	INX	H
	SHLD	@SEC1
	SHLD	@SEC2	
	MOV	A, L		;0 to 62 CPM Sectors
	CPI	MAXSEC-1
	JNZ	NextCopy

	LXI	H, 0		;Back to CPM sector 0
	SHLD	@SEC1
	SHLD	@SEC2
	
	LHLD	@TRK1		;Bump to next track
	INX	H
	SHLD	@TRK1
	
	LHLD	@TRK2		;Bump to next track
	INX	H
	SHLD	@TRK2
	
	LHLD	@TRK1		;Check if we are done
	MOV	A, L		;0-FFH tracks (only)
	CPI	MAXTRK
	JNZ	NextCopy1
	
	LXI	D, BackupDone	;Tell us we are all done.
	CALL	PSTRING
	JMP	MAINLOOP

RESTORE:			;Restore disk from backup partition
	LXI	D, RestoreMsg
	CALL	PSTRING
	CALL	ZCI
	CALL	UPPER
	CPI	'Y'
	JNZ	MAINLOOP
	
	LXI	H, 0		;Start with CPM sector 0
	SHLD	@SEC
	SHLD	@SEC1
	SHLD	@SEC2		;and on second partition
	SHLD	@TRK		;and track 0
	SHLD	@TRK1
	LXI	H, MAXTRK+0200H+1
	SHLD	@TRK2 

	CALL	ZCRLF
	CALL	ZCRLF
	
NextRestore1:	
	CALL	ZEOL		;Clear line cursor is on
	LXI	D, RBackup$MSG	;for each track update display
	CALL	PSTRING
	LDA	@TRK2+1		;High TRK byte
	CALL	PHEX
	LDA	@TRK2		;Low TRK byte
	CALL	PHEX
	LXI	D, WBackup$MSG
	CALL	PSTRING
	LDA	@TRK1+1		;High TRK byte
	CALL	PHEX
	LDA	@TRK1		;Low TRK byte
	CALL	PHEX
	LXI	D,H$Msg
	CALL	PSTRING

NextRestore:	
	LDA	@SEC2		;Point to backup partition
	STA	@SEC
	LHLD	@TRK2
	SHLD	@TRK
	CALL	wrlba		;Update LBA on "1st" drive

	LXI	H, buffer	;Point to buffer
	SHLD	@DMA
	CALL	READSECTOR	;Get sector data to buffer
	
	LDA	@SEC1
	STA	@SEC
	LHLD	@TRK1
	SHLD	@TRK
	CALL	wrlba		;Update LBA on "2nd" drive
	
	LXI	H,buffer	;Point to buffer
	SHLD	@DMA
	CALL	WRITESECTOR	;Write buffer data to sector
	
	CALL	ZCSTS		;Any keyboard character will stop display
	CPI	01H		;CPM Says something there
	JNZ	RESNEXTSEC1
	CALL	ZCI		;Flush character
	LXI	D, CONTINUE$MSG
	CALL	PSTRING
	CALL	ZCI
	CPI	ESC
	JZ	MAINLOOP

RESNEXTSEC1:
	LHLD	@SEC
	INX	H
	SHLD	@SEC1
	SHLD	@SEC2	
	MOV	A, L		;0 to 62 CPM Sectors
	CPI	MAXSEC-1
	JNZ	NextRestore

	LXI	H, 0		;Back to CPM sector 0
	SHLD	@SEC1
	SHLD	@SEC2
	
	LHLD	@TRK1		;Bump to next track
	INX	H
	SHLD	@TRK1
	
	LHLD	@TRK2		;Bump to next track
	INX	H
	SHLD	@TRK2
	
	LHLD	@TRK2		;Check if we are done
	MOV	A, L		;0-FFH tracks (only)
	CPI	MAXTRK
	JNZ	NextRestore1
	
	LXI	D, RestoreDone	;Inform user restore complete
	CALL	PSTRING
	JMP	MAINLOOP

ERROR:	LXI     D, msgErr	;CMD error msg
	CALL	PSTRING
	JMP	MAINLOOP

COPY$AB:			;Copy drive 0 to drive 1
	LXI	D, DiskCopyMsg
	CALL	PSTRING
	CALL	ZCI
	CALL	UPPER
	CPI	'Y'
	JNZ	MAINLOOP
	
	LXI	H, 0		;Start with CPM sector 0
	SHLD	@SEC
	SHLD	@TRK		;and track 0
	
	CALL	ZCRLF
	CALL	ZCRLF
	
NextDCopy1:	
	CALL	ZEOL		;Clear line cursor is on
	LXI	D, CopyTrk$MSG	;for each track update display
	CALL	PSTRING
	LDA	@TRK+1		;High TRK byte
	CALL	PHEX
	LDA	@TRK		;Low TRK byte
	CALL	PHEX
	LXI	D, H$Msg
	CALL	PSTRING

NextDCopy:	
	MVI	A, IDE0		;Login drive 0
	STA	@CURRENT$DRIVE
	OUT	IDEDrive

	CALL	wrlba		;Update LBA on drive 0

	LXI	H, buffer	;Point to buffer
	SHLD	@DMA
	CALL	READSECTOR	;Get sector data from drive 0 to buffer
	
	MVI	A, IDE1		;Login drive 1
	STA	@CURRENT$DRIVE
	OUT	IDEDrive

	CALL	wrlba		;Update LBA on drive 1
	
	LXI	H, buffer	;Point to buffer
	SHLD	@DMA
	CALL	WRITESECTOR	;Write buffer data to sector on drive 1
	
	CALL	ZCSTS		;Any keyboard character will stop display
	CPI	01H		;CPM says something is there
	JNZ	BK$D$NEXTSEC1
	CALL	ZCI		;Flush character
	LXI	D, CONTINUE$MSG
	CALL	PSTRING
	CALL	ZCI
	CPI	ESC
	JNZ	BK$D$NEXTSEC1
	MVI	A,IDE0		;Login drive 1
	STA	@CURRENT$DRIVE
	OUT	IDEDrive
	JMP	MAINLOOP

BK$D$NEXTSEC1:
	LHLD	@SEC
	INX	H
	SHLD	@SEC
	MOV	A, L		;0 to 62 CPM Sectors
	CPI	MAXSEC-1
	JNZ	NextDCopy

	LXI	H, 0		;Back to CPM sector 0
	SHLD	@SEC
	
	LHLD	@TRK		;Bump to next track
	INX	H
	SHLD	@TRK
				;Check if we are done
	MOV	A, L		;0-FFH tracks (only)
	CPI	MAXTRK
	JNZ	NextDCopy1
	
	LXI	D, CopyDone	;Inform user copy complete
	CALL	PSTRING
	MVI	A, IDE0		;Login drive 0
	STA	@CURRENT$DRIVE
	OUT	IDEDrive
	JMP	MAINLOOP

VERIFY$AB:			;Verify Drive 0 = 1
	LXI	D, DiskVerifyMsg
	CALL	PSTRING
	
	LXI	H, 0		;Start with CPM sector 0
	SHLD	@SEC
	SHLD	@TRK		;and track 0
	
	CALL	ZCRLF
	CALL	ZCRLF
	
NextVCopy1:	
	CALL	ZEOL		;Clear line cursor is on
	LXI	D, VerifyTrk$MSG ;for each track update display
	CALL	PSTRING
	LDA	@TRK+1		;High TRK byte
	CALL	PHEX
	LDA	@TRK		;Low TRK byte
	CALL	PHEX
	LXI	D, H$Msg
	CALL	PSTRING

NextVCopy:	
	MVI	A, IDE0		;Login drive 0
	STA	@CURRENT$DRIVE
	OUT	IDEDrive

	CALL	wrlba		;Update LBA on 0 drive

	LXI	H, buffer	;Point to buffer
	SHLD	@DMA
	CALL	READSECTOR	;Get sector data from buffer 0 drive
	
	MVI	A, IDE1		;Login drive 1
	STA	@CURRENT$DRIVE
	OUT	IDEDrive

	CALL	wrlba		;Update LBA on 1 drive
	
	LXI	H, buffer2	;Point to buffer2
	SHLD	@DMA
	CALL	READSECTOR	;Read buffer data from sector of drive 1
	
	LXI	BC, 512		;Now check both buffers are identical
	LXI	H, buffer
	LXI	D, buffer2
NEXTV:	LDAX	D
	CMP	M		;Is [DE] = [HL]?
	JNZ	COMPARE$ERROR
	INX	H
	INX	D
	DCX	B
	MOV	A,C
	ANA	B
	JZ	VERIFY$OK
	JMP	NEXTV

COMPARE$ERROR:
	LXI	D, VERIFY$ERR	;Indicate an error
	CALL	PSTRING
	LDA	@TRK+1		;High TRK byte
	CALL	PHEX
	LDA	@TRK		;Low TRK byte
	CALL	PHEX
	LXI	D, SEC$Msg
	CALL	PSTRING
	LDA	@SEC		;Sector byte
	CALL	PHEX
	LXI	D, H$Msg
	CALL	PSTRING
	JMP	VER$OK1
	
VERIFY$OK:
	CALL	ZCSTS		;Any keyboard character will stop display
	CPI	01H		;CPM says something is there
	JNZ	BK$V$NEXTSEC1
	CALL	ZCI		;Flush character
VER$OK1:
	LXI	D, CONTINUE$MSG
	CALL	PSTRING
	CALL	ZCI
	CPI	ESC
	JNZ	BK$V$NEXTSEC1
	MVI	A,IDE0		;Login drive 0
	STA	@CURRENT$DRIVE
	OUT	IDEDrive
	JMP	MAINLOOP

BK$V$NEXTSEC1:
	LHLD	@SEC
	INX	H
	SHLD	@SEC
	MOV	A, L		;0 to 62 CPM Sectors
	CPI	MAXSEC-1
	JNZ	NextVCopy

	LXI	H, 0		;Back to CPM sector 0
	SHLD	@SEC
	
	LHLD	@TRK		;Bump to next track
	INX	H
	SHLD	@TRK
				;Check if we are done
	MOV	A, L		;0-FFH tracks (only)
	CPI	MAXTRK
	JNZ	NextVCopy1
	
	LXI	D, VerifyDone	;Tell us we are all done.
	CALL	PSTRING
	MVI	A, IDE0		;Login drive 0
	STA	@CURRENT$DRIVE
	OUT	IDEDrive
	JMP	MAINLOOP

;----------------------------- SUPPORT FUNCTIONS ------------------------------	
				
driveid:CALL	IDEwaitnotbusy	;Retrieve drive info
	RC
	MVI	D, COMMANDid
	MVI	E, REGcommand
	CALL	IDEwr8D		;Issue the ID command
  IF VERBOSE
	LXI	D, READING$ID
	CALL	PSTRING
	LXI	D, DISKSTATUS	;Print status message
	CALL	PSTRING
  ENDIF
	MVI	E, REGstatus	;Get status after ID command
	CALL	IDErd8D		;Check Status (info in [D])
  IF VERBOSE
	MOV	A, D
	CALL	PHEX		;Print status
	CALL	ZPERCRLF
  ENDIF
	CALL	IDEwaitdrq	;Wait for Busy=0, DRQ=1
	JC	SHOWerrors
  IF VERBOSE
	LXI	D, GETTING$ID
	CALL	PSTRING
  ENDIF
	MVI	B, 0		;256 words
	LXI     H, IDbuffer	;Store data here
	CALL	MoreRD16	;Get 256 words of data from REGdata port to [HL]
	RET

spinup:				;Start the drive
	MVI	D, COMMANDspinup
spup2:	MVI	E, REGcommand
	CALL	IDEwr8D
	CALL	IDEwaitnotbusy
	JC	SHOWerrors
	ORA	A		;Clear carry
	RET
				
spindown:			;Tell the drive to spin down
	CALL	IDEwaitnotbusy
	JC	SHOWerrors
	MVI	D,COMMANDspindown
	JMP	spup2

SequentialReads:		;Sequentially read sectors from current position
	CALL	IDEwaitnotbusy
	JC	SHOWerrors
	CALL	ZCRLF
NEXTSEC:
	LXI	H, buffer	;Point to buffer
	SHLD	@DMA

	CALL	READSECTOR	;Errors will show in READSECTOR
	JZ	SEQOK
	LXI	D, CONTINUE$MSG
	CALL	PSTRING
	CALL	ZCI
	CPI	ESC		;Abort if ESC
	RZ
	
SEQOK:	CALL	ZEOL		;Clear line cursor is on
	CALL	DISPLAYposition	;Display current track, sector and head

	LXI	H, buffer	;Point to buffer
	SHLD	@DMA

	LDA	@DisplayFlag	;Do we have display flag on or off
	ORA	A		;NZ = on 
	CNZ	HEXDUMP
	CALL	ZCRLF
	CALL	ZCRLF
	CALL	ZCRLF

	CALL	ZCSTS		;Any keyboard character will stop display
	CPI	01H		;CPM Says something there
	JNZ	NEXTSEC1
	CALL	ZCI		;Flush character
	LXI	D, CONTINUE$MSG
	CALL	PSTRING
	CALL	ZCI
	CPI	ESC
	RZ
	CALL	ZCRLF
NEXTSEC1:
	LHLD	@SEC
	INX	H
	SHLD	@SEC	
	MOV	A, L		;0 to 62 CPM Sectors
	CPI	MAXSEC-1
	JNZ	NEXTSEC

	LXI	H, 0		;Back to CPM sector 0
	SHLD	@SEC
	LHLD	@TRK		;Bump to next track
	INX	H
	SHLD	@TRK
	JMP	NEXTSEC		;Note will go to last sec on disk unless stopped

DISPLAYposition:		;Display current track, sector & head position
	LXI     D, msgCPMTRK	;Display in LBA format
	CALL	PSTRING		;---- CPM FORMAT ----
	LDA	@TRK+1		;High TRK byte
	CALL	PHEX
	LDA	@TRK		;Low TRK byte
	CALL	PHEX
		
	LXI     D, msgCPMSEC
	CALL	PSTRING		;SEC = (16 bits)
	LDA	@SEC+1		;High Sec
	CALL	PHEX
	LDA	@SEC		;Low sec
	CALL	PHEX
				;---- LBA FORMAT ----
	LXI     D, msgLBA
	CALL	PSTRING		;LBA = 00 ("Heads" = 0 for these drives)
	LDA	@DRIVE$TRK+1	;High "cylinder" byte
	CALL	PHEX
	LDA	@DRIVE$TRK	;Low "cylinder" byte
	CALL	PHEX	
	LDA	@DRIVE$SEC
	CALL	PHEX
	LXI     D, MSGBracket
	CALL	PSTRING		
	RET

printText:			;Print text up to [B] (16-bit word) byte-pairs
	MOV	C, M		;Text is contiguous byte array
	CALL	ZCO	
	INX	H
	MOV	C, M
	CALL	ZCO	
	INX	H
	DCR	B
	JNZ	printText
	RET

printSwap:			;Print text up to [B] (16-bit word) byte-pairs	
	INX	H		;Swap byte pairs - low byte, high byte
	MOV	C, M
	CALL	ZCO	
	DCX	H
	MOV	C, M
	CALL	ZCO
	INX	H
	INX	H
	DCR	B
	JNZ	printSwap
	RET

ZCRLF:
	PUSH	PSW
	MVI	C, CR
	CALL	ZCO
	MVI	C, LF
	CALL	ZCO
	POP	PSW
	RET

ZPERCRLF:
	PUSH	PSW
	MVI	C, PERIOD
	CALL	ZCO
	MVI	C, CR
	CALL	ZCO
	MVI	C, LF
	CALL	ZCO
	POP	PSW
	RET

ZEOL:				;CR and clear current line
	MVI	C, CR
	CALL	ZCO
	MVI	C, CLEAR	;Use 80 spaces if necessary
	CALL	ZCO
	RET

ZCSTS:
  IF CPM
	PUSH	B
	PUSH	D
	PUSH	H
	MVI	C, CONST
	CALL	BDOS		;Returns with 1 in [A] if character at keyboard
	POP	H
	POP	D
	POP	B
	CPI	1
	RET
  ELSE	
	IN	COMI		;Get Character in [A]
	ANI	02H
	RZ
	MVI	A, 01H
	ORA	A
	RET
  ENDIF

ZCO:				;Write character that is in [C]
  IF CPM
	PUSH	PSW
	PUSH	B
	PUSH	D
	PUSH	H
	MOV	E, C
	MVI	C, WRCON
	CALL	BDOS
	POP	H
	POP	D
	POP	B
	POP	PSW
	RET
  ELSE	
	PUSH	PSW	
ZCO1:	IN   	CONI		;Show Character
	ANI	04H
	JZ	ZCO1
	MOV	A, C
	OUT	CONO
	POP	PSW
	RET
  ENDIF

ZCI:				;Return keyboard character in [A]
  IF CPM
	PUSH	B
	PUSH	D
	PUSH	H
	MVI	C, RDCON
	CALL	BDOS
	POP	H
	POP	D
	POP	B
	RET
  ELSE
ZCI1:	IN	CONI		;Get Character in [A]
	ANI	02H
	JZ	ZCI1
	IN	CONO
	RET
  ENDIF

;------------------------------------------------------------------------------	
;Print a string in [DE] up to '$'
;------------------------------------------------------------------------------	

PSTRING:
  IF CPM
	MVI	C, PRINT
	JMP	BDOS		;PRINT MESSAGE
  ELSE
	PUSH	B
	PUSH	D
	PUSH	H
	XCHG
PSTRX:	MOV	A, M
	CPI	'$'
	JZ	DONEP
	MOV	C, A
	CALL	ZCO
	INX	H
	JMP	PSTRX
DONEP:	POP	H
	POP	D
	POP	B
	RET
  ENDIF

SHOWerrors:
  IF NOT DEBUG
	ORA	A		;Set NZ flag
	STC			;Set Carry Flag
	RET
  ELSE
	CALL	ZCRLF
	MVI	E, REGstatus	;Get status in status register
	CALL	IDErd8D
	MOV	A, D
	ANI	1H
	JNZ	MoreError	;Go to  REGerr register for more info
				;All OK if 01000000
	PUSH	PSW		;Save for return below
	ANI	80H
	JZ	NOT7
	LXI	D, DRIVE$BUSY	;Drive Busy (bit 7) stuck high 
	CALL	PSTRING
	JMP	DONEERR
NOT7:	ANI	40H
	JNZ	NOT6
	LXI	D, DRIVE$NOT$READY ;Drive Not Ready (bit 6) stuck low 
	CALL	PSTRING
	JMP	DONEERR
NOT6:	ANI	20H
	JNZ	NOT5
	LXI	D, DRIVE$WR$FAULT ;Drive write fault
	CALL	PSTRING
	JMP	DONEERR
NOT5	LXI	D, UNKNOWN$ERROR
	CALL	PSTRING
	JMP	DONEERR

MoreError:			;Bit 0 of the status register indicates problem
	MVI	E, REGerr	;Get error code in REGerr
	CALL	IDErd8D
	MOV	A, D
	PUSH	PSW

	ANI	10H
	JZ	NOTE4
	LXI	D, SEC$NOT$FOUND
	CALL	PSTRING
	JMP	DONEERR

NOTE4:	ANI	80H
	JZ	NOTE7
	LXI	D, BAD$BLOCK
	CALL	PSTRING
	JMP	DONEERR
NOTE7:	ANI	40H
	JZ	NOTE6
	LXI	D, UNRECOVER$ERR
	CALL	PSTRING
	JMP	DONEERR
NOTE6:	ANI	4H
	JZ	NOTE2
	LXI	D, INVALID$CMD
	CALL	PSTRING
	JMP	DONEERR
NOTE2:	ANI	2H
	JZ	NOTE1
	LXI	D, TRK0$ERR
	CALL	PSTRING
	JMP	DONEERR
NOTE1:	LXI	D, UNKNOWN$ERROR1
	CALL	PSTRING
	JMP	DONEERR

DONEERR:POP	PSW
	PUSH	PSW
	CALL	ZBITS
	CALL	ZCRLF
	POP	PSW
	ORA	A		;Set Z flag
	STC			;Set Carry flag
	RET
   ENDIF

;------------------------------------------------------------------------------	
;Print a 16-bit number in RAM located @ [HL], low-byte first for Drive ID 
;------------------------------------------------------------------------------	

printparm:
	INX	H	;Index to high byte first
	MOV	A, M
	CALL	PHEX
	DCX	H	;Now low byte
	MOV	A, M
	CALL	PHEX
	RET

;------------------------------------------------------------------------------	
;Print an 8 bit number located in [A] 
;------------------------------------------------------------------------------	

PHEX:	PUSH	PSW
	PUSH	B
	PUSH	PSW
	RRC
	RRC
	RRC
	RRC
	CALL	ZCONV
	POP	PSW
	CALL	ZCONV
	POP	B
	POP	PSW
	RET

ZCONV:	ANI	0FH		;HEX to ASCII and print it
	ADI	90H
	DAA
	ACI	40H
	DAA
	MOV	C, A
	CALL	ZCO
	RET

;------------------------------------------------------------------------------	
;Display binary in [A] 
;------------------------------------------------------------------------------	

ZBITS:	PUSH	PSW
	PUSH	B
	PUSH	D
	MOV	E, A		
	MVI	B, 8
BQ2:	DB	0CBH, 23H	
	SLAR	E
	MVI	A, 18H
	ADC	A
	MOV	C, A
	CALL	ZCO
	DCR	B
	JNZ	BQ2
	POP	D
	POP	B
	POP	PSW
	RET

ghex32lba:			;Convert CPM Track & Sector to LBA format
	LXI     D,ENTER$SECL	;Enter sector number
	CALL	PSTRING
	CALL	GETHEX		;Get 2 HEX digits
	RC
	STA	@SEC
	CALL	ZCRLF

	LXI	D, ENTER$TRKH	;Enter high byte track number
	CALL	PSTRING
	CALL	GETHEX		;Get 2 HEX digits
	RC
	STA	@TRK+1
	CALL	ZCRLF

	LXI     D, ENTER$TRKL	;Enter low byte track number
	CALL	PSTRING
	CALL	GETHEX		;Get 2 more HEX digits
	RC
	STA	@TRK
	CALL	ZCRLF
	XRA	A
	ORA	A		;To return NC
	RET

;------------------------------------------------------------------------------	
;Get a HEX character from the keyboard and echo it 
;------------------------------------------------------------------------------	

GETHEX:
	CALL	GETCMD		;Get character
	CPI	ESC
	JZ	HEXABORT
	CPI	'/'		;check 0-9, A-F
	JC	HEXABORT
	CPI	'F'+1
	JNC	HEXABORT
	CALL	ASBIN		;Convert to binary
	RLC			;Shift to high nibble
	RLC
	RLC
	RLC
	MOV	B, A		;Store it
	CALL	GETCMD		;Get 2nd character from keyboard & ECHO
	CPI	ESC
	JZ	HEXABORT
	CPI	'/'		;check 0-9, A-F
	JC	HEXABORT
	CPI	'F'+1
	JNC	HEXABORT
	CALL	ASBIN		;Convert to binary
	ORA	B		;add in the first digit
	ORA	A		;To return NC
	RET
HEXABORT:
	STC			;Set Carry flag 
	RET

;------------------------------------------------------------------------------	
;Get a character from the keyboard, convert to uppercase and echo it 
;------------------------------------------------------------------------------	

GETCMD:	CALL	ZCI		;Get character
	CALL	UPPER
	CPI	ESC
	RZ			;Don't echo an ESC
  IF NOT CPM
	PUSH	PSW		;Save state of registers
	PUSH	B
 	MOV	C, A
	CALL	ZCO		;Echo it
	POP	B
	POP	PSW		;Retrieve original state
  ENDIF
	RET

;------------------------------------------------------------------------------	
;Convert lowercase to uppercase 
;------------------------------------------------------------------------------	

UPPER:	CPI	'a'		;Must be >= lowercase a
	RC			;else return as-is
	CPI	'z'+1		;Must be <= lowercase z
	RNC			;else return as-is
	SUI	'a'-'A'		;Subtract lowercase bias
	RET

ASBIN:	SUI	30H 		;ASCII to binary conversion
	CPI	0AH 
	RM
	SUI	07H 
	RET

;------------------------------------------------------------------------------	
;Print a hexdump of the data in the 512 byte buffer starting at [HL]
;------------------------------------------------------------------------------	

HEXDUMP:
	PUSH	PSW		;Save everything
	PUSH	B
	PUSH	D			 
	PUSH	H
	
	CALL	ZCRLF		;CR/LF first
	MVI	D, 32		;Print 32 lines total
	MVI	B, 16		;16 characters across
	SHLD	@StartLineHex	;Save buffer location for ASCII display below
	LXI	H, 0
	SHLD	@BYTE$COUNT
	
SF172:	CALL	ZCRLF
	LHLD	@BYTE$COUNT
	MOV	A, H
	CALL	PHEX		;Print byte count in sector
	MOV	A, L
	CALL	PHEX		
	PUSH	D
	LXI	D, 16
	DAD	D
	POP	D
	SHLD	@BYTE$COUNT	;Store for next time
	CALL	BLANK
	LHLD	@StartLineHex
	SHLD	@StartLineASCII	;Store for ASCII display below

SF175:	MOV	A, M
	CALL	LBYTE		;Display [A] on CRT/LCD
	INX	H
	DCR	B
	JNZ	SF175
	SHLD	@StartLineHex	;Save for next line later
	CALL	ShowAscii	;Now translate to ASCII and display
	MVI	B, 16		;16 characters across for next line
	DCR	D
	JNZ	SF172		;Have we done all 32 lines

	CALL	ZCRLF
	POP	H		;Get back original registers
	POP	D
	POP	B
	POP	PSW
	RET
	
ShowAscii:			;Show as ASCII info
	LHLD	@StartLineASCII
	MVI	B, 16		;16 ASCII characters across
XF172:	CALL	BLANK		;Send a space character
	CALL	BLANK
XF175:	MOV	A, M
	ANI	7FH
	CPI	' ' 		;Filter out control characters
	JNC	XT33
XT22:	MVI	A, '.'
XT33:	CPI	07CH
	JNC	XT22
	MOV	C, A		;Setup to send
	PUSH	B
	CALL	ZCO
	POP	B
	INX	H		;Next position in buffer
	DCR	B
	JNZ	XF175
	RET

BLANK:	PUSH	B
	PUSH	H
	MVI	C, ' '
	CALL	ZCO
	POP	H
	POP	B
	RET

LBYTE:	PUSH	PSW
	RRC
	RRC
	RRC
	RRC
	CALL	SF598
	POP	PSW

SF598:	CALL	ZCONV
	RET

;------------------------------------------------------------------------------	
;IDE Drive BIOS Routines written in a format that can be used directly with CPM
;------------------------------------------------------------------------------	

IDEinit:			;Initialize the 8255 and drive then do a hard reset
  IF VERBOSE
	LXI	D, INITDRIVE
	CALL	PSTRING
  ENDIF
	MVI	A, READcfg8255	;Config 8255 chip (10010010B)
	OUT	IDEportCtrl	;for READ mode
				
	MVI	A, IDErstline	;Hard reset the disk drive
	OUT	IDEportC	;Some CF cards are sensitive to reset pulse width
	MVI	B, 20H		;Symptom is incorrect data back from a sector read

ResetDelay:
	DCR	B
	JNZ	ResetDelay	;Delay (reset pulse width)

	XRA	A
	OUT	IDEportC	;No control lines asserted (just bit 7 of port C)
	CALL	DELAY$SHORT	;Short Delay

	MVI	D,11100000b	;Data for IDE SDH reg (512byte, LBA, single drive, hd 0)
				;For Trk, Sec, Head (non LBA) use 10100000

	MVI	E,REGshd	;00001110,(0EH) for CS0,A2,A1,  
	CALL	IDEwr8D		;Write byte to select the MASTER device

	MVI	B, 02H		;Delay time for hard disks to get up to speed (2s)

WaitInit:
  IF VERBOSE
	LXI	D, DISKSTATUS	;Print initialization status message
	CALL	PSTRING
  ENDIF
	MVI	E, REGstatus	;Get status after initilization
	CALL	IDErd8D		;Check Status (info in [D])
	MOV	A, D
  IF VERBOSE
	CALL	PHEX		;Print drive initialization status
	CALL	ZPERCRLF
  ENDIF
	ANI	80H
	RZ			;Return. We'll check for errors when we get back
	MVI	A, 2
	CALL	DELAY$LONG	;Long delay, drive has to get up to speed
	DCR	B
	JNZ	WaitInit
	XRA	A
	DCR	A
	RET			;Return NZ. We'll check for errors when we get back
	
DELAY$LONG:			;Long delay (Seconds)
	STA	@DELAYStore
	PUSH	B
	LXI	B, 0FFFFH
DELAY2:	LDA	@DELAYStore
DELAY1:	DCR	A
	JNZ	DELAY1
	DCX	B
	MOV	A, C
	ORA	B
	JNZ	DELAY2
	POP	B
	RET

DELAY$SHORT:			;Short delay (32ms)
	MVI	A, 40
DELAY3:	MVI	B, 0
M0:	DCR	B
	JNZ	M0
	DCR	A
	JNZ     DELAY3 
	RET

;------------------------------------------------------------------------------	
;Sector Read
;------------------------------------------------------------------------------	

READSECTOR:			;Read a sector, specified by the 3 bytes in LBA
				;Z on success, NZ call error routine if problem

	CALL	wrlba		;Tell which sector we want to read from.
				;Translate first in case of an error, otherewise 
				;we will get stuck on bad sector
 
	CALL	IDEwaitnotbusy	;Make sure drive is ready
	JC	SHOWerrors	;Returned with NZ set if error

	MVI	D, COMMANDread
	MVI	E, REGcommand
	CALL	IDEwr8D		;Send sec read command to drive.
	CALL	IDEwaitdrq	;Wait until it's got the data
	JC	SHOWerrors
		
	LHLD  	@DMA		;DMA address
	MVI	B, 0		;Read 512 bytes to [HL]

MoreRD16:
	MVI	A, REGdata	;REG register address
	OUT	IDEportC	

	ORI	IDErdline	;08H+40H, Pulse RD line
	OUT	IDEportC	

	IN	IDEportA	;Read the lower byte first
	MOV	M, A
	INX	H
	IN	IDEportB	;Then read the upper byte
	MOV	M, A
	INX	H
	
	MVI	A, REGdata	;Deassert RD line
	OUT	IDEportC
	DCR	B
	JNZ	MoreRD16

	MVI	E, REGstatus
	CALL	IDErd8D
	MOV	A, D
	ANI	1H
	CNZ	SHOWerrors	;If error display status
	RET

;------------------------------------------------------------------------------	
;Sector Write
;------------------------------------------------------------------------------	

WRITESECTOR:			;Write a sector, specified by the 3 bytes in LBA
				;Z on success, NZ to error routine if problem

	CALL	wrlba		;Tell which sector we want to read from.
				;Translate first in case of an error, otherewise 
				;we will get stuck on bad sector 

	CALL	IDEwaitnotbusy	;Make sure drive is ready
	JC	SHOWerrors

	MVI	D, COMMANDwrite
	MVI	E, REGcommand
	CALL	IDEwr8D		;Tell drive to write a sector
	CALL	IDEwaitdrq	;Wait unit it wants the data
	JC	SHOWerrors

	LHLD    @DMA
	MVI	B, 0

	MVI	A, WRITEcfg8255
	OUT	IDEportCtrl
	
WRSEC1:	MOV	A, M
	INX	H
	OUT	IDEportA	;Write the lower byte first
	MOV	A, M
	INX	H
	OUT	IDEportB	;Then high byte on B

	MVI	A, REGdata
	PUSH	PSW
	OUT	IDEportC	;Send write command
	ORI	IDEwrline	;Send WR pulse
	OUT	IDEportC
	POP	PSW
	OUT	IDEportC
	DCR	B
	JNZ	WRSEC1
	
	MVI	A, READcfg8255	;Set 8255 back to read mode
	OUT	IDEportCtrl	

	MVI	E, REGstatus
	CALL	IDErd8D
	MOV	A, D
	ANI	1H
	CNZ	SHOWerrors	;If error display status
	RET

;------------------------------------------------------------------------------	
;Write Logical Block Address (LBA) mode
;------------------------------------------------------------------------------	

wrlba:
	LDA	@SEC		;LBA mode low sectors go directly 
	INR	A		;Sectors are numbered 1 -- MAXSEC
	STA	@DRIVE$SEC	;For Diagnostic Display Only
	MOV	D, A
	MVI	E, REGsector	;Send info to drive
	CALL	IDEwr8D
				
	LHLD	@TRK		
	MOV	A, L
	STA	@DRIVE$TRK
	MOV	D, L		;Send Low TRK#
	MVI	E, REGcylinderLSB
	CALL	IDEwr8D

	MOV	A, H
	STA	@DRIVE$TRK+1
	MOV	D, H		;Send High TRK#
	MVI	E, REGcylinderMSB
	CALL	IDEwr8D

	MVI	D, 1		;For now, one sector at a time
	MVI	E, REGseccnt
	CALL	IDEwr8D
	RET

;------------------------------------------------------------------------------	
;Wait for drive to come ready
;------------------------------------------------------------------------------	

IDEwaitnotbusy:			;Drive READY if status = 01000000
	MVI	B, 0FFH
	MVI	A, 0FFH		;Delay must be above 80H, longer for slow drives
	STA	@DELAYStore

MoreWait:
	MVI	E, REGstatus	;Wait for RDY bit to be set
	CALL	IDErd8D
	MOV	A, D
	ANI	11000000B
	XRI	01000000B
	JZ	DoneNotbusy
	DCR	B	
	JNZ	MoreWait
	LDA	@DELAYStore	;Check timeout delay
	DCR	A
	STA	@DELAYStore
	JNZ	MoreWait

	STC			;Set carry to indicate an error
	ret
DoneNotBusy:
	ORA	A		;Clear carry it indicate no error
	RET

;------------------------------------------------------------------------------	
;Wait for drive to assert data request (DRQ) line ready
;------------------------------------------------------------------------------	

IDEwaitdrq:
	MVI	B, 0FFH
	MVI	A, 0FFH		;Delay must be above 80H, longer for slow drives
	STA	@DELAYStore

MoreDRQ:
	MVI	E, REGstatus	;Wait for DRQ bit to be set
	CALL	IDErd8D
	MOV	A, D
	ANI	10001000B
	CPI	00001000B
	JZ	DoneDRQ
	DCR	B
	JNZ	MoreDRQ
	LDA	@DELAYStore	;Check timeout delay
	DCR	A
	STA	@DELAYStore
	JNZ	MoreDRQ
	STC			;Set carry to indicate error
	RET
DoneDRQ:
	ORA	A		;Clear carry
	RET			;Return drive status in A

;------------------------------------------------------------------------------	
;Clear the ID buffer
;------------------------------------------------------------------------------	

CLEAR$ID$BUFFER:
	LXI	H, IDBuffer
	LXI	B, 512
CLEAR2:	MVI	A, ' '
	MOV	M, A
	INX	H
	DCX	B
	MOV	A, C
	ORA	B
	JNZ	CLEAR2
	
	LXI	H, IDBuffer	;Zero for cylinder, heads, sectors
	LXI	B, 14
CLEAR3:	MVI	A, 0
	MOV	M, A
	INX	H
	DCX	B
	MOV	A, C
	ORA	B
	JNZ	CLEAR3
	RET

;------------------------------------------------------------------------------	
; Low Level 8 bit R/W to the drive controller. These are the routines that talk
; directly to the drive controller registers, via the 8255 chip.  
; Note the 16 bit I/O to the drive (which is only for SEC R/W) is done directly 
; in the routines READSECTOR & WRITESECTOR for speed reasons.
;------------------------------------------------------------------------------	

;------------------------------------------------------------------------------	
;Read One Byte
;------------------------------------------------------------------------------	

IDErd8D:				;Read 8 bits from IDE register in [E],
	MOV	A, E			;and return info in [D]
	OUT	IDEportC		;Drive address onto control lines

	ORI	IDErdline		;RD pulse pin (40H)
	OUT	IDEportC		;Assert read pin

	IN	IDEportA
	MOV	D, A			;Return with data in [D]

	MOV	A, E
	OUT	IDEportC		;Deassert RD pin

	XRA	A
	OUT	IDEportC		;Zero all port C lines
	RET

;------------------------------------------------------------------------------	
;Write One Byte
;------------------------------------------------------------------------------	

IDEwr8D:				;Write Data in [D] to IDE register [E]
	MVI	A, WRITEcfg8255		;Set 8255 to write mode
	OUT	IDEportCtrl

	MOV	A, D			;Get data put it in 8255 A port
	OUT	IDEportA

	MOV	A, E			;Select IDE register
	OUT	IDEportC

	ORI	IDEwrline		;Lower WR line
	OUT	IDEportC
	
	MOV	A, E			;Raise WR line
	OUT	IDEportC		;Deassert RD pin

	XRA	A			;Deselect all lines including WR line
	OUT	IDEportC

	MVI	A, READcfg8255		;Config 8255 chip, read mode on return
	OUT	IDEportCtrl
	RET

;------------------------------------------------------------------------------	
;This code is written to reside and run from 0H.  To re-introduce the CPMLDR,
;it must be copied from where it is stored in high memory and relocated to 100H
;in RAM, which overwrites this program.
;------------------------------------------------------------------------------	

CPM$MOVE$CODE
	LXI	H, BUFFER
	LXI	D, 100H
	LXI	B, (12*512)
	LDIR
	JMP	100H
CPM$MOVE$CODE$END:

;------------------------------------------------------------------------------	
;
;COMMAND BRANCH TABLE
;
;------------------------------------------------------------------------------	

TBL:	DW  DRIVE$0   ; "A"  Select Drive 0
	DW  DRIVE$1   ; "B"  Select Drive 1
	DW  CPMBOOT   ; "C"  LOAD CPM (if present)
	DW  DISPLAY   ; "D"  Sector contents display: ON/OFF
	DW  RAMCLEAR  ; "E"  Clear RAM buffer
	DW  FORMAT    ; "F"  Format current disk
	DW  RESTORE   ; "G"  Restore backup
	DW  BACKUP    ; "H"  Backup partition
	DW  NEXT$SECT ; "I"  Next Sector
	DW  PREV$SEC  ; "J"  Previous sector
	DW  ERROR     ; "K"  
	DW  SET$LBA   ; "L"  Set LBA value (Set Track, sector)  
	DW  ERROR     ; "M"  
	DW  POWER$DOWN; "N"  Power down hard disk command
	DW  ERROR     ; "O"  
	DW  ERROR     ; "P"  
	DW  ERROR     ; "Q"  
	DW  READ$SEC  ; "R"  Read sector to data buffer
	DW  SEQ$RD    ; "S"  Sequental sec read and display contents
	DW  ERROR     ; "T"  
	DW  POWER$UP  ; "U"  Power up hard disk command
	DW  N$RD$SEC  ; "V"  Read N sectors
	DW  WRITE$SEC ; "W"  Write data buffer to current sector
	DW  N$WR$SEC  ; "X"  Write N sectors
	DW  COPY$AB   ; "Y"  Copy Drive 0 to Drive 1
	DW  VERIFY$AB ; "Z"  Verify Drive 0 = Drive 1

;------------------------------------------------------------------------------	
;
;String constants - Messages generated by this program
;
;------------------------------------------------------------------------------

SIGN$ON:	DB	CR,LF,'IDE Disk Drive Utility Program 12/01/2022 (v2.9b)',CR,LF,LF,'$'
SEL0MSG		DB	'Selecting first IDE drive.',CR,LF,'$'
SEL1MSG		DB	'Selecting second IDE drive.',CR,LF,'$'
INITDRIVE	DB	'Initializing drive.  $'
READING$ID	DB	'Reading drive ID.  $'
GETTING$ID	DB	'Getting drive ID...',CR,LF,'$'
DISKSTATUS	DB	'Status is $'
INIT$0$ERROR:	DB	'Initialization of First Drive failed. Aborting Program.',BELL,CR,LF,LF,'$'
INIT$1$ERROR	DB	'Initialization of Second Drive failed. (Possibly not present).',BELL,CR,LF,LF,'$'
ID$ERROR:	DB	'Error obtaining Drive ID.',BELL,CR,LF,'$'
INIT$DR$OK:	DB	'Drive Initialized OK.',CR,LF,LF,'$'
BAD$DRIVE:	DB	CR,LF,'First Drive ID Information appears invalid.',CR,LF
		DB	'Aborting program.',BELL,CR,LF,LF,'$'
DRIVE0$INFO:	DB	'------------ Drive 0 -------------',CR,LF,'$'
DRIVE1$INFO:	DB	'------------ Drive 1 -------------',CR,LF,'$'
msgmdl:		DB	'Model: $'
msgsn:		DB	'S/N:   $'
msgrev:		DB	'Rev:   $'
msgcy:		DB	'Cyl: $'
msghd:		DB	', Hd: $'
msgsc:		DB	', Sec: $'
msgCPMTRK:	DB	'CPM TRK = $'
msgCPMSEC:	DB	' CPM SEC = $'
msgLBA:		DB	'  (LBA = 00$'
MSGBracket	DB	')$'
DRIVE$0$MSG	DB	CR,LF,LF,'  >>> DRIVE #0 <<<$'
DRIVE$1$MSG	DB	CR,LF,LF,'  >>> DRIVE #1 <<<$'
CMD$STRING1: 	DB	'  IDE Board Diagnostic MAIN MENU',CR,LF,LF
		DB	'(L) Set LBA value    (R) Read Sector to Buffer (W) Write Buffer '
		DB	'to Sector',CR,LF
		DB	'(D) Set Display ON   (S) Sequental Sec Read    (F) Format Disk',CR,LF
		DB	'(V) Read N Sectors   (X) Write N Sectors       (H) Backup disk',CR,LF
		DB	'(G) Restore Backup   (I) Next Sector           '
		DB	'(J) Previous Sector',CR,LF
		DB	'(U) Power Up         (N) Power Down            (C) Boot CPM',CR,LF
		DB	'(A) Select Drive 0   (B) Select Drive 1        '
		DB	'(E) Clear Sector Buffer',CR,LF
		DB	'(Y) Copy d0 to d1    (Z) Verify d0 = d1        (ESC) Quit',CR,LF
		DB	LF,'Current settings: $'
CMD$STRING2: 	DB	'      IDE Board Diagnostic MAIN MENU',CR,LF,LF
		DB	'(L) Set LBA value    (R) Read Sector to Buffer (W) Write Buffer '
		DB	'to Sector',CR,LF
		DB	'(D) Set Display OFF  (S) Sequental Sec Read    (F) Format Disk',CR,LF
		DB	'(V) Read N Sectors   (X) Write N Sectors       (H) Backup disk',CR,LF
		DB	'(G) Restore Backup   (I) Next Sector           '
		DB	'(J) Previous Sector',CR,LF
		DB	'(U) Power Up         (N) Power Down            (C) Boot CPM',CR,LF
		DB	'(A) Select Drive 0   (B) Select Drive 1        '
		DB	'(E) Clear Sector Buffer',CR,LF
		DB	'(Y) Copy d0 to d1    (Z) Verify d0 = d1        (ESC) Quit',CR,LF
		DB	LF,'Current settings: $'
Prompt:		DB	CR,LF,LF,'Please enter command > $'
Response:	DB	CR,LF,'Command received:      $'
msgsure:	DB	CR,LF,'Warning: this will change data on the drive, '
		DB	'are you sure? (Y/N)...$'
msgrd:		DB	CR,LF,'Sector Read OK',CR,LF,'$'
msgwr:		DB	CR,LF,'Sector Write OK',CR,LF,'$'
GET$LBA:	DB	'Enter CPM style TRK & SEC values (in hex).',CR,LF,'$'
SEC$RW$ERROR	DB	'Drive Error, Status Register = $'
ERR$REG$DATA	DB	'Drive Error, Error Register = $'
ENTER$SECL	DB	'Starting sector number,(xxH) = $'
ENTER$TRKL	DB	'Track number (LOW byte, xxH) = $'
ENTER$TRKH	DB	'Track number (HIGH byte, xxH) = $'
ENTER$HEAD	DB	'Head number (01-0F) = $'
ENTER$COUNT	DB	'Number of sectors to R/W = $'
DRIVE$BUSY	DB	'Drive Busy (bit 7) stuck high.   Status = $'
DRIVE$NOT$READY	DB	'Drive Ready (bit 6) stuck low.  Status = $'
DRIVE$WR$FAULT	DB	'Drive write fault.    Status = $'
UNKNOWN$ERROR	DB	'Unknown error in status register.   Status = $'
BAD$BLOCK	DB	'Bad Sector ID.    Error Register = $'
UNRECOVER$ERR	DB	'Uncorrectable data error.  Error Register = $'
READ$ID$ERROR	DB	'Error setting up to read Drive ID',CR,LF,'$'
SEC$NOT$FOUND	DB	'Sector not found. Error Register = $'
INVALID$CMD	DB	'Invalid Command. Error Register = $'
TRK0$ERR	DB	'Track Zero not found. Error Register = $'
UNKNOWN$ERROR1	DB	'Unknown Error. Error Register = $'
CONTINUE$MSG	DB	CR,LF,'To Abort enter ESC. Any other key to continue. $'
FORMAT$MSG	DB	'FORMAT DISK. Fill all sectors with E5'
		DB	60H,'s on the CURRENT drive/CF card.$'
ReadN$MSG	DB	CR,LF,'Read multiple sectors from current disk/CF card to RAM buffer.'
		DB	CR,LF,'How many 512 byte sectores (xx HEX):$'
WriteN$MSG	DB	CR,LF,'Write multiple sectors RAM buffer CURRENT disk/CF card.'
		DB	CR,LF,'How many 512 byte sectores (xx HEX):$'
ReadingN$MSG	DB	CR,LF,'Reading Sector at: $'
WritingN$MSG	DB	CR,LF,'Writing Sector at: $'
msgErr		DB	CR,LF,'Sorry, that was not a valid menu option!$'
FormatDone	DB	CR,LF,'Disk Format Complete.',CR,LF,'$'
BackupDone	DB	CR,LF,'Disk partition copy complete.',CR,LF,'$'
CopyMsg		DB	CR,LF,'Copy disk partition to a second area on disk (CF card).'
		DB	CR,LF,'>>> This assumes that tracks greater than MAXTRK '
		DB	'(for CPM, 0FFH) are unused <<<'
		DB	CR,LF,'>>> on this disk. Be sure you have nothing in this '
		DB	'"Backup partition area". <<<'
		DB	CR,LF,BELL,'Warning: This will change data in the partition area, '
		DB	'are you sure? (Y/N)...$ '
AtEnd		DB	CR,LF,'At end of disk partition!',CR,LF,'$'
RBackup$MSG	DB	'Reading track: $'
WBackup$MSG	DB	'H. Writing track: $'
H$Msg		DB	'H$'
RestoreMsg	DB	CR,LF,'Restore disk with data from backup partition on disk (CF card).'
		DB	CR,LF,BELL,'Warning: This will change data on disk, '
		DB	'are you sure? (Y/N)...$ '
RestoreDone	DB	CR,LF,'Restore of disk data from backup partition complete.',CR,LF,'$'
RANGE$MSG	DB	CR,LF,'Sector value out of range.',CR,LF,'$'
CPM$ERROR	DB	CR,LF,'Error reading CPMLDR.',CR,LF,'$'
CPM$ERROR1	DB	CR,LF,'Data error reading CPMLDR. (The first byte loaded was not 31H).',CR,LF,'$'
MOVE$REQUEST	DB	CR,LF,'The CPMLDR image is now at 3000H in RAM. '
		DB	'To boot CPM you will have to'
		DB	CR,LF,'overwrite this program at 100H. Do you wish to do so (Y/N)...$'
SETA$MSG	DB	CR,LF,'Current Drive is now #0 (Yellow LED)$'
SETB$MSG	DB	CR,LF,'Current Drive is now #1 (Green LED)$'
FILL$MSG	DB	CR,LF,'Sector buffer in RAM filled with 0',27H,'s$'		
DiskCopyMsg	DB	CR,LF,'Copy disk partition of Drive 0 to Drive 1 (CF card).'
		DB	CR,LF,BELL,'Warning: This will delete all data on Drive 1, '
		DB	'are you sure? (Y/N)...$ '
CopyDone	DB	CR,LF,'Disk copy of CPM disk 0 to 1 complete.',CR,LF,'$'
CopyTrk$MSG	DB	'Copying track: $'
DiskVerifyMsg	DB	CR,LF,'Verify disk partition Drive 0 = Drive 1 (CF card).$'
VerifyTrk$MSG	DB	'Verifying track: $'
VerifyDone	DB	CR,LF,'Verify CPM disk 0 = 1 complete.',CR,LF,'$'
Verify$ERR	DB	CR,LF,BELL,'Verify error on Track $'
SEC$Msg		DB	'H  Sector $'

;------------------------------------------------------------------------------	
;RAM usage
;------------------------------------------------------------------------------	

RAMAREA		DB	'           RAM STORE AREA -------->'
@DMA		DW	buffer
@DRIVE$SEC	DB	0H
@DRIVE$TRK	DW	0H
@DisplayFlag	DB	0FFH		;Display of sector data initially ON
@SEC		DW	0H
@TRK		DW	0H
@SEC1		DW	0H		;For disk partition copy
@TRK1		DW	0H
@SEC2		DW	0H
@TRK2		DW	0H
@StartLineHex	DW	0H
@StartLineASCII	DW	0H
@BYTE$COUNT	DW	0H
@SECTOR$COUNT	DW	0H
@DELAYStore	DB	0H
@CURRENT$DRIVE	DB	0H

		DB	'          Start of ID buffer-->'
IDbuffer	DS	512
		DB	'<--End of ID buffer            '

		ORG	BUFFER$ORG

BUFFER:		DB	76H
		DB	'<--Start buffer area' 
		DS	476
		DB	'End of buffer-->'

BUFFER2:	DB	'<--Start buffer2 area'
		DS	476
		DB	'End of buffer2-->'

		DS	100H

STACK		DW	0H

;END
TronDD
Posts: 40
Joined: November 20th, 2018, 7:51 pm
Contact:

Re: IDE interface for Altair 8800c

Post by TronDD »

I had converted the myide assembly code to 8080 and, actually, to my own assembler but couldn't get anything out of the hardware.

I compiled your C version and still get no response from the card. So I'm back to hardware debugging.
Wayne Parham
Posts: 253
Joined: March 18th, 2022, 3:01 pm
Contact:

Re: IDE interface for Altair 8800c

Post by Wayne Parham »

The C program "IDEtest.c" should really help you get the hardware running because it's really "chatty" with debugging information. You can easily add more of your own too.

One thing I found as a first-try requirement is that you be able to hit the ports and see the results reflected in the drive-select LEDs.

IDEtest will let you do that: Do an "out 33H 80H" and then an "out 32H 00H" to see the LEDs. Then send 80, 40, 20, 10, 8, 4, 2 and 1 out to port 32H. You can directly control the 8255 this way and expect to see results, either with a scope probing pins on the 8255 or by just watching the LEDs.

This will let you verify board addressing is right before going any further. The switches need to be set to the right address and the jumpers need to be set right too. One jumper controls the signal used for reset and the other jumper controls the how the board interprets the top eight bits of the address bus, so it's important you set that right.

Back to the IDEutil/myIDE code, I'm now looking at the "wrlba" function for a couple of reasons. Firstly, it reports some goofy data, which I think might be caused by a problem similar to the one in the jump table code. Secondly, a handful of people have worked on the codebase used by myIDE - specifically on the wrlba function - to provide alternate formats.

The initial format left "holes" on the disk, which, on one hand, didn't matter all that much because we are only using a tiny sliver of it. But on the other hand, if you removed the flash drive and plugged it into something else, software tools to work with the disk (like image utilities) weren't happy with the holes. They're just weird. So a handful of guys explored that problem and have made improvements in the code.

I started my work with Monahan's v.2.9 myIDE, so what's listed in this thread is a modified version of that. But there are a couple later versions of myIDE - v.3.0 and v.3.1 - which address the "holes" problem and a few others. I'll probably incorporate those changes at some time. The IDEutil program I'm working on will ultimately support contiguous sector addressing.

But even more importantly - certainly more fundamentally - the myIDE program is working as-is for a handful of folks. It is odd to me that it just doesn't work on my Altair. Could be that I need to explore other assemblers and libraries. I mean, I'd prefer to have code that builds with Digital Research's ASM assembler without anything extra. But maybe I'm looking at it the wrong way and should try to explore other build mechanisms.
Wayne Parham
Posts: 253
Joined: March 18th, 2022, 3:01 pm
Contact:

Re: IDE interface for Altair 8800c

Post by Wayne Parham »

Update:

As you might have read, I was growing suspicious that the build problems might be environmental, e.g. different assembly tools. I don't know what Monahan used to assemble his program but I initially assumed he used ASM from Digital Research.

So this evening I took a moment and tried to build with MAC/HEXCOM and RMAC/LINK. I also included the Z80.LIB file contained in the DR archive that has ASM, MAC and RMAC. None of these build environments created an executable that worked fully on my Altairs, but the programs built with MAC and RMAC did show meaningful sector and track values.

That was an excellent clue, and it suggested to me that I should compare the ideutil.prn output file made when building with ASM to the one generated by MAC. What I found was a handful of variables prefixed with the '@' symbol, and ASM generated 0000 for each of those symbols. MAC properly translated those values to the addresses expected, but ASM did not.

What I decided to do was to change each variable that began with an '@' character to one prefixed with 'm' instead. They are all variables that hold memory addresses, and each location is used to store one-byte or two-byte data values. So my 'm' prefix stands for "memory." I considered 'p' for "pointer" too, but that seemed more appropriate for other multi-byte variables, like "buffer." Ultimately, I decided I was giving it more consideration than it was worth, chose the 'm' prefix and called it good.

The resulting code builds with ASM and runs well on my Altair. I haven't tested every function yet, but I have done many of them and they all have worked properly. I can choose any sector on disk, and I can see meaningful data there. I am fairly confident it is accurate data because I see CP/M stuff on one of the disks I had previously placed a CP/M image upon. This version of IDEutil can also format a disk, and read back the E5 bytes in every position. So IDEutil can successfully read, write and format, which confirms all low-level functions are working properly. I'll test other things later, as time permits.

My next step is to update this program with the contiguous sector "no holes" version of wrlba written by David Fry. I'll also examine the other functions for idiosyncrasies and incorporate the improvements from myIDE versions 3.0 and 3.1. Should be able to diff the source code to find where changes were made, and manually bring 'em over if they seem worthwhile. One thing I'm pretty sure I won't incorporate is the option to run "holes" or "no holes" formats. I'll pick one - the contiguous one - and stick with it.

In hindsight, I'm laughing at myself for the huge Rube Goldberg machine I concocted to get where I am now. Started off with myIDE v.2.9. Couldn't get it to work, even after throwing in a few print statements for debugging. Decided to re-write in C. Got it to report its identity string. Went back to the assembly code and found the DJNZ instructions weren't being translated to machine code. Didn't even think to try the macro-assemblers or the libraries, and instead manually changed DJNZ to DCR/JNZ pairs. Found the @XYZZY variables weren't translated to addresses, so replaced 'em all with mXYZZY. In the end, this gave me source code that will build with ASM, but one might observe that it would have been a little bit easier to have started with the right build tools in the first place.

And that might be an attractive solution for others walking this path. You might start out with one of the later versions of myIDE and see if you can get it to run. I still had problems with my MAC and RMAC builds, even when using the Z80.LIB file. But certainly, there must be a solution there. Lots of people are running this IDE board.

As for me, I'm already knee-deep in this thing, and I'm going to finish what I started. I will have an IDEutil program that is based on the Monahan and Fry code, so if anyone makes any updates in the future, I can pretty easily hand-merge them going forward. And I can use the primitive interface functions in this code to create a BIOS that works on the Altair.

Code: Select all

;------------------------------------------------------------------------------
; Utility Program for IDE interface board
;	v2.9d	12/05/2022
;------------------------------------------------------------------------------

;------------------------------------------------------------------------------
;Build equates:
;------------------------------------------------------------------------------

FALSE		EQU	0
TRUE		EQU	NOT FALSE

CPM		EQU	TRUE	;TRUE if using CPM, FALSE if loaded directly
DEBUG		EQU	TRUE	;TRUE for error messages
VERBOSE		EQU	FALSE	;TRUE for extended error messages
CPM$TRANSLATE	EQU	TRUE	;Translate Trk, Sec, Head to CPM TRACK# & SEC#

;------------------------------------------------------------------------------
;Drive number equates:
;------------------------------------------------------------------------------

IDE0		EQU	0	;Physical disk 0 drive number assignment
IDE1		EQU	1	;Physical disk 1 drive number assignment

;------------------------------------------------------------------------------
;Console equates:
;------------------------------------------------------------------------------

CONI		EQU	10H	;Console input port
CONO		EQU	11H	;Console output port

;------------------------------------------------------------------------------
;CP/M equates:
;------------------------------------------------------------------------------

RDCON		EQU	1	;For CP/M I/O
WRCON		EQU	2
RESET$DISK	EQU	0DH	;Reset all CPM disks
PRINT		EQU	9
CONST		EQU	11	;Console stat
BDOS		EQU	5

;------------------------------------------------------------------------------
;Display control equates:
;------------------------------------------------------------------------------

SCROLL		EQU	01H	;Set scroll direction UP
LF		EQU	0AH
CR		EQU	0DH
BS		EQU	08H	;Back space (required for sector display)
PERIOD		EQU	2EH
BELL		EQU	07H
SPACE		EQU	20H
QUIT		EQU	11H	;Turns off any screen enhancements
NO$ENHANCEMENT	EQU	17H	;Turns off whatever is on
FAST		EQU	10H	;High speed scrool
TAB		EQU	09H	;Tab across
ESC		EQU	1BH
CLEAR		EQU	1CH	;Clear line (Use 80 spaces if not available)

;------------------------------------------------------------------------------
;IDE Interface equates:
;------------------------------------------------------------------------------
;Ports for 8255 chip. Change these to specify where your 8255 is addressed,
;The first three control which 8255 ports have the control signals,
;upper and lower data bytes.  The last one (IDEportCtrl), is for mode setting
;for the 8255 to configure its actual I/O ports (A,B & C).  
;
;Note most drives these days don't use the old Head,Track, Sector terminology.
;Instead we use "Logical Block Addressing" or LBA. This is what we use below.
;LBA treats the drive as one continous set of sectors, 0,1,2,3,... 3124,...etc.
;However as seen below we need to convert this LBA to heads,tracks and sectors
;to be compatible with CPM & MSDOS.
;
;NOTE: If you have only one drive/CF card, be sure it is in drive #0.
;The IDE hardware gets confused if there is only a drive in slot #1.
;------------------------------------------------------------------------------

IDEportA	EQU	030H	;Lower 8 bits of IDE interface (8255)
IDEportB	EQU	031H	;Upper 8 bits of IDE interface
IDEportC	EQU	032H	;Control lines for IDE interface
IDEportCtrl	EQU	033H	;8255 configuration port
IDEDrive	EQU	034H	;Bit 0 - 0 for drive 0 and 1 for drive 1

READcfg8255	EQU	10010010b ;Set 8255 IDEportC to out, IDEportA/B input
WRITEcfg8255	EQU	10000000b ;Set all three 8255 ports to output mode

;------------------------------------------------------------------------------
;IDE control lines for use with IDEportC.  
;------------------------------------------------------------------------------

IDEa0line	EQU	01H	;direct from 8255 to IDE interface
IDEa1line	EQU	02H	;direct from 8255 to IDE interface
IDEa2line	EQU	04H	;direct from 8255 to IDE interface
IDEcs0line	EQU	08H	;inverter between 8255 and IDE interface
IDEcs1line	EQU	10H	;inverter between 8255 and IDE interface
IDEwrline	EQU	20H	;inverter between 8255 and IDE interface
IDErdline	EQU	40H	;inverter between 8255 and IDE interface
IDErstline	EQU	80H	;inverter between 8255 and IDE interface

;------------------------------------------------------------------------------
;Symbolic constants for the IDE drive registers
;------------------------------------------------------------------------------

REGdata		EQU	IDEcs0line
REGerr		EQU	IDEcs0line + IDEa0line
REGseccnt	EQU	IDEcs0line + IDEa1line
REGsector	EQU	IDEcs0line + IDEa1line + IDEa0line
REGcylinderLSB	EQU	IDEcs0line + IDEa2line
REGcylinderMSB	EQU	IDEcs0line + IDEa2line + IDEa0line
REGshd		EQU	IDEcs0line + IDEa2line + IDEa1line
REGcommand	EQU	IDEcs0line + IDEa2line + IDEa1line + IDEa0line
REGstatus	EQU	IDEcs0line + IDEa2line + IDEa1line + IDEa0line
REGcontrol	EQU	IDEcs1line + IDEa2line + IDEa1line
REGastatus	EQU	IDEcs1line + IDEa2line + IDEa1line + IDEa0line

;------------------------------------------------------------------------------
;IDE Command Constants.  These should never change.
;------------------------------------------------------------------------------

COMMANDrecal	EQU	10H
COMMANDread	EQU	20H
COMMANDwrite	EQU	30H
COMMANDinit	EQU	91H
COMMANDid	EQU	0ECH
COMMANDspindown	EQU	0E0H
COMMANDspinup	EQU	0E1H

;------------------------------------------------------------------------------
;IDE Status Register:
;------------------------------------------------------------------------------

;  bit 7: Busy	1=busy, 0=not busy
;  bit 6: Ready 1=ready for command, 0=not ready yet
;  bit 5: DF	1=fault occurred
;  bit 4: DSC	1=seek complete
;  bit 3: DRQ	1=data request ready, 0=not ready to xfer yet
;  bit 2: CORR	1=correctable error occurred
;  bit 1: IDX	vendor specific
;  bit 0: ERR	1=error occured

;------------------------------------------------------------------------------
;Disk equates:
;------------------------------------------------------------------------------

SEC$SIZE	EQU	512	;Bytes per sector
MAXSEC		EQU	3DH	;Sectors per track
MAXTRK		EQU	0FFH	;CPM3 allows up to 8MG so 0-256 "tracks"
BUFFER$ORG	EQU	3000H	;<----- Will place all sector data here

CPM$BOOT$COUNT	EQU	12	;Allow up to 12 CPM sectors for CPMLDR
CPMLDR$ADDRESS	EQU	BUFFER$ORG

;------------------------------- INITIALIZATION -------------------------------	

	ORG	100H		;<--- For CPM

begin:
	LXI	SP, STACK
	LXI     D, SIGN$ON	;Print welcome message
	CALL	PSTRING
  IF VERBOSE
	LXI	D, SEL0MSG	;Print select drive 0 message
	CALL	PSTRING
  ENDIF
	MVI	A, IDE0
	STA	mCURRENT$DRIVE
	OUT	IDEDrive	;Select first drive
	
	CALL	CLEAR$ID$BUFFER	;Clear ID Buffer
  IF VERBOSE
	LXI	D, INITDRIVE	;Print initialization message
	CALL	PSTRING
  ENDIF
	CALL	IDEinit		;Initialize the board and first drive
	JZ	INIT$OK		;Continue on Zero
	
	LXI	D, INIT$0$ERROR	;Non-zero is error, probably no drive
	CALL	PSTRING
	JMP	ABORT
	
INIT$OK:			;Get drive 0 identification info			
	CALL	driveid
	JZ	INIT$OK1

	LXI	D, ID$ERROR	;End program on error
	CALL	PSTRING
	JMP	ABORT

INIT$OK1:			;Check sector count
	LXI     H, IDbuffer + 12
	MOV	A, M		;(High Byte)
	ORA	A
	JNZ	INIT$OK2
	INX	H
	MOV	A, M		;(Low Byte)
	ORA	A
	JNZ	INIT$OK2	;Looks like we have a valid IDE drive
	
	LXI	D, BAD$DRIVE	;Zero sectors means something's wrong
	CALL	PSTRING
	JMP	ABORT		;No drive #0 so abort

INIT$OK2:			;Print drive 0 info
	LXI	D, DRIVE0$INFO
	CALL	PSTRING
	LXI     D, msgmdl	;Drive name	
	CALL	PSTRING
	LXI     H, IDbuffer + 54
	MVI	B, 20		;Character count in words
	CALL	printSwap	;Print [HL], [B] X 2 characters
	CALL	ZCRLF

	LXI     D, msgsn	;Serial number
	CALL	PSTRING
	LXI     H, IDbuffer + 20
	MVI	B, 10		;Character count in words
	CALL	printText
	CALL	ZCRLF

	LXI     D, msgrev	;Firmware revision string
	CALL	PSTRING
	LXI     H, IDbuffer + 46
	MVI	B, 4		;Character count in words
	CALL	printSwap
	CALL	ZCRLF

	LXI     D, msgcy	;Drive specs (cyl/hd/sect)
	CALL	PSTRING
	LXI     H, IDbuffer + 2
	CALL	printparm
	LXI     D, msghd
	CALL	PSTRING
	LXI     H, IDbuffer + 6
	CALL	printparm
	LXI     D, msgsc
	CALL	PSTRING
	LXI     H, IDbuffer + 12
	CALL	printparm
	CALL	ZCRLF

INIT$OK3:			;Move to second drive
	CALL	ZCRLF
  IF VERBOSE
	LXI	D, SEL1MSG	;Print select drive 1 message
	CALL	PSTRING
  ENDIF
	MVI	A, IDE1		
	STA	mCURRENT$DRIVE
	OUT	IDEDrive	

	CALL	CLEAR$ID$BUFFER	;Clear ID Buffer
   IF VERBOSE
	LXI	D, INITDRIVE	;Print initialization message
	CALL	PSTRING
   ENDIF
	CALL	IDEinit		;Initialize the second drive
	JZ	INIT$OK4

	LXI	D, INIT$1$ERROR	;Non-zero is error, so print warning
	CALL	PSTRING
	JMP	MAINLOOP

INIT$OK4:			;Get drive 1 identification info			
	CALL	driveid
	JZ	INIT$OK5

	LXI	D, ID$ERROR	;On error, display message
	CALL	PSTRING
	JMP	MAINLOOP	;Continue to main menu

INIT$OK5:			;Check sector count
	LXI     H, IDbuffer + 12
	MOV	A, M		;(High Byte)
	ORA	A
	JNZ	INIT$OK6
	INX	H
	MOV	A, M		;(Low Byte)
	ORA	A
	JNZ	INIT$OK6	;Looks like we have a valid IDE drive
BAD$DR1:	
	LXI	D, BAD$DRIVE	;Zero sectors, so display error
	CALL	PSTRING
	JMP	MAINLOOP	;Continue to main menu

INIT$OK6:			;Print drive 1 info
	LXI	D, DRIVE1$INFO
	CALL	PSTRING
	LXI     D, msgmdl	;Drive name	
	CALL	PSTRING
	LXI     H,IDbuffer + 54
	MVI	B, 20		;Character count in words
	CALL	printSwap	;Print [HL], [B] X 2 characters
	CALL	ZCRLF

	LXI     D, msgsn	;Serial number
	CALL	PSTRING
	LXI     H, IDbuffer + 20
	MVI	B, 10		;Character count in words
	CALL	printText
	CALL	ZCRLF

	LXI     D, msgrev	;Firmware revision string
	CALL	PSTRING
	LXI     H, IDbuffer + 46
	MVI	B, 4		;Character count in words
	CALL	printSwap
	CALL	ZCRLF

	LXI     D, msgcy	;Drive specs (cyl/hd/sect)
	CALL	PSTRING
	LXI     H, IDbuffer + 2
	CALL	printparm
	LXI     D, msghd
	CALL	PSTRING
	LXI     H, IDbuffer + 6
	CALL	printparm
	LXI     D, msgsc
	CALL	PSTRING
	LXI     H, IDbuffer + 12
	CALL	printparm
	CALL	ZCRLF

INIT$DONE:			;Cleanup and enter main menu
	CALL	CLEAR$ID$BUFFER
	CALL	IDEinit		;Re-initialize drive 1
	MVI	A, IDE0
	STA	mCURRENT$DRIVE	;Select drive 0
	OUT	IDEDrive
	CALL	IDEinit		;Re-initialize drive 0
	LXI	H, 0
	SHLD	mSEC		;Default to track 0 and sector 0
	SHLD	mTRK
	LXI	H, buffer	;Set DMA address to buffer
	SHLD	mDMA
	JMP	MAINLOOP	;Display Main Menu

;------------------------------------------------------------------------------	

TERMINATE:			;End program from ESC command
ABORT:				;Controlled termination
  IF CPM
	MVI	C, RESET$DISK	;Reset all disks
	JMP	0FF00H		;Reboot	CPM
  ELSE
	JMP	0F800H		;Transfer control to Monitor ROM	
  ENDIF

;-------------------------------- MENU OPTIONS --------------------------------	

MAINLOOP:			;Print main menu
	LDA	mCURRENT$DRIVE
	ORA	A
	JNZ	DRIVE$1$MENU
	LXI	D, DRIVE$0$MSG
	CALL	PSTRING
	JMP	Display0
DRIVE$1$MENU:
	LXI	D, DRIVE$1$MSG
	CALL	PSTRING
Display0:
	LDA	mDisplayFlag	;Sector data display flag on or off
	ORA	A		;NZ = on (Initially 0FFH so display on)
	JNZ     Display1
	LXI     D, CMD$STRING1	;List command options (Turn display option on)
	JP	Display2
Display1:
	LXI     D, CMD$STRING2	;List command options (Turn display option off)
Display2:
	CALL	PSTRING
	
	CALL	wrlba		;Update LBA on drive
	CALL	DISPLAYposition	;Display current track, sector, head
	
	LXI	D, Prompt	;'>'
	CALL	PSTRING

;--------------------------------- USER INPUT ---------------------------------	
	
	CALL	GETCMD		;Character Input
	CPI	ESC
	JZ	TERMINATE	;End on ESC
	CPI	'A'
	JC	ERROR		;Must be >= 'A'
	CPI	'Z'+1
	JNC	ERROR		;Must be <= 'Z'
	CALL	ZCRLF

	SBI	'A'-1		;Adjust to make 'A' keypress = 0
	ADD	A

	LXI	H, TBL		;Offset into vector table
	ADD	L
	MOV	L, A
	MOV	A, M
	INX	HL

	MOV	E, M		;Get selected function address
	INX	H
	MOV	D, M
	XCHG

	PCHL			;Jump to command function address
	
;------------------------------ COMMAND FUNCTIONS -----------------------------	

READ$SEC:			;Read Sector @ LBA to the RAM buffer
	LXI	H, buffer	;Point to buffer
	SHLD	mDMA

	CALL	READSECTOR

	JZ	main1b		;Z means the sector read was OK
	CALL	ZCRLF
	JMP	MAINLOOP

main1b:	LXI     D, msgrd	;Sector read OK
	CALL	PSTRING

	LDA	mDisplayFlag	;Do we have display flag on or off
	ORA	A		;NZ = on 
	JZ	MAINLOOP
	LXI	H, buffer	;Point to buffer. Show sector data flag is on
	SHLD	mDMA
	CALL	HEXDUMP		;Show sector data
	JMP	MAINLOOP

WRITE$SEC:			;Write data in RAM buffer to sector @ LBA
	LXI     D, msgsure	;Are you sure?
	CALL	PSTRING
	CALL	ZCI
	CALL	UPPER
	CPI	'Y'
	JNZ	main2c
	CALL	ZCRLF

	LXI	H, buffer	;Point to buffer
	SHLD	mDMA

	CALL	WRITESECTOR

	JZ	main2b		;Z means the sector write was OK
	CALL	ZCRLF
	JMP	MAINLOOP
main2b:	LXI     D, msgwr	;Sector written OK
	CALL	PSTRING
main2c: JMP	MAINLOOP

SET$LBA:			;Set the logical block address
	LXI     D, GET$LBA	
	CALL	PSTRING
	CALL	ghex32lba	;Get CPM style Track & Sector, put in RAM 
	JC	main3b		;Ret C set if abort/error
	CALL	wrlba		;Update LBA on drive
main3b:	CALL	ZCRLF
	JMP	MAINLOOP

NEXT$SECT:
	LDA	mSEC
	INR	A		
	CPI	MAXSEC-1
	JNC	RANGE$ERROR
	STA	mSEC
	CALL	wrlba		;Update LBA on drive
	CALL	ZCRLF
	JMP	MAINLOOP

RANGE$ERROR:
	LXI     D, RANGE$MSG	
	CALL	PSTRING
	JMP	MAINLOOP
	
PREV$SEC:
	LDA	mSEC
	ORA	A
	JZ	RANGE$ERROR
	DCR	A
	STA	mSEC
	CALL	wrlba		;Update LBA on drive
	CALL	ZCRLF
	JMP	MAINLOOP
	
POWER$UP:			;Set the drive to spin up
	CALL	spinup
	JMP	MAINLOOP

POWER$DOWN:			;Set the drive to spin down
	CALL	spindown
	JMP	MAINLOOP

DISPLAY:			;Do we have display flag on or off
	LDA	mDisplayFlag	
	CMA			;flip it
	STA	mDisplayFlag
	JMP	MAINLOOP	;Update display and back to next menu command

SEQ$RD:				;Do sequential reads
	CALL	SequentialReads
	JMP	MAINLOOP

DRIVE$0:
	MVI	A, IDE0		;Select Drive 0:
	STA	mCURRENT$DRIVE
	OUT	IDEDrive
	LXI     D, SETA$MSG	
	CALL	PSTRING
	JMP	MAINLOOP

DRIVE$1:
	MVI	A, IDE1		;Select Drive 1:
	STA	mCURRENT$DRIVE
	OUT	IDEDrive
	LXI     D,SETB$MSG	
	CALL	PSTRING
	JMP	MAINLOOP

RAMCLEAR:			;Fill RAM buffer with 0's
	LXI	H, buffer	;Point to buffer
	LXI	D, 512
	MVI	A, 0		;Fill area with 0's
CLEAR1:	MOV	M, A
	INX	H
	DCX	D
	MOV	A, E
	ANA	D
	JNZ	CLEAR1
	LXI     D, FILL$MSG	
	CALL	PSTRING
	JMP	MAINLOOP

CPMBOOT:			;Boot CPM from IDE system tracks -- if present
	MVI	A, 0		;Load from track 0, sec 1, head 0 (always)
	STA	mSEC		;Remember sectors are numbered +1
	XRA	A
	STA	mTRK+1
	STA	mTRK

	MVI	A, CPM$BOOT$COUNT ;Count of CPMLDR sectors  (12)
	STA	mSECTOR$COUNT
	LXI	H, CPMLDR$ADDRESS ;DMA address where the CPMLDR resides in RAM
	SHLD	mDMA

NextRCPM:
	CALL	wrlba		;Update LBA on drive
	CALL	DISPLAYposition	;Display current track, sector, head
	CALL	ZCRLF
	
	LHLD	mDMA
	CALL	READSECTOR	;Read a sector
	SHLD	mDMA

	LDA	mSECTOR$COUNT
	DCR	A
	STA	mSECTOR$COUNT
	JZ	LOAD$DONE

	LHLD	mSEC
	INX	H
	SHLD	mSEC		;Stay on track 0 in this special case
	JMP	NextRCPM

LOAD$DONE:
	MVI	E, REGstatus	;Check the R/W status when done
	CALL	IDErd8D
	BIT	0, D
	JNZ	CPMLoadErr	;Zero if no errors
	LXI	H, CPMLDR$ADDRESS
	MOV	A, M
	CPI	31H		;EXPECT TO HAVE 31H @80H IE. LD SP,80H
	JNZ	CPMLoadErr1	;Zero if no errors
	
	LXI	D, MOVE$REQUEST	;Ask if we can move data to 100H
	CALL	PSTRING
	CALL	ZCI
	CALL	UPPER
	CPI	'Y'
	JNZ	MAINLOOP
	
	LXI	H, CPM$MOVE$CODE ;Need to move code out of the way.
	LXI	D, 0H
	LXI	B, (CPM$MOVE$CODE$END-CPM$MOVE$CODE)
	LDIR
	JMP	0H		;Now jump here to move the CPMLDR (@3000H) to 100H
	
CPMLoadErr1:
	LXI	D, CPM$ERROR1	;Drive data error
	CALL	PSTRING
	JMP	MAINLOOP
	
CPMLoadErr:
	LXI	D, CPM$ERROR	;Drive Read Error
	CALL	PSTRING
	JMP	MAINLOOP

N$RD$SEC:			;Read N sectors 
	LXI	D, ReadN$MSG	;No check for possible high RAM (CPM) overwrite
	CALL	PSTRING
	CALL	GETHEX
	JC	MAINLOOP	;Abort if ESC (C flag set)
	
	STA	mSECTOR$COUNT	;Store sector count
	LXI	H, buffer	;Point to buffer
	SHLD	mDMA

NextRSec:	
	LXI	D, ReadingN$MSG
	CALL	PSTRING
	CALL	wrlba		;Update LBA on drive
	CALL	DISPLAYposition	;Display current track, sector, head
	
	LHLD	mDMA
	CALL	READSECTOR
	SHLD	mDMA

	LDA	mSECTOR$COUNT
	DCR	A
	STA	mSECTOR$COUNT
	JZ	MAINLOOP
	
	LHLD	mSEC
	INX	H
	SHLD	mSEC	
	MOV	A, L		;0 to 62 CPM Sectors
	CPI	MAXSEC-1
	JNZ	NextRSec

	LXI	H, 0		;Back to CPM sector 0
	SHLD	mSEC
	LHLD	mTRK		;Bump to next track
	INX	H
	SHLD	mTRK
	MOV	A, L		;0-FFH tracks (only)
	JNZ	NextRSec
	
	LXI	D, AtEnd	;Tell us we are at end of disk
	CALL	PSTRING
	JMP	MAINLOOP

N$WR$SEC:			;Write N sectors 
	LXI     D, msgsure	;Are you sure?
	CALL	PSTRING
	CALL	ZCI
	CALL	UPPER
	CPI	'Y'
	JNZ	main2c

	LXI	D, WriteN$MSG
	CALL	PSTRING
	CALL	GETHEX
	JC	MAINLOOP	;Abort if ESC (C flag set)

	STA	mSECTOR$COUNT	;Store sector count
	LXI	H, buffer	;Point to buffer
	SHLD	mDMA

NextWSec:	
	LXI	D, WritingN$MSG
	CALL	PSTRING
	CALL	wrlba		;Update LBA on drive
	CALL	DISPLAYposition	;Display current track, sector, head
	
	LHLD	mDMA		;Actully, Sector/track values are already updated
	CALL	WRITESECTOR	;in wrlba, but WRITESECTOR is used in multiple places.
	SHLD	mDMA		;A repeat does no harm -- speed is not an issue here

	LDA	mSECTOR$COUNT
	DCR	A
	STA	mSECTOR$COUNT
	JZ	MAINLOOP
	
	LHLD	mSEC
	INX	H
	SHLD	mSEC	
	MOV	A, L		;0 to 62 CPM Sectors
	CPI	MAXSEC-1
	JNZ	NextWSec

	LXI	H, 0		;Back to CPM sector 0
	SHLD	mSEC
	LHLD	mTRK		;Bump to next track
	INX	H
	SHLD	mTRK
	MOV	A, L		;0-FFH tracks (only)
	ORA	A
	JNZ	NextWSec
	
	LXI	D, AtEnd	;Tell us we are at end of disk
	CALL	PSTRING
	JMP	MAINLOOP

FORMAT:				;Format (Fill sectors with E5)
	LXI	D, FORMAT$MSG
	CALL	PSTRING
	LXI     D, msgsure	;Are you sure?
	CALL	PSTRING
	CALL	ZCI
	CALL	UPPER
	CPI	'Y'
	JNZ	MAINLOOP
	LXI	H, buffer	;Fill buffer with E5's (512 of them)
	MVI	B, 0
Fill0:	MVI	A, 0E5H		;<-- Sector fill character (E5 for CPM)
	MOV	M, A
	INX	H
	MOV	M, A
	INX	H
	DCR	B
	JNZ	Fill0
	CALL	ZCRLF

NEXT$FORMAT:
	LXI	H, buffer
	SHLD	mDMA
	CALL	WRITESECTOR	;Will return error if there was one
	JZ	main9b		;Z means the sector write was OK
	CALL	ZCRLF
	JMP	MAINLOOP
main9b:	CALL	ZEOL		;Clear line cursor is on
	CALL	DISPLAYposition	;Display actual current track, sector and head
	CALL	ZCSTS		;Any keyboard character will stop display
	CPI	01H		;CPM Says something there
	JNZ	WRNEXTSEC1
	CALL	ZCI		;Flush character
	LXI	D, CONTINUE$MSG
	CALL	PSTRING
	CALL	ZCI
	CPI	ESC
	JZ	MAINLOOP
	CALL	ZCRLF
WRNEXTSEC1:
	LHLD	mSEC
	INX	H
	SHLD	mSEC		;0 to MAXSEC CPM Sectors
	MOV	A, L
	CPI	MAXSEC
	JNZ	NEXT$FORMAT

	LXI	H, 0		;Back to CPM sector 0
	SHLD	mSEC
	LHLD	mTRK		;Bump to next track
	INX	H
	SHLD	mTRK
	MOV	A, L		;0-FFH tracks (only)
	CPI	MAXTRK
	JNZ	NEXT$FORMAT	

	LXI	D, FormatDone	;Tell us we are all done.
	CALL	PSTRING
	JMP	MAINLOOP
				
BACKUP:				;Backup the CPM partition to another area
	LXI	D, CopyMsg
	CALL	PSTRING
	CALL	ZCI
	CALL	UPPER
	CPI	'Y'
	JNZ	MAINLOOP
	
	LXI	H, 0		;Start with CPM sector 0
	SHLD	mSEC
	SHLD	mSEC1
	SHLD	mSEC2		;and on second partition
	SHLD	mTRK		;and track 0
	SHLD	mTRK1
	LXI	H, MAXTRK+0200H+1
	SHLD	mTRK2 

	CALL	ZCRLF
	CALL	ZCRLF
	
NextCopy1:	
	CALL	ZEOL		;Clear line cursor is on
	LXI	D, RBackup$MSG	;for each track update display
	CALL	PSTRING
	LDA	mTRK1+1		;High TRK byte
	CALL	PHEX
	LDA	mTRK1		;Low TRK byte
	CALL	PHEX
	LXI	D, WBackup$MSG
	CALL	PSTRING
	LDA	mTRK2+1		;High TRK byte
	CALL	PHEX
	LDA	mTRK2		;Low TRK byte
	CALL	PHEX
	LXI	D, H$Msg
	CALL	PSTRING

NextCopy:	
	LDA	mSEC1
	STA	mSEC
	LHLD	mTRK1
	SHLD	mTRK
	CALL	wrlba		;Update LBA on "1st" drive

	LXI	H, buffer	;Point to buffer
	SHLD	mDMA
	CALL	READSECTOR	;Get sector data to buffer
	
	LDA	mSEC2
	STA	mSEC
	LHLD	mTRK2
	SHLD	mTRK
	CALL	wrlba		;Update LBA on "2nd" drive
	
	LXI	H,buffer	;Point to buffer
	SHLD	mDMA
	CALL	WRITESECTOR	;Write buffer data to sector
	
	CALL	ZCSTS		;Any keyboard character will stop display
	CPI	01H		;CPM Says something there
	JNZ	BKNEXTSEC1
	CALL	ZCI		;Flush character
	LXI	D, CONTINUE$MSG
	CALL	PSTRING
	CALL	ZCI
	CPI	ESC
	JZ	MAINLOOP

BKNEXTSEC1:
	LHLD	mSEC
	INX	H
	SHLD	mSEC1
	SHLD	mSEC2	
	MOV	A, L		;0 to 62 CPM Sectors
	CPI	MAXSEC-1
	JNZ	NextCopy

	LXI	H, 0		;Back to CPM sector 0
	SHLD	mSEC1
	SHLD	mSEC2
	
	LHLD	mTRK1		;Bump to next track
	INX	H
	SHLD	mTRK1
	
	LHLD	mTRK2		;Bump to next track
	INX	H
	SHLD	mTRK2
	
	LHLD	mTRK1		;Check if we are done
	MOV	A, L		;0-FFH tracks (only)
	CPI	MAXTRK
	JNZ	NextCopy1
	
	LXI	D, BackupDone	;Tell us we are all done.
	CALL	PSTRING
	JMP	MAINLOOP

RESTORE:			;Restore disk from backup partition
	LXI	D, RestoreMsg
	CALL	PSTRING
	CALL	ZCI
	CALL	UPPER
	CPI	'Y'
	JNZ	MAINLOOP
	
	LXI	H, 0		;Start with CPM sector 0
	SHLD	mSEC
	SHLD	mSEC1
	SHLD	mSEC2		;and on second partition
	SHLD	mTRK		;and track 0
	SHLD	mTRK1
	LXI	H, MAXTRK+0200H+1
	SHLD	mTRK2 

	CALL	ZCRLF
	CALL	ZCRLF
	
NextRestore1:	
	CALL	ZEOL		;Clear line cursor is on
	LXI	D, RBackup$MSG	;for each track update display
	CALL	PSTRING
	LDA	mTRK2+1		;High TRK byte
	CALL	PHEX
	LDA	mTRK2		;Low TRK byte
	CALL	PHEX
	LXI	D, WBackup$MSG
	CALL	PSTRING
	LDA	mTRK1+1		;High TRK byte
	CALL	PHEX
	LDA	mTRK1		;Low TRK byte
	CALL	PHEX
	LXI	D,H$Msg
	CALL	PSTRING

NextRestore:	
	LDA	mSEC2		;Point to backup partition
	STA	mSEC
	LHLD	mTRK2
	SHLD	mTRK
	CALL	wrlba		;Update LBA on "1st" drive

	LXI	H, buffer	;Point to buffer
	SHLD	mDMA
	CALL	READSECTOR	;Get sector data to buffer
	
	LDA	mSEC1
	STA	mSEC
	LHLD	mTRK1
	SHLD	mTRK
	CALL	wrlba		;Update LBA on "2nd" drive
	
	LXI	H, buffer	;Point to buffer
	SHLD	mDMA
	CALL	WRITESECTOR	;Write buffer data to sector
	
	CALL	ZCSTS		;Any keyboard character will stop display
	CPI	01H		;CPM Says something there
	JNZ	RESNEXTSEC1
	CALL	ZCI		;Flush character
	LXI	D, CONTINUE$MSG
	CALL	PSTRING
	CALL	ZCI
	CPI	ESC
	JZ	MAINLOOP

RESNEXTSEC1:
	LHLD	mSEC
	INX	H
	SHLD	mSEC1
	SHLD	mSEC2	
	MOV	A, L		;0 to 62 CPM Sectors
	CPI	MAXSEC-1
	JNZ	NextRestore

	LXI	H, 0		;Back to CPM sector 0
	SHLD	mSEC1
	SHLD	mSEC2
	
	LHLD	mTRK1		;Bump to next track
	INX	H
	SHLD	mTRK1
	
	LHLD	mTRK2		;Bump to next track
	INX	H
	SHLD	mTRK2
	
	LHLD	mTRK2		;Check if we are done
	MOV	A, L		;0-FFH tracks (only)
	CPI	MAXTRK
	JNZ	NextRestore1
	
	LXI	D, RestoreDone	;Inform user restore complete
	CALL	PSTRING
	JMP	MAINLOOP

ERROR:	LXI     D, msgErr	;CMD error msg
	CALL	PSTRING
	JMP	MAINLOOP

COPY$AB:			;Copy drive 0 to drive 1
	LXI	D, DiskCopyMsg
	CALL	PSTRING
	CALL	ZCI
	CALL	UPPER
	CPI	'Y'
	JNZ	MAINLOOP
	
	LXI	H, 0		;Start with CPM sector 0
	SHLD	mSEC
	SHLD	mTRK		;and track 0
	
	CALL	ZCRLF
	CALL	ZCRLF
	
NextDCopy1:	
	CALL	ZEOL		;Clear line cursor is on
	LXI	D, CopyTrk$MSG	;for each track update display
	CALL	PSTRING
	LDA	mTRK+1		;High TRK byte
	CALL	PHEX
	LDA	mTRK		;Low TRK byte
	CALL	PHEX
	LXI	D, H$Msg
	CALL	PSTRING

NextDCopy:	
	MVI	A, IDE0		;Login drive 0
	STA	mCURRENT$DRIVE
	OUT	IDEDrive

	CALL	wrlba		;Update LBA on drive 0

	LXI	H, buffer	;Point to buffer
	SHLD	mDMA
	CALL	READSECTOR	;Get sector data from drive 0 to buffer
	
	MVI	A, IDE1		;Login drive 1
	STA	mCURRENT$DRIVE
	OUT	IDEDrive

	CALL	wrlba		;Update LBA on drive 1
	
	LXI	H, buffer	;Point to buffer
	SHLD	mDMA
	CALL	WRITESECTOR	;Write buffer data to sector on drive 1
	
	CALL	ZCSTS		;Any keyboard character will stop display
	CPI	01H		;CPM says something is there
	JNZ	BK$D$NEXTSEC1
	CALL	ZCI		;Flush character
	LXI	D, CONTINUE$MSG
	CALL	PSTRING
	CALL	ZCI
	CPI	ESC
	JNZ	BK$D$NEXTSEC1
	MVI	A,IDE0		;Login drive 1
	STA	mCURRENT$DRIVE
	OUT	IDEDrive
	JMP	MAINLOOP

BK$D$NEXTSEC1:
	LHLD	mSEC
	INX	H
	SHLD	mSEC
	MOV	A, L		;0 to 62 CPM Sectors
	CPI	MAXSEC-1
	JNZ	NextDCopy

	LXI	H, 0		;Back to CPM sector 0
	SHLD	mSEC
	
	LHLD	mTRK		;Bump to next track
	INX	H
	SHLD	mTRK
				;Check if we are done
	MOV	A, L		;0-FFH tracks (only)
	CPI	MAXTRK
	JNZ	NextDCopy1
	
	LXI	D, CopyDone	;Inform user copy complete
	CALL	PSTRING
	MVI	A, IDE0		;Login drive 0
	STA	mCURRENT$DRIVE
	OUT	IDEDrive
	JMP	MAINLOOP

VERIFY$AB:			;Verify Drive 0 = 1
	LXI	D, DiskVerifyMsg
	CALL	PSTRING
	
	LXI	H, 0		;Start with CPM sector 0
	SHLD	mSEC
	SHLD	mTRK		;and track 0
	
	CALL	ZCRLF
	CALL	ZCRLF
	
NextVCopy1:	
	CALL	ZEOL		;Clear line cursor is on
	LXI	D, VerifyTrk$MSG ;for each track update display
	CALL	PSTRING
	LDA	mTRK+1		;High TRK byte
	CALL	PHEX
	LDA	mTRK		;Low TRK byte
	CALL	PHEX
	LXI	D, H$Msg
	CALL	PSTRING

NextVCopy:	
	MVI	A, IDE0		;Login drive 0
	STA	mCURRENT$DRIVE
	OUT	IDEDrive

	CALL	wrlba		;Update LBA on 0 drive

	LXI	H, buffer	;Point to buffer
	SHLD	mDMA
	CALL	READSECTOR	;Get sector data from buffer 0 drive
	
	MVI	A, IDE1		;Login drive 1
	STA	mCURRENT$DRIVE
	OUT	IDEDrive

	CALL	wrlba		;Update LBA on 1 drive
	
	LXI	H, buffer2	;Point to buffer2
	SHLD	mDMA
	CALL	READSECTOR	;Read buffer data from sector of drive 1
	
	LXI	BC, 512		;Now check both buffers are identical
	LXI	H, buffer
	LXI	D, buffer2
NEXTV:	LDAX	D
	CMP	M		;Is [DE] = [HL]?
	JNZ	COMPARE$ERROR
	INX	H
	INX	D
	DCX	B
	MOV	A,C
	ANA	B
	JZ	VERIFY$OK
	JMP	NEXTV

COMPARE$ERROR:
	LXI	D, VERIFY$ERR	;Indicate an error
	CALL	PSTRING
	LDA	mTRK+1		;High TRK byte
	CALL	PHEX
	LDA	mTRK		;Low TRK byte
	CALL	PHEX
	LXI	D, SEC$Msg
	CALL	PSTRING
	LDA	mSEC		;Sector byte
	CALL	PHEX
	LXI	D, H$Msg
	CALL	PSTRING
	JMP	VER$OK1
	
VERIFY$OK:
	CALL	ZCSTS		;Any keyboard character will stop display
	CPI	01H		;CPM says something is there
	JNZ	BK$V$NEXTSEC1
	CALL	ZCI		;Flush character
VER$OK1:
	LXI	D, CONTINUE$MSG
	CALL	PSTRING
	CALL	ZCI
	CPI	ESC
	JNZ	BK$V$NEXTSEC1
	MVI	A, IDE0		;Login drive 0
	STA	mCURRENT$DRIVE
	OUT	IDEDrive
	JMP	MAINLOOP

BK$V$NEXTSEC1:
	LHLD	mSEC
	INX	H
	SHLD	mSEC
	MOV	A, L		;0 to 62 CPM Sectors
	CPI	MAXSEC-1
	JNZ	NextVCopy

	LXI	H, 0		;Back to CPM sector 0
	SHLD	mSEC
	
	LHLD	mTRK		;Bump to next track
	INX	H
	SHLD	mTRK
				;Check if we are done
	MOV	A, L		;0-FFH tracks (only)
	CPI	MAXTRK
	JNZ	NextVCopy1
	
	LXI	D, VerifyDone	;Tell us we are all done.
	CALL	PSTRING
	MVI	A, IDE0		;Login drive 0
	STA	mCURRENT$DRIVE
	OUT	IDEDrive
	JMP	MAINLOOP

;----------------------------- SUPPORT FUNCTIONS ------------------------------	
				
driveid:CALL	IDEwaitnotbusy	;Retrieve drive info
	RC
	MVI	D, COMMANDid
	MVI	E, REGcommand
	CALL	IDEwr8D		;Issue the ID command
  IF VERBOSE
	LXI	D, READING$ID
	CALL	PSTRING
	LXI	D, DISKSTATUS	;Print status message
	CALL	PSTRING
  ENDIF
	MVI	E, REGstatus	;Get status after ID command
	CALL	IDErd8D		;Check Status (info in [D])
  IF VERBOSE
	MOV	A, D
	CALL	PHEX		;Print status
	CALL	ZPERCRLF
  ENDIF
	CALL	IDEwaitdrq	;Wait for Busy=0, DRQ=1
	JC	SHOWerrors
  IF VERBOSE
	LXI	D, GETTING$ID
	CALL	PSTRING
  ENDIF
	MVI	B, 0		;256 words
	LXI     H, IDbuffer	;Store data here
	CALL	MoreRD16	;Get 256 words of data from REGdata port to [HL]
	RET

spinup:				;Start the drive
	MVI	D, COMMANDspinup
spup2:	MVI	E, REGcommand
	CALL	IDEwr8D
	CALL	IDEwaitnotbusy
	JC	SHOWerrors
	ORA	A		;Clear carry
	RET
				
spindown:			;Tell the drive to spin down
	CALL	IDEwaitnotbusy
	JC	SHOWerrors
	MVI	D,COMMANDspindown
	JMP	spup2

SequentialReads:		;Sequentially read sectors from current position
	CALL	IDEwaitnotbusy
	JC	SHOWerrors
	CALL	ZCRLF
NEXTSEC:
	LXI	H, buffer	;Point to buffer
	SHLD	mDMA

	CALL	READSECTOR	;Errors will show in READSECTOR
	JZ	SEQOK
	LXI	D, CONTINUE$MSG
	CALL	PSTRING
	CALL	ZCI
	CPI	ESC		;Abort if ESC
	RZ
	
SEQOK:	CALL	ZEOL		;Clear line cursor is on
	CALL	DISPLAYposition	;Display current track, sector and head

	LXI	H, buffer	;Point to buffer
	SHLD	mDMA

	LDA	mDisplayFlag	;Do we have display flag on or off
	ORA	A		;NZ = on 
	CNZ	HEXDUMP
	CALL	ZCRLF
	CALL	ZCRLF
	CALL	ZCRLF

	CALL	ZCSTS		;Any keyboard character will stop display
	CPI	01H		;CPM Says something there
	JNZ	NEXTSEC1
	CALL	ZCI		;Flush character
	LXI	D, CONTINUE$MSG
	CALL	PSTRING
	CALL	ZCI
	CPI	ESC
	RZ
	CALL	ZCRLF
NEXTSEC1:
	LHLD	mSEC
	INX	H
	SHLD	mSEC	
	MOV	A, L		;0 to 62 CPM Sectors
	CPI	MAXSEC-1
	JNZ	NEXTSEC

	LXI	H, 0		;Back to CPM sector 0
	SHLD	mSEC
	LHLD	mTRK		;Bump to next track
	INX	H
	SHLD	mTRK
	JMP	NEXTSEC		;Note will go to last sec on disk unless stopped

DISPLAYposition:		;Display current track, sector & head position
	LXI     D, msgCPMTRK	;Display in LBA format
	CALL	PSTRING		;---- CPM FORMAT ----
	LDA	mTRK+1		;High TRK byte
	CALL	PHEX
	LDA	mTRK		;Low TRK byte
	CALL	PHEX
		
	LXI     D, msgCPMSEC
	CALL	PSTRING		;SEC = (16 bits)
	LDA	mSEC+1		;High Sec
	CALL	PHEX
	LDA	mSEC		;Low sec
	CALL	PHEX
				;---- LBA FORMAT ----
	LXI     D, msgLBA
	CALL	PSTRING		;LBA = 00 ("Heads" = 0 for these drives)
	LDA	mDRIVE$TRK+1	;High "cylinder" byte
	CALL	PHEX
	LDA	mDRIVE$TRK	;Low "cylinder" byte
	CALL	PHEX	
	LDA	mDRIVE$SEC
	CALL	PHEX
	LXI     D, MSGBracket
	CALL	PSTRING		
	RET

printText:			;Print text up to [B] (16-bit word) byte-pairs
	MOV	C, M		;Text is contiguous byte array
	CALL	ZCO	
	INX	H
	MOV	C, M
	CALL	ZCO	
	INX	H
	DCR	B
	JNZ	printText
	RET

printSwap:			;Print text up to [B] (16-bit word) byte-pairs	
	INX	H		;Swap byte pairs - low byte, high byte
	MOV	C, M
	CALL	ZCO	
	DCX	H
	MOV	C, M
	CALL	ZCO
	INX	H
	INX	H
	DCR	B
	JNZ	printSwap
	RET

ZCRLF:
	PUSH	PSW
	MVI	C, CR
	CALL	ZCO
	MVI	C, LF
	CALL	ZCO
	POP	PSW
	RET

ZPERCRLF:
	PUSH	PSW
	MVI	C, PERIOD
	CALL	ZCO
	MVI	C, CR
	CALL	ZCO
	MVI	C, LF
	CALL	ZCO
	POP	PSW
	RET

ZEOL:				;CR and clear current line
	MVI	C, CR
	CALL	ZCO
	MVI	C, CLEAR	;Use 80 spaces if necessary
	CALL	ZCO
	RET

ZCSTS:
  IF CPM
	PUSH	B
	PUSH	D
	PUSH	H
	MVI	C, CONST
	CALL	BDOS		;Returns with 1 in [A] if character at keyboard
	POP	H
	POP	D
	POP	B
	CPI	1
	RET
  ELSE	
	IN	COMI		;Get Character in [A]
	ANI	02H
	RZ
	MVI	A, 01H
	ORA	A
	RET
  ENDIF

ZCO:				;Write character that is in [C]
  IF CPM
	PUSH	PSW
	PUSH	B
	PUSH	D
	PUSH	H
	MOV	E, C
	MVI	C, WRCON
	CALL	BDOS
	POP	H
	POP	D
	POP	B
	POP	PSW
	RET
  ELSE	
	PUSH	PSW	
ZCO1:	IN   	CONI		;Show Character
	ANI	04H
	JZ	ZCO1
	MOV	A, C
	OUT	CONO
	POP	PSW
	RET
  ENDIF

ZCI:				;Return keyboard character in [A]
  IF CPM
	PUSH	B
	PUSH	D
	PUSH	H
	MVI	C, RDCON
	CALL	BDOS
	POP	H
	POP	D
	POP	B
	RET
  ELSE
ZCI1:	IN	CONI		;Get Character in [A]
	ANI	02H
	JZ	ZCI1
	IN	CONO
	RET
  ENDIF

;------------------------------------------------------------------------------	
;Print a string in [DE] up to '$'
;------------------------------------------------------------------------------	

PSTRING:
  IF CPM
	MVI	C, PRINT
	JMP	BDOS		;PRINT MESSAGE
  ELSE
	PUSH	B
	PUSH	D
	PUSH	H
	XCHG
PSTRX:	MOV	A, M
	CPI	'$'
	JZ	DONEP
	MOV	C, A
	CALL	ZCO
	INX	H
	JMP	PSTRX
DONEP:	POP	H
	POP	D
	POP	B
	RET
  ENDIF

SHOWerrors:
  IF NOT DEBUG
	ORA	A		;Set NZ flag
	STC			;Set Carry Flag
	RET
  ELSE
	CALL	ZCRLF
	MVI	E, REGstatus	;Get status in status register
	CALL	IDErd8D
	MOV	A, D
	ANI	1H
	JNZ	MoreError	;Go to  REGerr register for more info
				;All OK if 01000000
	PUSH	PSW		;Save for return below
	ANI	80H
	JZ	NOT7
	LXI	D, DRIVE$BUSY	;Drive Busy (bit 7) stuck high 
	CALL	PSTRING
	JMP	DONEERR
NOT7:	ANI	40H
	JNZ	NOT6
	LXI	D, DRIVE$NOT$READY ;Drive Not Ready (bit 6) stuck low 
	CALL	PSTRING
	JMP	DONEERR
NOT6:	ANI	20H
	JNZ	NOT5
	LXI	D, DRIVE$WR$FAULT ;Drive write fault
	CALL	PSTRING
	JMP	DONEERR
NOT5	LXI	D, UNKNOWN$ERROR
	CALL	PSTRING
	JMP	DONEERR

MoreError:			;Bit 0 of the status register indicates problem
	MVI	E, REGerr	;Get error code in REGerr
	CALL	IDErd8D
	MOV	A, D
	PUSH	PSW

	ANI	10H
	JZ	NOTE4
	LXI	D, SEC$NOT$FOUND
	CALL	PSTRING
	JMP	DONEERR

NOTE4:	ANI	80H
	JZ	NOTE7
	LXI	D, BAD$BLOCK
	CALL	PSTRING
	JMP	DONEERR
NOTE7:	ANI	40H
	JZ	NOTE6
	LXI	D, UNRECOVER$ERR
	CALL	PSTRING
	JMP	DONEERR
NOTE6:	ANI	4H
	JZ	NOTE2
	LXI	D, INVALID$CMD
	CALL	PSTRING
	JMP	DONEERR
NOTE2:	ANI	2H
	JZ	NOTE1
	LXI	D, TRK0$ERR
	CALL	PSTRING
	JMP	DONEERR
NOTE1:	LXI	D, UNKNOWN$ERROR1
	CALL	PSTRING
	JMP	DONEERR

DONEERR:POP	PSW
	PUSH	PSW
	CALL	ZBITS
	CALL	ZCRLF
	POP	PSW
	ORA	A		;Set Z flag
	STC			;Set Carry flag
	RET
   ENDIF

;------------------------------------------------------------------------------	
;Print a 16-bit number in RAM located @ [HL], low-byte first for Drive ID 
;------------------------------------------------------------------------------	

printparm:
	INX	H	;Index to high byte first
	MOV	A, M
	CALL	PHEX
	DCX	H	;Now low byte
	MOV	A, M
	CALL	PHEX
	RET

;------------------------------------------------------------------------------	
;Print an 8 bit number located in [A] 
;------------------------------------------------------------------------------	

PHEX:	PUSH	PSW
	PUSH	B
	PUSH	PSW
	RRC
	RRC
	RRC
	RRC
	CALL	ZCONV
	POP	PSW
	CALL	ZCONV
	POP	B
	POP	PSW
	RET

ZCONV:	ANI	0FH		;HEX to ASCII and print it
	ADI	90H
	DAA
	ACI	40H
	DAA
	MOV	C, A
	CALL	ZCO
	RET

;------------------------------------------------------------------------------	
;Display binary in [A] 
;------------------------------------------------------------------------------	

ZBITS:	PUSH	PSW
	PUSH	B
	PUSH	D
	MOV	E, A		
	MVI	B, 8
BQ2:	DB	0CBH, 23H	
	SLAR	E
	MVI	A, 18H
	ADC	A
	MOV	C, A
	CALL	ZCO
	DCR	B
	JNZ	BQ2
	POP	D
	POP	B
	POP	PSW
	RET

ghex32lba:			;Convert CPM Track & Sector to LBA format
	LXI     D,ENTER$SECL	;Enter sector number
	CALL	PSTRING
	CALL	GETHEX		;Get 2 HEX digits
	RC
	STA	mSEC
	CALL	ZCRLF

	LXI	D, ENTER$TRKH	;Enter high byte track number
	CALL	PSTRING
	CALL	GETHEX		;Get 2 HEX digits
	RC
	STA	mTRK+1
	CALL	ZCRLF

	LXI     D, ENTER$TRKL	;Enter low byte track number
	CALL	PSTRING
	CALL	GETHEX		;Get 2 more HEX digits
	RC
	STA	mTRK
	CALL	ZCRLF
	XRA	A
	ORA	A		;To return NC
	RET

;------------------------------------------------------------------------------	
;Get a HEX character from the keyboard and echo it 
;------------------------------------------------------------------------------	

GETHEX:
	CALL	GETCMD		;Get character
	CPI	ESC
	JZ	HEXABORT
	CPI	'/'		;check 0-9, A-F
	JC	HEXABORT
	CPI	'F'+1
	JNC	HEXABORT
	CALL	ASBIN		;Convert to binary
	RLC			;Shift to high nibble
	RLC
	RLC
	RLC
	MOV	B, A		;Store it
	CALL	GETCMD		;Get 2nd character from keyboard & ECHO
	CPI	ESC
	JZ	HEXABORT
	CPI	'/'		;check 0-9, A-F
	JC	HEXABORT
	CPI	'F'+1
	JNC	HEXABORT
	CALL	ASBIN		;Convert to binary
	ORA	B		;add in the first digit
	ORA	A		;To return NC
	RET
HEXABORT:
	STC			;Set Carry flag 
	RET

;------------------------------------------------------------------------------	
;Get a character from the keyboard, convert to uppercase and echo it 
;------------------------------------------------------------------------------	

GETCMD:	CALL	ZCI		;Get character
	CALL	UPPER
	CPI	ESC
	RZ			;Don't echo an ESC
  IF NOT CPM
	PUSH	PSW		;Save state of registers
	PUSH	B
 	MOV	C, A
	CALL	ZCO		;Echo it
	POP	B
	POP	PSW		;Retrieve original state
  ENDIF
	RET

;------------------------------------------------------------------------------	
;Convert lowercase to uppercase 
;------------------------------------------------------------------------------	

UPPER:	CPI	'a'		;Must be >= lowercase a
	RC			;else return as-is
	CPI	'z'+1		;Must be <= lowercase z
	RNC			;else return as-is
	SUI	'a'-'A'		;Subtract lowercase bias
	RET

ASBIN:	SUI	30H 		;ASCII to binary conversion
	CPI	0AH 
	RM
	SUI	07H 
	RET

;------------------------------------------------------------------------------	
;Print a hexdump of the data in the 512 byte buffer starting at [HL]
;------------------------------------------------------------------------------	

HEXDUMP:
	PUSH	PSW		;Save everything
	PUSH	B
	PUSH	D			 
	PUSH	H
	
	CALL	ZCRLF		;CR/LF first
	MVI	D, 32		;Print 32 lines total
	MVI	B, 16		;16 characters across
	SHLD	mStartLineHex	;Save buffer location for ASCII display below
	LXI	H, 0
	SHLD	mBYTE$COUNT
	
SF172:	CALL	ZCRLF
	LHLD	mBYTE$COUNT
	MOV	A, H
	CALL	PHEX		;Print byte count in sector
	MOV	A, L
	CALL	PHEX		
	PUSH	D
	LXI	D, 16
	DAD	D
	POP	D
	SHLD	mBYTE$COUNT	;Store for next time
	CALL	BLANK
	LHLD	mStartLineHex
	SHLD	mStartLineASCII	;Store for ASCII display below

SF175:	MOV	A, M
	CALL	LBYTE		;Display [A] on CRT/LCD
	INX	H
	DCR	B
	JNZ	SF175
	SHLD	mStartLineHex	;Save for next line later
	CALL	ShowAscii	;Now translate to ASCII and display
	MVI	B, 16		;16 characters across for next line
	DCR	D
	JNZ	SF172		;Have we done all 32 lines

	CALL	ZCRLF
	POP	H		;Get back original registers
	POP	D
	POP	B
	POP	PSW
	RET
	
ShowAscii:			;Show as ASCII info
	LHLD	mStartLineASCII
	MVI	B, 16		;16 ASCII characters across
XF172:	CALL	BLANK		;Send a space character
	CALL	BLANK
XF175:	MOV	A, M
	ANI	7FH
	CPI	' ' 		;Filter out control characters
	JNC	XT33
XT22:	MVI	A, '.'
XT33:	CPI	07CH
	JNC	XT22
	MOV	C, A		;Setup to send
	PUSH	B
	CALL	ZCO
	POP	B
	INX	H		;Next position in buffer
	DCR	B
	JNZ	XF175
	RET

BLANK:	PUSH	B
	PUSH	H
	MVI	C, ' '
	CALL	ZCO
	POP	H
	POP	B
	RET

LBYTE:	PUSH	PSW
	RRC
	RRC
	RRC
	RRC
	CALL	SF598
	POP	PSW

SF598:	CALL	ZCONV
	RET

;------------------------------------------------------------------------------	
;IDE Drive BIOS Routines written in a format that can be used directly with CPM
;------------------------------------------------------------------------------	

IDEinit:			;Initialize the 8255 and drive then do a hard reset
  IF VERBOSE
	LXI	D, INITDRIVE
	CALL	PSTRING
  ENDIF
	MVI	A, READcfg8255	;Config 8255 chip (10010010B)
	OUT	IDEportCtrl	;for READ mode
				
	MVI	A, IDErstline	;Hard reset the disk drive
	OUT	IDEportC	;Some CF cards are sensitive to reset pulse width
	MVI	B, 20H		;Symptom is incorrect data back from a sector read

ResetDelay:
	DCR	B
	JNZ	ResetDelay	;Delay (reset pulse width)

	XRA	A
	OUT	IDEportC	;No control lines asserted (just bit 7 of port C)
	CALL	DELAY$SHORT	;Short Delay

	MVI	D,11100000b	;Data for IDE SDH reg (512byte, LBA, single drive, hd 0)
				;For Trk, Sec, Head (non LBA) use 10100000

	MVI	E,REGshd	;00001110,(0EH) for CS0,A2,A1,  
	CALL	IDEwr8D		;Write byte to select the MASTER device

	MVI	B, 02H		;Delay time for hard disks to get up to speed (2s)

WaitInit:
  IF VERBOSE
	LXI	D, DISKSTATUS	;Print initialization status message
	CALL	PSTRING
  ENDIF
	MVI	E, REGstatus	;Get status after initilization
	CALL	IDErd8D		;Check Status (info in [D])
	MOV	A, D
  IF VERBOSE
	CALL	PHEX		;Print drive initialization status
	CALL	ZPERCRLF
  ENDIF
	ANI	80H
	RZ			;Return. We'll check for errors when we get back
	MVI	A, 2
	CALL	DELAY$LONG	;Long delay, drive has to get up to speed
	DCR	B
	JNZ	WaitInit
	XRA	A
	DCR	A
	RET			;Return NZ. We'll check for errors when we get back
	
DELAY$LONG:			;Long delay (Seconds)
	STA	mDELAYStore
	PUSH	B
	LXI	B, 0FFFFH
DELAY2:	LDA	mDELAYStore
DELAY1:	DCR	A
	JNZ	DELAY1
	DCX	B
	MOV	A, C
	ORA	B
	JNZ	DELAY2
	POP	B
	RET

DELAY$SHORT:			;Short delay (32ms)
	MVI	A, 40
DELAY3:	MVI	B, 0
M0:	DCR	B
	JNZ	M0
	DCR	A
	JNZ     DELAY3 
	RET

;------------------------------------------------------------------------------	
;Sector Read
;------------------------------------------------------------------------------	

READSECTOR:			;Read a sector, specified by the 3 bytes in LBA
				;Z on success, NZ call error routine if problem

	CALL	wrlba		;Tell which sector we want to read from.
				;Translate first in case of an error, otherewise 
				;we will get stuck on bad sector
 
	CALL	IDEwaitnotbusy	;Make sure drive is ready
	JC	SHOWerrors	;Returned with NZ set if error

	MVI	D, COMMANDread
	MVI	E, REGcommand
	CALL	IDEwr8D		;Send sec read command to drive.
	CALL	IDEwaitdrq	;Wait until it's got the data
	JC	SHOWerrors
		
	LHLD  	mDMA		;DMA address
	MVI	B, 0		;Read 512 bytes to [HL]

MoreRD16:
	MVI	A, REGdata	;REG register address
	OUT	IDEportC	

	ORI	IDErdline	;08H+40H, Pulse RD line
	OUT	IDEportC	

	IN	IDEportA	;Read the lower byte first
	MOV	M, A
	INX	H
	IN	IDEportB	;Then read the upper byte
	MOV	M, A
	INX	H
	
	MVI	A, REGdata	;Deassert RD line
	OUT	IDEportC
	DCR	B
	JNZ	MoreRD16

	MVI	E, REGstatus
	CALL	IDErd8D
	MOV	A, D
	ANI	1H
	CNZ	SHOWerrors	;If error display status
	RET

;------------------------------------------------------------------------------	
;Sector Write
;------------------------------------------------------------------------------	

WRITESECTOR:			;Write a sector, specified by the 3 bytes in LBA
				;Z on success, NZ to error routine if problem

	CALL	wrlba		;Tell which sector we want to read from.
				;Translate first in case of an error, otherewise 
				;we will get stuck on bad sector 

	CALL	IDEwaitnotbusy	;Make sure drive is ready
	JC	SHOWerrors

	MVI	D, COMMANDwrite
	MVI	E, REGcommand
	CALL	IDEwr8D		;Tell drive to write a sector
	CALL	IDEwaitdrq	;Wait unit it wants the data
	JC	SHOWerrors

	LHLD    mDMA
	MVI	B, 0

	MVI	A, WRITEcfg8255
	OUT	IDEportCtrl
	
WRSEC1:	MOV	A, M
	INX	H
	OUT	IDEportA	;Write the lower byte first
	MOV	A, M
	INX	H
	OUT	IDEportB	;Then high byte on B

	MVI	A, REGdata
	PUSH	PSW
	OUT	IDEportC	;Send write command
	ORI	IDEwrline	;Send WR pulse
	OUT	IDEportC
	POP	PSW
	OUT	IDEportC
	DCR	B
	JNZ	WRSEC1
	
	MVI	A, READcfg8255	;Set 8255 back to read mode
	OUT	IDEportCtrl	

	MVI	E, REGstatus
	CALL	IDErd8D
	MOV	A, D
	ANI	1H
	CNZ	SHOWerrors	;If error display status
	RET

;------------------------------------------------------------------------------	
;Write Logical Block Address (LBA) mode
;------------------------------------------------------------------------------	

wrlba:
	LDA	mSEC		;LBA mode low sectors go directly 
	INR	A		;Sectors are numbered 1 -- MAXSEC
	STA	mDRIVE$SEC	;For Diagnostic Display Only
	MOV	D, A
	MVI	E, REGsector	;Send info to drive
	CALL	IDEwr8D
				
	LHLD	mTRK

	MOV	A, L

	STA	mDRIVE$TRK
	MOV	D, L		;Send Low TRK#
	MVI	E, REGcylinderLSB
	CALL	IDEwr8D

	MOV	A, H

	STA	mDRIVE$TRK+1
	MOV	D, H		;Send High TRK#
	MVI	E, REGcylinderMSB
	CALL	IDEwr8D

	MVI	D, 1		;For now, one sector at a time
	MVI	E, REGseccnt
	CALL	IDEwr8D
	RET

;------------------------------------------------------------------------------	
;Wait for drive to come ready
;------------------------------------------------------------------------------	

IDEwaitnotbusy:			;Drive READY if status = 01000000
	MVI	B, 0FFH
	MVI	A, 0FFH		;Delay must be above 80H, longer for slow drives
	STA	mDELAYStore

MoreWait:
	MVI	E, REGstatus	;Wait for RDY bit to be set
	CALL	IDErd8D
	MOV	A, D
	ANI	11000000B
	XRI	01000000B
	JZ	DoneNotbusy
	DCR	B	
	JNZ	MoreWait
	LDA	mDELAYStore	;Check timeout delay
	DCR	A
	STA	mDELAYStore
	JNZ	MoreWait

	STC			;Set carry to indicate an error
	ret
DoneNotBusy:
	ORA	A		;Clear carry it indicate no error
	RET

;------------------------------------------------------------------------------	
;Wait for drive to assert data request (DRQ) line ready
;------------------------------------------------------------------------------	

IDEwaitdrq:
	MVI	B, 0FFH
	MVI	A, 0FFH		;Delay must be above 80H, longer for slow drives
	STA	mDELAYStore

MoreDRQ:
	MVI	E, REGstatus	;Wait for DRQ bit to be set
	CALL	IDErd8D
	MOV	A, D
	ANI	10001000B
	CPI	00001000B
	JZ	DoneDRQ
	DCR	B
	JNZ	MoreDRQ
	LDA	mDELAYStore	;Check timeout delay
	DCR	A
	STA	mDELAYStore
	JNZ	MoreDRQ
	STC			;Set carry to indicate error
	RET
DoneDRQ:
	ORA	A		;Clear carry
	RET			;Return drive status in A

;------------------------------------------------------------------------------	
;Clear the ID buffer
;------------------------------------------------------------------------------	

CLEAR$ID$BUFFER:
	LXI	H, IDBuffer
	LXI	B, 512
CLEAR2:	MVI	A, ' '
	MOV	M, A
	INX	H
	DCX	B
	MOV	A, C
	ORA	B
	JNZ	CLEAR2
	
	LXI	H, IDBuffer	;Zero for cylinder, heads, sectors
	LXI	B, 14
CLEAR3:	MVI	A, 0
	MOV	M, A
	INX	H
	DCX	B
	MOV	A, C
	ORA	B
	JNZ	CLEAR3
	RET

;------------------------------------------------------------------------------	
; Low Level 8 bit R/W to the drive controller. These are the routines that talk
; directly to the drive controller registers, via the 8255 chip.  
; Note the 16 bit I/O to the drive (which is only for SEC R/W) is done directly 
; in the routines READSECTOR & WRITESECTOR for speed reasons.
;------------------------------------------------------------------------------	

;------------------------------------------------------------------------------	
;Read One Byte
;------------------------------------------------------------------------------	

IDErd8D:				;Read 8 bits from IDE register in [E],
	MOV	A, E			;and return info in [D]
	OUT	IDEportC		;Drive address onto control lines

	ORI	IDErdline		;RD pulse pin (40H)
	OUT	IDEportC		;Assert read pin

	IN	IDEportA
	MOV	D, A			;Return with data in [D]

	MOV	A, E
	OUT	IDEportC		;Deassert RD pin

	XRA	A
	OUT	IDEportC		;Zero all port C lines
	RET

;------------------------------------------------------------------------------	
;Write One Byte
;------------------------------------------------------------------------------	

IDEwr8D:				;Write Data in [D] to IDE register [E]
	MVI	A, WRITEcfg8255		;Set 8255 to write mode
	OUT	IDEportCtrl

	MOV	A, D			;Get data put it in 8255 A port
	OUT	IDEportA

	MOV	A, E			;Select IDE register
	OUT	IDEportC

	ORI	IDEwrline		;Lower WR line
	OUT	IDEportC
	
	MOV	A, E			;Raise WR line
	OUT	IDEportC		;Deassert RD pin

	XRA	A			;Deselect all lines including WR line
	OUT	IDEportC

	MVI	A, READcfg8255		;Config 8255 chip, read mode on return
	OUT	IDEportCtrl
	RET

;------------------------------------------------------------------------------	
;This code is written to reside and run from 0H.  To re-introduce the CPMLDR,
;it must be copied from where it is stored in high memory and relocated to 100H
;in RAM, which overwrites this program.
;------------------------------------------------------------------------------	

CPM$MOVE$CODE
	LXI	H, BUFFER
	LXI	D, 100H
	LXI	B, (12*512)
	LDIR
	JMP	100H
CPM$MOVE$CODE$END:

;------------------------------------------------------------------------------	
;
;COMMAND BRANCH TABLE
;
;------------------------------------------------------------------------------	

TBL:	DW  DRIVE$0   ; "A"  Select Drive 0
	DW  DRIVE$1   ; "B"  Select Drive 1
	DW  CPMBOOT   ; "C"  LOAD CPM (if present)
	DW  DISPLAY   ; "D"  Sector contents display: ON/OFF
	DW  RAMCLEAR  ; "E"  Clear RAM buffer
	DW  FORMAT    ; "F"  Format current disk
	DW  RESTORE   ; "G"  Restore backup
	DW  BACKUP    ; "H"  Backup partition
	DW  NEXT$SECT ; "I"  Next Sector
	DW  PREV$SEC  ; "J"  Previous sector
	DW  ERROR     ; "K"  
	DW  SET$LBA   ; "L"  Set LBA value (Set Track, sector)  
	DW  ERROR     ; "M"  
	DW  POWER$DOWN; "N"  Power down hard disk command
	DW  ERROR     ; "O"  
	DW  ERROR     ; "P"  
	DW  ERROR     ; "Q"  
	DW  READ$SEC  ; "R"  Read sector to data buffer
	DW  SEQ$RD    ; "S"  Sequental sec read and display contents
	DW  ERROR     ; "T"  
	DW  POWER$UP  ; "U"  Power up hard disk command
	DW  N$RD$SEC  ; "V"  Read N sectors
	DW  WRITE$SEC ; "W"  Write data buffer to current sector
	DW  N$WR$SEC  ; "X"  Write N sectors
	DW  COPY$AB   ; "Y"  Copy Drive 0 to Drive 1
	DW  VERIFY$AB ; "Z"  Verify Drive 0 = Drive 1

;------------------------------------------------------------------------------	
;
;String constants - Messages generated by this program
;
;------------------------------------------------------------------------------

SIGN$ON:	DB	CR,LF,'IDE Disk Drive Utility Program 12/01/2022 (v2.9b)',CR,LF,LF,'$'
SEL0MSG		DB	'Selecting first IDE drive.',CR,LF,'$'
SEL1MSG		DB	'Selecting second IDE drive.',CR,LF,'$'
INITDRIVE	DB	'Initializing drive.  $'
READING$ID	DB	'Reading drive ID.  $'
GETTING$ID	DB	'Getting drive ID...',CR,LF,'$'
DISKSTATUS	DB	'Status is $'
INIT$0$ERROR:	DB	'Initialization of First Drive failed. Aborting Program.',BELL,CR,LF,LF,'$'
INIT$1$ERROR	DB	'Initialization of Second Drive failed. (Possibly not present).',BELL,CR,LF,LF,'$'
ID$ERROR:	DB	'Error obtaining Drive ID.',BELL,CR,LF,'$'
INIT$DR$OK:	DB	'Drive Initialized OK.',CR,LF,LF,'$'
BAD$DRIVE:	DB	CR,LF,'First Drive ID Information appears invalid.',CR,LF
		DB	'Aborting program.',BELL,CR,LF,LF,'$'
DRIVE0$INFO:	DB	'------------ Drive 0 -------------',CR,LF,'$'
DRIVE1$INFO:	DB	'------------ Drive 1 -------------',CR,LF,'$'
msgmdl:		DB	'Model: $'
msgsn:		DB	'S/N:   $'
msgrev:		DB	'Rev:   $'
msgcy:		DB	'Cyl: $'
msghd:		DB	', Hd: $'
msgsc:		DB	', Sec: $'
msgCPMTRK:	DB	'CPM TRK = $'
msgCPMSEC:	DB	' CPM SEC = $'
msgLBA:		DB	'  (LBA = 00$'
MSGBracket	DB	')$'
DRIVE$0$MSG	DB	CR,LF,LF,'  >>> DRIVE #0 <<<$'
DRIVE$1$MSG	DB	CR,LF,LF,'  >>> DRIVE #1 <<<$'
CMD$STRING1: 	DB	'  IDE Board Diagnostic MAIN MENU',CR,LF,LF
		DB	'(L) Set LBA value    (R) Read Sector to Buffer (W) Write Buffer '
		DB	'to Sector',CR,LF
		DB	'(D) Set Display ON   (S) Sequental Sec Read    (F) Format Disk',CR,LF
		DB	'(V) Read N Sectors   (X) Write N Sectors       (H) Backup disk',CR,LF
		DB	'(G) Restore Backup   (I) Next Sector           '
		DB	'(J) Previous Sector',CR,LF
		DB	'(U) Power Up         (N) Power Down            (C) Boot CPM',CR,LF
		DB	'(A) Select Drive 0   (B) Select Drive 1        '
		DB	'(E) Clear Sector Buffer',CR,LF
		DB	'(Y) Copy d0 to d1    (Z) Verify d0 = d1        (ESC) Quit',CR,LF
		DB	LF,'Current settings: $'
CMD$STRING2: 	DB	'      IDE Board Diagnostic MAIN MENU',CR,LF,LF
		DB	'(L) Set LBA value    (R) Read Sector to Buffer (W) Write Buffer '
		DB	'to Sector',CR,LF
		DB	'(D) Set Display OFF  (S) Sequental Sec Read    (F) Format Disk',CR,LF
		DB	'(V) Read N Sectors   (X) Write N Sectors       (H) Backup disk',CR,LF
		DB	'(G) Restore Backup   (I) Next Sector           '
		DB	'(J) Previous Sector',CR,LF
		DB	'(U) Power Up         (N) Power Down            (C) Boot CPM',CR,LF
		DB	'(A) Select Drive 0   (B) Select Drive 1        '
		DB	'(E) Clear Sector Buffer',CR,LF
		DB	'(Y) Copy d0 to d1    (Z) Verify d0 = d1        (ESC) Quit',CR,LF
		DB	LF,'Current settings: $'
Prompt:		DB	CR,LF,LF,'Please enter command > $'
Response:	DB	CR,LF,'Command received:      $'
msgsure:	DB	CR,LF,'Warning: this will change data on the drive, '
		DB	'are you sure? (Y/N)...$'
msgrd:		DB	CR,LF,'Sector Read OK',CR,LF,'$'
msgwr:		DB	CR,LF,'Sector Write OK',CR,LF,'$'
GET$LBA:	DB	'Enter CPM style TRK & SEC values (in hex).',CR,LF,'$'
SEC$RW$ERROR	DB	'Drive Error, Status Register = $'
ERR$REG$DATA	DB	'Drive Error, Error Register = $'
ENTER$SECL	DB	'Starting sector number,(xxH) = $'
ENTER$TRKL	DB	'Track number (LOW byte, xxH) = $'
ENTER$TRKH	DB	'Track number (HIGH byte, xxH) = $'
ENTER$HEAD	DB	'Head number (01-0F) = $'
ENTER$COUNT	DB	'Number of sectors to R/W = $'
DRIVE$BUSY	DB	'Drive Busy (bit 7) stuck high.   Status = $'
DRIVE$NOT$READY	DB	'Drive Ready (bit 6) stuck low.  Status = $'
DRIVE$WR$FAULT	DB	'Drive write fault.    Status = $'
UNKNOWN$ERROR	DB	'Unknown error in status register.   Status = $'
BAD$BLOCK	DB	'Bad Sector ID.    Error Register = $'
UNRECOVER$ERR	DB	'Uncorrectable data error.  Error Register = $'
READ$ID$ERROR	DB	'Error setting up to read Drive ID',CR,LF,'$'
SEC$NOT$FOUND	DB	'Sector not found. Error Register = $'
INVALID$CMD	DB	'Invalid Command. Error Register = $'
TRK0$ERR	DB	'Track Zero not found. Error Register = $'
UNKNOWN$ERROR1	DB	'Unknown Error. Error Register = $'
CONTINUE$MSG	DB	CR,LF,'To Abort enter ESC. Any other key to continue. $'
FORMAT$MSG	DB	'FORMAT DISK. Fill all sectors with E5'
		DB	60H,'s on the CURRENT drive/CF card.$'
ReadN$MSG	DB	CR,LF,'Read multiple sectors from current disk/CF card to RAM buffer.'
		DB	CR,LF,'How many 512 byte sectores (xx HEX):$'
WriteN$MSG	DB	CR,LF,'Write multiple sectors RAM buffer CURRENT disk/CF card.'
		DB	CR,LF,'How many 512 byte sectores (xx HEX):$'
ReadingN$MSG	DB	CR,LF,'Reading Sector at: $'
WritingN$MSG	DB	CR,LF,'Writing Sector at: $'
msgErr		DB	CR,LF,'Sorry, that was not a valid menu option!$'
FormatDone	DB	CR,LF,'Disk Format Complete.',CR,LF,'$'
BackupDone	DB	CR,LF,'Disk partition copy complete.',CR,LF,'$'
CopyMsg		DB	CR,LF,'Copy disk partition to a second area on disk (CF card).'
		DB	CR,LF,'>>> This assumes that tracks greater than MAXTRK '
		DB	'(for CPM, 0FFH) are unused <<<'
		DB	CR,LF,'>>> on this disk. Be sure you have nothing in this '
		DB	'"Backup partition area". <<<'
		DB	CR,LF,BELL,'Warning: This will change data in the partition area, '
		DB	'are you sure? (Y/N)...$ '
AtEnd		DB	CR,LF,'At end of disk partition!',CR,LF,'$'
RBackup$MSG	DB	'Reading track: $'
WBackup$MSG	DB	'H. Writing track: $'
H$Msg		DB	'H$'
RestoreMsg	DB	CR,LF,'Restore disk with data from backup partition on disk (CF card).'
		DB	CR,LF,BELL,'Warning: This will change data on disk, '
		DB	'are you sure? (Y/N)...$ '
RestoreDone	DB	CR,LF,'Restore of disk data from backup partition complete.',CR,LF,'$'
RANGE$MSG	DB	CR,LF,'Sector value out of range.',CR,LF,'$'
CPM$ERROR	DB	CR,LF,'Error reading CPMLDR.',CR,LF,'$'
CPM$ERROR1	DB	CR,LF,'Data error reading CPMLDR. (The first byte loaded was not 31H).',CR,LF,'$'
MOVE$REQUEST	DB	CR,LF,'The CPMLDR image is now at 3000H in RAM. '
		DB	'To boot CPM you will have to'
		DB	CR,LF,'overwrite this program at 100H. Do you wish to do so (Y/N)...$'
SETA$MSG	DB	CR,LF,'Current Drive is now #0 (Yellow LED)$'
SETB$MSG	DB	CR,LF,'Current Drive is now #1 (Green LED)$'
FILL$MSG	DB	CR,LF,'Sector buffer in RAM filled with 0',27H,'s$'		
DiskCopyMsg	DB	CR,LF,'Copy disk partition of Drive 0 to Drive 1 (CF card).'
		DB	CR,LF,BELL,'Warning: This will delete all data on Drive 1, '
		DB	'are you sure? (Y/N)...$ '
CopyDone	DB	CR,LF,'Disk copy of CPM disk 0 to 1 complete.',CR,LF,'$'
CopyTrk$MSG	DB	'Copying track: $'
DiskVerifyMsg	DB	CR,LF,'Verify disk partition Drive 0 = Drive 1 (CF card).$'
VerifyTrk$MSG	DB	'Verifying track: $'
VerifyDone	DB	CR,LF,'Verify CPM disk 0 = 1 complete.',CR,LF,'$'
Verify$ERR	DB	CR,LF,BELL,'Verify error on Track $'
SEC$Msg		DB	'H  Sector $'

;------------------------------------------------------------------------------	
;RAM usage
;------------------------------------------------------------------------------	

RAMAREA		DB	'           RAM STORE AREA -------->'
mDMA		DW	buffer
mDRIVE$SEC	DB	0H
mDRIVE$TRK	DW	0H
mDisplayFlag	DB	0FFH		;Display of sector data initially ON
mSEC		DW	0H
mTRK		DW	0H
mSEC1		DW	0H		;For disk partition copy
mTRK1		DW	0H
mSEC2		DW	0H
mTRK2		DW	0H
mStartLineHex	DW	0H
mStartLineASCII	DW	0H
mBYTE$COUNT	DW	0H
mSECTOR$COUNT	DW	0H
mDELAYStore	DB	0H
mCURRENT$DRIVE	DB	0H

		DB	'          Start of ID buffer-->'
IDbuffer	DS	512
		DB	'<--End of ID buffer            '

		ORG	BUFFER$ORG

BUFFER:		DB	76H
		DB	'<--Start buffer area' 
		DS	476
		DB	'End of buffer-->'

BUFFER2:	DB	'<--Start buffer2 area'
		DS	476
		DB	'End of buffer2-->'

		DS	100H

STACK		DW	0H

;END
Wayne Parham
Posts: 253
Joined: March 18th, 2022, 3:01 pm
Contact:

Re: IDE interface for Altair 8800c

Post by Wayne Parham »

Getting closer.

See below the latest version of IDEutil. I'm calling it v3.0x to distinguish it from the fully tested version, which will drop the "x." Just think of that as "experimental."

It has several changes from the last one I uploaded, which was a derivative of v2.9. This one has David Fry's wrlba function for contiguous sector access. It also has numerous bugfixes from John Monahan. I diffed myIDE 2.9 and 3.0 to find all the updates, and incorporated them in this version of IDEutil. But I purposely removed the facility to set sector size or to provide the non-contiguous original "holes" format. This code only supports to contiguous "no holes" format with 64 sectors per track.

I also diffed 3.0 and 3.1 but found very few differences. It's mostly re-arranging the menu choices. I'll look at it more closely and if I find more significant improvements, I'll add them. But for now, I'm mostly in a test/break/fix mode with IDEutil.

As far as that's concerned, the breaks that needed fixing were mostly either Z80 and/or assembler/macro related. In addition to the DJNZ instructions I found early on, I have also discovered BIT and LDIR instructions. I "cheated" on those by running MAC and Z80.LIB to see what opcodes it generated, and I inserted them into the source code using DB statements. That removed the errors, of course, but I still need to be sure the opcodes are valid 8080 instructions. I also found IF/ELSE/ENDIF directives that aren't supported by ASM, so I rewrote the directives to work with ASM. That got rid of all the build errors.
Wayne Parham
Posts: 253
Joined: March 18th, 2022, 3:01 pm
Contact:

Re: IDE interface for Altair 8800c

Post by Wayne Parham »

Code: Select all

;------------------------------------------------------------------------------
; Utility Program for IDE interface board
;	v3.0x	      12/08/2022
;
; Wayne Parham        wayne@parhamdata.com
;
; The IDE interface board is expected to be configured for I/O base address 30H
; but can be changed by redefining IDE interface equates below.
;
; IDEutil.com can be built using the following commands:
;
; ASM IDEutil
; HEXCOM IDEutil
;
; This program is largely borrowed from John Monahan's "myIDE" used to  support
; the  IDE/CF v4 board sold by S100computers.com.   It is generally  compatible
; with  myIDE except for minor cosmetic changes.  The most significant  changes
; were  made to provide compatibility with the Digital Research ASM  assembler.
;
; Other credits should be given to Peter Faasse and to David Fry.  Peter Faasse
; described the 8255 interface that is implemented herein.  And David Fry wrote
; the wrlba function used here to translate CP/M track and sector addresses  to
; IDE LBA addresses.
;
;------------------------------------------------------------------------------

;------------------------------------------------------------------------------
;Build equates:
;------------------------------------------------------------------------------

FALSE		EQU	0
TRUE		EQU	NOT FALSE

CPM		EQU	TRUE	;TRUE if using CPM, FALSE if loaded directly
DEBUG		EQU	TRUE	;TRUE for error messages
VERBOSE		EQU	FALSE	;TRUE for extended error messages
CPM$TRANSLATE	EQU	TRUE	;Translate Trk, Sec, Head to CPM TRACK# & SEC#

;------------------------------------------------------------------------------
;Console equates:
;------------------------------------------------------------------------------

CONI		EQU	10H	;Console input port
CONO		EQU	11H	;Console output port

;------------------------------------------------------------------------------
;CP/M equates:
;------------------------------------------------------------------------------

RDCON		EQU	1	;For CP/M I/O
WRCON		EQU	2
RESET$DISK	EQU	0DH	;Reset all CPM disks
PRINT		EQU	9
CONST		EQU	11	;Console stat
BDOS		EQU	5

;------------------------------------------------------------------------------
;Display control equates:
;------------------------------------------------------------------------------

SCROLL		EQU	01H	;Set scroll direction UP
LF		EQU	0AH
CR		EQU	0DH
BS		EQU	08H	;Back space (required for sector display)
PERIOD		EQU	2EH
BELL		EQU	07H
SPACE		EQU	20H
QUIT		EQU	11H	;Turns off any screen enhancements
NO$ENHANCEMENT	EQU	17H	;Turns off whatever is on
FAST		EQU	10H	;High speed scrool
TAB		EQU	09H	;Tab across
ESC		EQU	1BH
CLEAR		EQU	1CH	;Clear line (Use 80 spaces if not available)

;------------------------------------------------------------------------------
;IDE interface equates:
;------------------------------------------------------------------------------
;Ports for 8255 chip. Change these to specify where your 8255 is addressed,
;The first three control which 8255 ports have the control signals,
;upper and lower data bytes.  The last one (IDEportCtrl), is for mode setting
;for the 8255 to configure its actual I/O ports (A,B & C).  
;
;Note most drives these days don't use the old Head, Track, Sector terminology.
;Instead we use "Logical Block Addressing" or LBA.  This is what we use below.
;LBA treats the drive as one continous set of sectors, 0,1,2,3,... 3124,...etc.
;However as seen below we need to convert this LBA to heads,tracks and sectors
;to be compatible with CPM & MSDOS.
;
;NOTE: If you have only one drive/CF card, be sure it is in drive #0.
;The IDE hardware gets confused if there is only a drive in slot #1.
;------------------------------------------------------------------------------

IDEportA	EQU	030H	;Lower 8 bits of IDE interface (8255)
IDEportB	EQU	031H	;Upper 8 bits of IDE interface
IDEportC	EQU	032H	;Control lines for IDE interface
IDEportCtrl	EQU	033H	;8255 configuration port
IDEDrive	EQU	034H	;Bit 0 - 0 for drive 0 and 1 for drive 1

READcfg8255	EQU	10010010b ;Set 8255 IDEportC to out, IDEportA/B input
WRITEcfg8255	EQU	10000000b ;Set all three 8255 ports to output mode

;------------------------------------------------------------------------------
;IDE control lines for use with IDEportC.  
;------------------------------------------------------------------------------

IDEa0line	EQU	01H	;direct from 8255 to IDE interface
IDEa1line	EQU	02H	;direct from 8255 to IDE interface
IDEa2line	EQU	04H	;direct from 8255 to IDE interface
IDEcs0line	EQU	08H	;inverter between 8255 and IDE interface
IDEcs1line	EQU	10H	;inverter between 8255 and IDE interface
IDEwrline	EQU	20H	;inverter between 8255 and IDE interface
IDErdline	EQU	40H	;inverter between 8255 and IDE interface
IDErstline	EQU	80H	;inverter between 8255 and IDE interface

;------------------------------------------------------------------------------
;Symbolic constants for the IDE drive registers
;------------------------------------------------------------------------------

REGdata		EQU	IDEcs0line
REGerr		EQU	IDEcs0line + IDEa0line
REGseccnt	EQU	IDEcs0line + IDEa1line
REGsector	EQU	IDEcs0line + IDEa1line + IDEa0line
REGcylinderLSB	EQU	IDEcs0line + IDEa2line
REGcylinderMSB	EQU	IDEcs0line + IDEa2line + IDEa0line
REGshd		EQU	IDEcs0line + IDEa2line + IDEa1line
REGcommand	EQU	IDEcs0line + IDEa2line + IDEa1line + IDEa0line
REGstatus	EQU	IDEcs0line + IDEa2line + IDEa1line + IDEa0line
REGcontrol	EQU	IDEcs1line + IDEa2line + IDEa1line
REGastatus	EQU	IDEcs1line + IDEa2line + IDEa1line + IDEa0line

;------------------------------------------------------------------------------
;IDE Command Constants.  These should never change.
;------------------------------------------------------------------------------

COMMANDrecal	EQU	10H
COMMANDread	EQU	20H
COMMANDwrite	EQU	30H
COMMANDinit	EQU	91H
COMMANDid	EQU	0ECH
COMMANDspindown	EQU	0E0H
COMMANDspinup	EQU	0E1H

;------------------------------------------------------------------------------
;IDE Status Register:
;------------------------------------------------------------------------------

;  bit 7: Busy	1=busy, 0=not busy
;  bit 6: Ready 1=ready for command, 0=not ready yet
;  bit 5: DF	1=fault occurred
;  bit 4: DSC	1=seek complete
;  bit 3: DRQ	1=data request ready, 0=not ready to xfer yet
;  bit 2: CORR	1=correctable error occurred
;  bit 1: IDX	vendor specific
;  bit 0: ERR	1=error occured

;------------------------------------------------------------------------------
;Disk equates:
;------------------------------------------------------------------------------

SEC$SIZE	EQU	512	;Bytes per sector
MAXSEC		EQU	3FH	;Sectors per track
MAXTRK		EQU	0FFH	;CPM3 allows up to 8MG so 256 "tracks"
BUFFER$ORG	EQU	3000H	;<----- Will place all sector data here

CPM$BOOT$COUNT	EQU	12	;Allow up to 12 CPM sectors for CPMLDR
CPMLDR$ADDRESS	EQU	BUFFER$ORG

;------------------------------- INITIALIZATION -------------------------------	

	ORG	100H		;<--- For CPM

begin:
	LXI	SP, STACK
	LXI     D, SIGN$ON	;Print welcome message
	CALL	PSTRING
  IF VERBOSE
	LXI	D, SEL0MSG	;Print select drive 0 message
	CALL	PSTRING
  ENDIF
	CALL	SELECT0		;Select the first drive
  IF VERBOSE
	LXI	D, INITDRIVE	;Print initialization message
	CALL	PSTRING
  ENDIF
	CALL	IDEinit		;Initialize the board and drive 0

	JZ	INIT$OK		;Continue on Zero
	
	LXI	D, INIT$0$ERROR	;Non-zero is error, probably no drive
	CALL	PSTRING
	JMP	ABORT
	
INIT$OK:			;Get drive 0 identification info			
	CALL	driveid
	JZ	INIT$OK1

	LXI	D, ID$ERROR	;End program on error
	CALL	PSTRING
	JMP	ABORT

INIT$OK1:			;Check sector count
	LXI     H, IDbuffer + 12
	MOV	A, M		;(High Byte)
	ORA	A
	JNZ	INIT$OK2
	INX	H
	MOV	A, M		;(Low Byte)
	ORA	A
	JNZ	INIT$OK2	;Looks like we have a valid IDE drive
	
	LXI	D, BAD$DRIVE	;Zero sectors means something's wrong
	CALL	PSTRING
	JMP	ABORT		;No drive #0 so abort

INIT$OK2:			;Print drive 0 info
  IF VERBOSE
	LXI	D, DRIVE0$INFO
	CALL	PSTRING
	LXI     D, msgmdl	;Drive name	
	CALL	PSTRING
	LXI     H, IDbuffer + 54
	MVI	B, 20		;Character count in words
	CALL	printSwap	;Print [HL], [B] X 2 characters
	CALL	ZCRLF

	LXI     D, msgsn	;Serial number
	CALL	PSTRING
	LXI     H, IDbuffer + 20
	MVI	B, 10		;Character count in words
	CALL	printText
	CALL	ZCRLF

	LXI     D, msgrev	;Firmware revision string
	CALL	PSTRING
	LXI     H, IDbuffer + 46
	MVI	B, 4		;Character count in words
	CALL	printSwap
	CALL	ZCRLF

	LXI     D, msgcy	;Drive specs (cyl/hd/sect)
	CALL	PSTRING
	LXI     H, IDbuffer + 2
	CALL	printparm
	LXI     D, msghd
	CALL	PSTRING
	LXI     H, IDbuffer + 6
	CALL	printparm
	LXI     D, msgsc
	CALL	PSTRING
	LXI     H, IDbuffer + 12
	CALL	printparm
	CALL	ZCRLF

	LXI	D, msgLBAsup1	;First part of LBA support message
	CALL	PSTRING
	LDA	IDbuffer+98+1	;Bits 15-10 reserved, 9 LBA, 8 DMA
	ANI	02H
	JNZ	ID$SUP0		;LBA is supported
	LXI	D, msgLBAnot	;LBA is not supported
	CALL	PSTRING
ID$SUP0:
	LXI	D, msgLBAsup2
	CALL	PSTRING
  ENDIF
INIT$OK3:			;Move to second drive
  IF VERBOSE
	CALL	ZCRLF
	LXI	D, SEL1MSG	;Print select drive 1 message
	CALL	PSTRING
  ENDIF
	CALL	SELECT1		;Select drive 1
  IF VERBOSE
	LXI	D, INITDRIVE	;Print initialization message
	CALL	PSTRING
  ENDIF
	CALL	IDEinit		;Initialize the second drive
	JZ	INIT$OK4

	LXI	D, INIT$1$ERROR	;Non-zero is error, so print warning
	CALL	PSTRING
	XRA	A
	STA	mDriveBPresent	;Clear flag to indicate drive 1 absense
	JMP	INIT$DONE

INIT$OK4:			;Get drive 1 identification info
	CALL	driveid
	JZ	INIT$OK5

	LXI	D, ID$ERROR	;On error, display message
	CALL	PSTRING
	JMP	INIT$DONE

INIT$OK5:			;Check sector count
	LXI     H, IDbuffer + 12
	MOV	A, M		;(High Byte)
	ORA	A
	JNZ	INIT$OK6
	INX	H
	MOV	A, M		;(Low Byte)
	ORA	A
	JNZ	INIT$OK6	;Looks like we have a valid IDE drive
BAD$DR1:	
	LXI	D, BAD$DRIVE	;Zero sectors, so display error
	CALL	PSTRING
	JMP	INIT$DONE

INIT$OK6:			;Print drive 1 info
  IF VERBOSE
	LXI	D, DRIVE1$INFO
	CALL	PSTRING
	LXI     D, msgmdl	;Drive name	
	CALL	PSTRING
	LXI     H,IDbuffer + 54
	MVI	B, 20		;Character count in words
	CALL	printSwap	;Print [HL], [B] X 2 characters
	CALL	ZCRLF

	LXI     D, msgsn	;Serial number
	CALL	PSTRING
	LXI     H, IDbuffer + 20
	MVI	B, 10		;Character count in words
	CALL	printText
	CALL	ZCRLF

	LXI     D, msgrev	;Firmware revision string
	CALL	PSTRING
	LXI     H, IDbuffer + 46
	MVI	B, 4		;Character count in words
	CALL	printSwap
	CALL	ZCRLF

	LXI     D, msgcy	;Drive specs (cyl/hd/sect)
	CALL	PSTRING
	LXI     H, IDbuffer + 2
	CALL	printparm
	LXI     D, msghd
	CALL	PSTRING
	LXI     H, IDbuffer + 6
	CALL	printparm
	LXI     D, msgsc
	CALL	PSTRING
	LXI     H, IDbuffer + 12
	CALL	printparm
	CALL	ZCRLF

	LXI	D,msgLBAsup1	;First part of LBA support message
	CALL	PSTRING
	LDA	IDbuffer+98+1	;Bits 15-10 reserved, 9 LBA, 8 DMA
	ANI	02H
	JNZ	ID$SUP1		;LBA is supported
	LXI	D,msgLBAnot	;LBA is not supported
	CALL	PSTRING
ID$SUP1:
	LXI	D,msgLBAsup2
	CALL	PSTRING
  ENDIF
	MVI	A, 1		;Set flag to indicate drive 1 present
	STA	mDriveBPresent			

INIT$DONE:			;Cleanup and enter main menu
	CALL	IDEinit		;Re-initialize drive 1
	MVI	A, 0
	STA	mCURRENT$DRIVE	;Select drive 0
	OUT	IDEDrive
	CALL	IDEinit		;Re-initialize drive 0
	LXI	H, 0
	SHLD	mSEC		;Default to track 0 and sector 0
	SHLD	mTRK
	LXI	H, buffer	;Set DMA address to buffer
	SHLD	mDMA
	JMP	MAINLOOP	;Display Main Menu

;------------------------------------------------------------------------------	

TERMINATE:			;End program from ESC command
ABORT:				;Controlled termination
	CALL	SELECT0
	CALL	ZCRLF
  IF CPM
	MVI	C, RESET$DISK	;Reset all disks
	RET
  ENDIF			;Return to CPM
  IF NOT CPM
	JMP	0F800H		;Transfer control to Monitor ROM	
  ENDIF

;-------------------------------- MENU OPTIONS --------------------------------	

MAINLOOP:			;Print main menu
	LDA	mCURRENT$DRIVE
	ORA	A
	JNZ	DRIVE$1$MENU
	LXI	D, DRIVE$0$MSG
	CALL	PSTRING
	JMP	Display0
DRIVE$1$MENU:
	LXI	D, DRIVE$1$MSG
	CALL	PSTRING
Display0:
	LDA	mDisplayFlag	;Sector data display flag on or off
	ORA	A		;NZ = on (Initially 0FFH so display on)
	JNZ     Display1
	LXI     D, CMD$STRING1	;List command options (Turn display option on)
	JP	Display2
Display1:
	LXI     D, CMD$STRING2	;List command options (Turn display option off)
Display2:
	CALL	PSTRING
	
	CALL	wrlba		;Update LBA on drive
	CALL	DISPLAYposition	;Display current track, sector, head
	
	LXI	D, Prompt	;'>'
	CALL	PSTRING

;--------------------------------- USER INPUT ---------------------------------	
	
	CALL	GETCMD		;Character Input
	CPI	ESC
	JZ	TERMINATE	;End on ESC
	CPI	'A'
	JC	ERROR		;Must be >= 'A'
	CPI	'Z'+1
	JNC	ERROR		;Must be <= 'Z'
	CALL	ZCRLF

	SBI	'A'-1		;Adjust to make 'A' keypress = 0
	ADD	A

	LXI	H, TBL		;Offset into vector table
	ADD	L
	MOV	L, A
	MOV	A, M
	DB	03H		;INX  HL

	MOV	E, M		;Get selected function address
	INX	H
	MOV	D, M
	XCHG

	PCHL			;Jump to command function address
	
;------------------------------ COMMAND FUNCTIONS -----------------------------	

READ$SEC:			;Read Sector @ LBA to the RAM buffer
	LXI	H, buffer	;Point to buffer
	SHLD	mDMA

	CALL	READSECTOR

	JZ	main1b		;Z means the sector read was OK
	CALL	ZCRLF
	JMP	MAINLOOP

main1b:	LXI     D, msgrd	;Sector read OK
	CALL	PSTRING

	LDA	mDisplayFlag	;Do we have display flag on or off
	ORA	A		;NZ = on 
	JZ	MAINLOOP
	LXI	H, buffer	;Point to buffer. Show sector data flag is on
	SHLD	mDMA
	CALL	HEXDUMP		;Show sector data
	JMP	MAINLOOP

WRITE$SEC:			;Write data in RAM buffer to sector @ LBA
	LXI     D, msgsure	;Are you sure?
	CALL	PSTRING
	CALL	ZCI
	CALL	UPPER
	CPI	'Y'
	JNZ	main2c
	CALL	ZCRLF

	LXI	H, buffer	;Point to buffer
	SHLD	mDMA

	CALL	WRITESECTOR

	JZ	main2b		;Z means the sector write was OK
	CALL	ZCRLF
	JMP	MAINLOOP
main2b:	LXI     D, msgwr	;Sector written OK
	CALL	PSTRING
main2c: JMP	MAINLOOP

SET$LBA:			;Set the logical block address
	LXI     D, GET$LBA	
	CALL	PSTRING
	CALL	ghex32lba	;Get CPM style Track & Sector, put in RAM 
	JC	main3b		;Ret C set if abort/error
	CALL	wrlba		;Update LBA on drive
main3b:	CALL	ZCRLF
	JMP	MAINLOOP

NEXT$SECT:
	LDA	mSEC
	CALL	CHK$SEC		;Compare current to Max CPM Sector
	JZ	RANGE$ERROR	;If equal, we are at max already
	INR	A		;Otherwise, on to the next sector
	STA	mSEC
	CALL	wrlba		;Update LBA on drive
	CALL	ZCRLF
	JMP	MAINLOOP

RANGE$ERROR:
	LXI     D, RANGE$MSG	
	CALL	PSTRING
	JMP	MAINLOOP
	
PREV$SEC:
	LDA	mSEC
	ORA	A
	JZ	RANGE$ERROR
	DCR	A
	STA	mSEC
	CALL	wrlba		;Update LBA on drive
	CALL	ZCRLF
	JMP	MAINLOOP
	
POWER$UP:			;Set the drive to spin up
	CALL	spinup
	JMP	MAINLOOP

POWER$DOWN:			;Set the drive to spin down
	CALL	spindown
	JMP	MAINLOOP

DISPLAY:			;Do we have display flag on or off
	LDA	mDisplayFlag	
	CMA			;flip it
	STA	mDisplayFlag
	JMP	MAINLOOP	;Update display and back to next menu command

SEQ$RD:				;Do sequential reads
	CALL	SequentialReads
	JMP	MAINLOOP

DRIVE$0:
	CALL	SELECT0		;Select drive 0
	LXI     D, SET0$MSG	
	CALL	PSTRING
	JMP	MAINLOOP

DRIVE$1:
	CALL	SELECT1		;Select drive 1
	LXI     D, SET1$MSG	
	CALL	PSTRING
	JMP	MAINLOOP

RAMCLEAR:			;Fill RAM buffer with 0's
	LXI	H, buffer	;Point to buffer
	LXI	D, 512
CLEAR1:
	XRA	A		;Fill area with 0's
	MOV	M, A
	INX	H
	DCX	D
	MOV	A, E
	ORA	D
	JNZ	CLEAR1
	LXI     D, FILL$MSG
	CALL	PSTRING
	JMP	MAINLOOP

CPMBOOT:			;Boot CPM from IDE system tracks -- if present
	XRA	A		;Load from track 0, sec 1, head 0 (always)
	STA	mTRK+1
	STA	mTRK
	MVI	A, 1		;Sector 1
	STA	mSEC

	MVI	A, CPM$BOOT$COUNT ;Count of CPMLDR sectors  (12)
	STA	mSECTOR$COUNT
	LXI	H, CPMLDR$ADDRESS ;DMA address where the CPMLDR resides in RAM (100H)
	SHLD	mDMA

NextRCPM:
	CALL	wrlba		;Update LBA on drive
	CALL	DISPLAYposition	;Display current track, sector, head
	CALL	ZCRLF
	
	LHLD	mDMA
	CALL	READSECTOR	;Read a sector
	SHLD	mDMA

	LDA	mSECTOR$COUNT
	DCR	A
	STA	mSECTOR$COUNT
	JZ	LOAD$DONE

	LHLD	mSEC
	INX	H
	SHLD	mSEC		;Stay on track 0 in this special case
	JMP	NextRCPM

LOAD$DONE:
	MVI	E, REGstatus	;Check the R/W status when done
	CALL	IDErd8D
	DB	0CBH, 0*8+D+40H	;BIT  0, D
	JNZ	CPMLoadErr	;Zero if no errors
	LXI	H, CPMLDR$ADDRESS
	MOV	A, M
	CPI	31H		;EXPECT TO HAVE 31H @80H IE. LD SP,80H
	JNZ	CPMLoadErr1	;Zero if no errors
	
	LXI	D, MOVE$REQUEST	;Ask if we can move data to 100H
	CALL	PSTRING
	CALL	ZCI
	CALL	UPPER
	CPI	'Y'
	JNZ	MAINLOOP
	
	LXI	H, CPM$MOVE$CODE ;Need to move code out of the way.
	LXI	D, 0H
	LXI	B, (CPM$MOVE$CODE$END-CPM$MOVE$CODE)
	DB	0EDH, 0B0H	;LDIR
	JMP	0H		;Now jump here to move the CPMLDR (@3000H) to 100H
	
CPMLoadErr1:
	LXI	D, CPM$ERROR1	;Drive data error
	CALL	PSTRING
	JMP	MAINLOOP
	
CPMLoadErr:
	LXI	D, CPM$ERROR	;Drive Read Error
	CALL	PSTRING
	JMP	MAINLOOP

N$RD$SEC:			;Read N sectors 
	LXI	D, ReadN$MSG	;No check for possible high RAM (CPM) overwrite
	CALL	PSTRING
	CALL	GETHEX
	JC	MAINLOOP	;Abort if ESC (C flag set)
	
	STA	mSECTOR$COUNT	;Store sector count
	LXI	H, buffer	;Point to buffer
	SHLD	mDMA

NextRSec:	
	LXI	D, ReadingN$MSG
	CALL	PSTRING
	CALL	wrlba		;Update LBA on drive
	CALL	DISPLAYposition	;Display current track, sector, head
	
	LHLD	mDMA
	CALL	READSECTOR
	SHLD	mDMA

	LDA	mSECTOR$COUNT
	DCR	A
	STA	mSECTOR$COUNT
	JZ	MAINLOOP
	
	LHLD	mSEC
	MOV	A, L
	CALL	CHK$SEC		;Compare A to MAXSEC
	JZ	NextRZero	;Already at max, reset to 0
	INX	H		;Otherwise, on to next sector
	SHLD	mSEC	
	JMP	NextRSec

NextRZero:
	LXI	H, 0		;Back to CPM sector 0
	SHLD	mSEC
	LHLD	mTRK		;Bump to next track
	INX	H
	SHLD	mTRK
	MOV	A, L		;0-FFH tracks (only)
	ORA	A		;Set condition code for A (least 8 bits of track)
	JNZ	NextRSec
	
	LXI	D, AtEnd	;Tell us we are at end of disk
	CALL	PSTRING
	JMP	MAINLOOP

N$WR$SEC:			;Write N sectors 
	LXI     D, msgsure	;Are you sure?
	CALL	PSTRING
	CALL	ZCI
	CALL	UPPER
	CPI	'Y'
	JNZ	main2c

	LXI	D, WriteN$MSG
	CALL	PSTRING
	CALL	GETHEX
	JC	MAINLOOP	;Abort if ESC (C flag set)

	STA	mSECTOR$COUNT	;Store sector count
	LXI	H, buffer	;Point to buffer
	SHLD	mDMA

NextWSec:	
	LXI	D, WritingN$MSG
	CALL	PSTRING
	CALL	wrlba		;Update LBA on drive
	CALL	DISPLAYposition	;Display current track, sector, head
	
	LHLD	mDMA		;Actully, Sector/track values are already updated
	CALL	WRITESECTOR	;in wrlba, but WRITESECTOR is used in multiple places.
	SHLD	mDMA		;A repeat does no harm -- speed is not an issue here

	LDA	mSECTOR$COUNT
	DCR	A
	STA	mSECTOR$COUNT
	JZ	MAINLOOP
	
	LHLD	mSEC
	MOV	A, L
	CALL	CHK$SEC		;Compare sector to MAXSEC
	JZ	NextWZero	;Already at max sector - reset to 0
	INX	H
	SHLD	mSEC	
	JMP	NextWSec

NextWZero:
	LXI	H, 0		;Back to CPM sector 0
	SHLD	mSEC
	LHLD	mTRK		;Bump to next track
	INX	H
	SHLD	mTRK
	MOV	A, L		;0-FFH tracks (only)
	ORA	A
	JNZ	NextWSec
	
	LXI	D, AtEnd	;Tell us we are at end of disk
	CALL	PSTRING
	JMP	MAINLOOP

FORMAT:				;Format (Fill sectors with E5)
	LXI	D, FORMAT$MSG
	CALL	PSTRING
	LXI     D, msgsure	;Are you sure?
	CALL	PSTRING
	CALL	ZCI
	CALL	UPPER
	CPI	'Y'
	JNZ	MAINLOOP
	LXI	H, buffer	;Fill buffer with E5's (512 of them)
	MVI	B, 0
Fill0:	MVI	A, 0E5H		;<-- Sector fill character (E5 for CPM)
	MOV	M, A
	INX	H
	MOV	M, A
	INX	H
	DCR	B
	JNZ	Fill0
	CALL	ZCRLF

NEXT$FORMAT:
	LXI	H, buffer
	SHLD	mDMA
	CALL	WRITESECTOR	;Will return error if there was one
	JZ	main9b		;Z means the sector write was OK
	CALL	ZCRLF
	JMP	MAINLOOP
main9b:	CALL	ZEOL		;Clear line cursor is on
	CALL	DISPLAYposition	;Display actual current track, sector and head
	CALL	ZCSTS		;Any keyboard character will stop display
	CPI	01H		;CPM Says something there
	JNZ	WRNEXTSEC1
	CALL	ZCI		;Flush character
	LXI	D, CONTINUE$MSG
	CALL	PSTRING
	CALL	ZCI
	CPI	ESC
	JZ	MAINLOOP
	CALL	ZCRLF
WRNEXTSEC1:
	LHLD	mSEC
	MOV	A, L		;Current sector to A
	CALL	CHK$SEC		;Are we already at max sector?
	JZ	NextFormatZero	;Yes - set back to 0
	INX	H		;No - bump the sector
	SHLD	mSEC		;0 to MAXSEC CPM Sectors
	JMP	NEXT$FORMAT

NextFormatZero:
	LXI	H, 0		;Back to CPM sector 0
	SHLD	mSEC
	LHLD	mTRK		;Get current track
	MOV	A, L		;Current track to A
	CPI	MAXTRK		;Is it already at max?
	JZ	NextFormatDone	;Yes - all done
	INX	H		;Bump to next track
	SHLD	mTRK
	JMP	NEXT$FORMAT

NextFormatDone:
	LXI	D, FormatDone	;Tell us we are all done.
	CALL	PSTRING
	JMP	MAINLOOP
				
BACKUP:				;Backup the CPM partition to another area
	LXI	D, CopyMsg
	CALL	PSTRING
	CALL	ZCI
	CALL	UPPER
	CPI	'Y'
	JNZ	MAINLOOP
	
	LXI	H, 0		;Start with CPM sector 0
	SHLD	mSEC
	SHLD	mSEC1
	SHLD	mSEC2		;and on second partition
	SHLD	mTRK		;and track 0
	SHLD	mTRK1
	LXI	H, MAXTRK+1	;Start of backup partition
	SHLD	mTRK2 

	CALL	ZCRLF
	CALL	ZCRLF
	
NextCopy1:	
	CALL	ZEOL		;Clear line cursor is on
	LXI	D, RBackup$MSG	;for each track update display
	CALL	PSTRING
	LDA	mTRK1+1		;High TRK byte
	CALL	PHEX
	LDA	mTRK1		;Low TRK byte
	CALL	PHEX
	LXI	D, WBackup$MSG
	CALL	PSTRING
	LDA	mTRK2+1		;High TRK byte
	CALL	PHEX
	LDA	mTRK2		;Low TRK byte
	CALL	PHEX
	LXI	D, H$Msg
	CALL	PSTRING

NextCopy:	
	LDA	mSEC1
	STA	mSEC
	LHLD	mTRK1
	SHLD	mTRK
	CALL	wrlba		;Update LBA on source drive

	LXI	H, buffer	;Point to buffer
	SHLD	mDMA
	CALL	READSECTOR	;Get sector data to buffer
	
	LDA	mSEC2
	STA	mSEC
	LHLD	mTRK2
	SHLD	mTRK
	CALL	wrlba		;Update LBA on target drive
	
	LXI	H,buffer	;Point to buffer
	SHLD	mDMA
	CALL	WRITESECTOR	;Write buffer data to sector
	
	CALL	ZCSTS		;Any keyboard character will stop display
	CPI	01H		;CPM Says something there
	JNZ	BKNEXTSEC1
	CALL	ZCI		;Flush character
	LXI	D, CONTINUE$MSG
	CALL	PSTRING
	CALL	ZCI
	CPI	ESC
	JZ	MAINLOOP

BKNEXTSEC1:
	LHLD	mSEC
	MOV	A, L		;Sector number in A
	CALL	CHK$SEC		;Check Sector is not at max
	JZ	BKNEXTZERO	;It is at max already
	INX	H		;Otherwise, bump sector
	SHLD	mSEC1
	SHLD	mSEC2	
	JMP	NextCopy

BKNEXTZERO:
	LXI	H, 0		;Back to CPM sector 0
	SHLD	mSEC1
	SHLD	mSEC2
	
	LHLD	mTRK1		;Get current track
	MOV	A, L		;Into A
	CPI	MAXTRK		;Already at max?
	JZ	BKNextDone	;If so, we are done
	INX	H
	SHLD	mTRK1
	
	LHLD	mTRK2		;Bump to next track
	INX	H
	SHLD	mTRK2
	JMP	Nextcopy1
	
BKNextDone:
	LXI	D, BackupDone	;Tell us we are all done.
	CALL	PSTRING
	JMP	MAINLOOP

RESTORE:			;Restore disk from backup partition
	LXI	D, RestoreMsg
	CALL	PSTRING
	CALL	ZCI
	CALL	UPPER
	CPI	'Y'
	JNZ	MAINLOOP
	
	LXI	H, 0		;Start with CPM sector 0
	SHLD	mSEC
	SHLD	mSEC1
	SHLD	mSEC2		;and on second partition
	SHLD	mTRK		;and track 0
	SHLD	mTRK1
	LXI	H, MAXTRK+1	;Start of backup partition
	SHLD	mTRK2 

	CALL	ZCRLF
	CALL	ZCRLF
	
NextRestore1:	
	CALL	ZEOL		;Clear line cursor is on
	LXI	D, RBackup$MSG	;for each track update display
	CALL	PSTRING
	LDA	mTRK2+1		;High TRK byte
	CALL	PHEX
	LDA	mTRK2		;Low TRK byte
	CALL	PHEX
	LXI	D, WBackup$MSG
	CALL	PSTRING
	LDA	mTRK1+1		;High TRK byte
	CALL	PHEX
	LDA	mTRK1		;Low TRK byte
	CALL	PHEX
	LXI	D,H$Msg
	CALL	PSTRING

NextRestore:	
	LDA	mSEC2		;Point to backup partition
	STA	mSEC
	LHLD	mTRK2
	SHLD	mTRK
	CALL	wrlba		;Update LBA on source drive

	LXI	H, buffer	;Point to buffer
	SHLD	mDMA
	CALL	READSECTOR	;Get sector data to buffer
	
	LDA	mSEC1
	STA	mSEC
	LHLD	mTRK1
	SHLD	mTRK
	CALL	wrlba		;Update LBA on target drive
	
	LXI	H, buffer	;Point to buffer
	SHLD	mDMA
	CALL	WRITESECTOR	;Write buffer data to sector
	
	CALL	ZCSTS		;Any keyboard character will stop display
	CPI	01H		;CPM Says something there
	JNZ	RESNEXTSEC1
	CALL	ZCI		;Flush character
	LXI	D, CONTINUE$MSG
	CALL	PSTRING
	CALL	ZCI
	CPI	ESC
	JZ	MAINLOOP

RESNEXTSEC1:
	LHLD	mSEC
	MOV	A, L		;Current sector in A
	CALL	CHK$SEC		;Is sector already at max?
	JZ	RESNextZero	;Yes - go to sector 0
	INX	H		;No - bump to next sector
	SHLD	mSEC1
	SHLD	mSEC2	
	JMP	NextRestore

RESNextZero:
	LXI	H, 0		;Back to CPM sector 0
	SHLD	mSEC1
	SHLD	mSEC2
	
	LHLD	mTRK1		;Get current Track
	MOV	A, L
	CPI	MAXTRK		;Are we already at max?
	JZ	ResNextDone	;If so, we are done
	INX	H		;Bump to next track
	SHLD	mTRK1
	
	LHLD	mTRK2		;Bump to next track
	INX	H
	SHLD	mTRK2
	JMP	NextRestore1

ResNextDone:	
	LXI	D, RestoreDone	;Tell us we are all done.
	CALL	PSTRING
	JMP	MAINLOOP

ERROR:	LXI     D, msgErr	;Command error msg
	CALL	PSTRING
	JMP	MAINLOOP

COPY$D0D1:			;Copy drive 0 to drive 1
	LXI	D, DiskCopyMsg
	CALL	PSTRING
	CALL	ZCI
	CALL	UPPER
	CPI	'Y'
	JNZ	MAINLOOP
	
	LXI	H, 0		;Start with CPM sector 0
	SHLD	mSEC
	SHLD	mTRK		;and track 0
	
	CALL	ZCRLF
	CALL	ZCRLF
	
NextDCopy1:	
	CALL	ZEOL		;Clear line cursor is on
	LXI	D, CopyTrk$MSG	;for each track update display
	CALL	PSTRING
	LDA	mTRK+1		;High TRK byte
	CALL	PHEX
	LDA	mTRK		;Low TRK byte
	CALL	PHEX
	LXI	D, H$Msg
	CALL	PSTRING

NextDCopy:	
	CALL	SELECT0		;Select drive 0
	CALL	wrlba		;Update LBA on drive 0
	LXI	H, buffer	;Point to buffer
	SHLD	mDMA
	CALL	READSECTOR	;Get sector data from drive 0 to buffer
	
	CALL	SELECT1		;Select drive 1
	CALL	wrlba		;Update LBA on drive 1
	LXI	H, buffer	;Point to buffer
	SHLD	mDMA
	CALL	WRITESECTOR	;Write buffer data to sector on drive 1
	
	CALL	ZCSTS		;Any keyboard character will stop display
	CPI	01H		;CPM says something is there
	JNZ	BK$D$NEXTSEC1
	CALL	ZCI		;Flush character
	LXI	D, CONTINUE$MSG
	CALL	PSTRING
	CALL	ZCI
	CPI	ESC
	JNZ	BK$D$NEXTSEC1

	CALL	SELECT0		;Select drive 0
	JMP	MAINLOOP

BK$D$NEXTSEC1:
	LHLD	mSEC
	MOV	A, L		;Current sector to A
	CALL	CHK$SEC		;Are we already at max?
	JZ	BK$D$NextZero	;Yes - set sector to 0
	INX	H		;No - continue on
	SHLD	mSEC
	JMP	NextDcopy

BK$D$NextZero:
	LXI	H, 0		;Back to CPM sector 0
	SHLD	mSEC
	
	LHLD	mTRK		;Get Current Track
	MOV	A, L
	CPI	MAXTRK		;Are we already at max track?
	JZ	BK$D$NextDone	;Yes - done
	INX	H		;No - bump to next track
	SHLD	mTRK
	JMP	NextDcopy1

BK$D$NextDone:	
	LXI	D, CopyDone	;Tell us we are all done
	CALL	PSTRING
	CALL	SELECT0		;Select drive 0
	JMP	MAINLOOP

CMP$D0D1:			;Verify Drive 0 = 1
	LXI	D, DiskVerifyMsg
	CALL	PSTRING
	
	LXI	H, 0		;Start with CPM sector 0
	SHLD	mSEC
	SHLD	mTRK		;and track 0
	
	CALL	ZCRLF
	CALL	ZCRLF
	
NextVCopy1:	
	CALL	ZEOL		;Clear line cursor is on
	LXI	D, VerifyTrk$MSG ;for each track update display
	CALL	PSTRING
	LDA	mTRK+1		;High TRK byte
	CALL	PHEX
	LDA	mTRK		;Low TRK byte
	CALL	PHEX
	LXI	D, H$Msg
	CALL	PSTRING

NextVCopy:	
	CALL	SELECT0		;Select drive 0
	CALL	wrlba		;Update LBA on 0 drive
	LXI	H, buffer	;Point to buffer
	SHLD	mDMA
	CALL	READSECTOR	;Get sector data from buffer 0 drive
	
	CALL	SELECT1		;Select drive 1
	CALL	wrlba		;Update LBA on 1 drive
	LXI	H, buffer2	;Point to buffer2
	SHLD	mDMA
	CALL	READSECTOR	;Read buffer data from sector of drive 1
	
	LXI	B, 512		;Now check both buffers are identical
	LXI	H, buffer
	LXI	D, buffer2
NEXTV:	LDAX	D
	CMP	M		;Is [DE] = [HL]?
	JNZ	COMPARE$ERROR
	INX	H
	INX	D
	DCX	B
	MOV	A,C
	ORA	B
	JZ	VERIFY$OK
	JMP	NEXTV

COMPARE$ERROR:
	LXI	D, VERIFY$ERR	;Indicate an error
	CALL	PSTRING
	LDA	mTRK+1		;High TRK byte
	CALL	PHEX
	LDA	mTRK		;Low TRK byte
	CALL	PHEX
	LXI	D, SEC$Msg
	CALL	PSTRING
	LDA	mSEC		;Sector byte
	CALL	PHEX
	LXI	D, H$Msg
	CALL	PSTRING
	JMP	VER$OK1
	
VERIFY$OK:
	CALL	ZCSTS		;Any keyboard character will stop display
	CPI	01H		;CPM says something is there
	JNZ	BK$V$NEXTSEC1
	CALL	ZCI		;Flush character
VER$OK1:
	LXI	D, CONTINUE$MSG
	CALL	PSTRING
	CALL	ZCI
	CPI	ESC
	JNZ	BK$V$NEXTSEC1
	CALL	SELECT0		;Select drive 0
	JMP	MAINLOOP

BK$V$NEXTSEC1:
	LHLD	mSEC
	MOV	A, L		;Current sector to A
	CALL	CHK$SEC		;Is it already at max
	JZ	BK$V$NEXTZero	;Yes - back to sector 0
	INX	H		;No - bump to next sector
	SHLD	mSEC
	JMP	NextVCopy

BK$V$NEXTZero:
	LXI	H, 0		;Back to CPM sector 0
	SHLD	mSEC
	
	LHLD	mTRK		;Get Current track
	MOV	A, L
	CPI	MAXTRK		;Are we already at max
	JZ	BK$V$NEXTDone	;Yes - all done
	INX	H		;no - bump to next track
	SHLD	mTRK
	JMP	NextVCopy1

BK$V$NextDone:	
	LXI	D, VerifyDone	;Tell us we are all done.
	CALL	PSTRING
	CALL	SELECT0		;Select drive 0
	JMP	MAINLOOP

;----------------------------- SUPPORT FUNCTIONS ------------------------------	
				
driveid:CALL	IDEwaitnotbusy	;Retrieve drive info
	RC
	MVI	D, COMMANDid
	MVI	E, REGcommand
	CALL	IDEwr8D		;Issue the ID command
  IF VERBOSE
	LXI	D, READING$ID
	CALL	PSTRING
	LXI	D, DISKSTATUS	;Print status message
	CALL	PSTRING
  ENDIF
	MVI	E, REGstatus	;Get status after ID command
	CALL	IDErd8D		;Check Status (info in [D])
  IF VERBOSE
	MOV	A, D
	CALL	PHEX		;Print status
	CALL	ZPERCRLF
  ENDIF
	CALL	IDEwaitdrq	;Wait for Busy=0, DRQ=1
	JC	SHOWerrors
	CALL	CLEAR$ID$BUFFER	;Clear ID Buffer
  IF VERBOSE
	LXI	D, GETTING$ID
	CALL	PSTRING
  ENDIF
	MVI	B, 0		;256 words
	LXI     H, IDbuffer	;Store data here
	CALL	MoreRD16	;Get 256 words of data from REGdata port to [HL]
	RET

spinup:				;Start the drive
	MVI	D, COMMANDspinup
spup2:	MVI	E, REGcommand
	CALL	IDEwr8D
	CALL	IDEwaitnotbusy
	JC	SHOWerrors
	ORA	A		;Clear carry
	RET
				
spindown:			;Tell the drive to spin down
	CALL	IDEwaitnotbusy
	JC	SHOWerrors
	MVI	D,COMMANDspindown
	JMP	spup2

SequentialReads:		;Sequentially read sectors from current position
	CALL	IDEwaitnotbusy
	JC	SHOWerrors
	CALL	ZCRLF

NEXTSEC:
	LXI	H, buffer	;Point to buffer
	SHLD	mDMA

	CALL	READSECTOR	;Errors will show in READSECTOR
	JZ	SEQOK
	LXI	D, CONTINUE$MSG
	CALL	PSTRING
	CALL	ZCI
	CPI	ESC		;Abort if ESC
	RZ
	
SEQOK:	CALL	ZEOL		;Clear line cursor is on
	CALL	DISPLAYposition	;Display current track, sector and head

	LXI	H, buffer	;Point to buffer
	SHLD	mDMA

	LDA	mDisplayFlag	;Do we have display flag on or off
	ORA	A		;NZ = on 
	CNZ	HEXDUMP
	CALL	ZCRLF
	CALL	ZCRLF
	CALL	ZCRLF

	CALL	ZCSTS		;Any keyboard character will stop display
	CPI	01H		;CPM Says something there
	JNZ	NEXTSEC1
	CALL	ZCI		;Flush character
	LXI	D, CONTINUE$MSG
	CALL	PSTRING
	CALL	ZCI
	CPI	ESC
	RZ
	CALL	ZCRLF

NEXTSEC1:
	LHLD	mSEC
	MOV	A, L		;Current sector to A
	CALL	CHK$SEC		;Are we already at max sector?
	JZ	NextSecZero	;Yes - back to sector 0
	INX	H		;No - bump to next sector
	SHLD	mSEC	
	JMP	NEXTSEC

NextSecZero:
	LXI	H, 0		;Back to CPM sector 0
	SHLD	mSEC
	LHLD	mTRK		;Bump to next track
	INX	H
	SHLD	mTRK
	JMP	NEXTSEC		;Note will go to last track on disk unless stopped

PRN$0$INFO:			;Print Drive 0 identification info
	LXI	D, DRIVE0$INFO
	CALL	PSTRING
	CALL	REM$DRV
	CALL	SELECT0
	CALL	PRN$DRV$INFO
	CALL	IDEinit
	CALL	RET$DRV
	JMP	MAINLOOP

PRN$1$INFO:			;Print Drive 1 identification info
	LXI	D, DRIVE1$INFO
	CALL	PSTRING
	CALL	REM$DRV
	CALL	SELECT1
	CALL	PRN$DRV$INFO
	CALL	IDEinit
	CALL	RET$DRV
	JMP	MAINLOOP

PRN$DRV$INFO:			;Print drive identification info			
	CALL	driveid
	JZ	PRN$DETAILS
	LXI	D, ID$ERROR	;On error, display message
	CALL	PSTRING
	RET

PRN$DETAILS:			;Get Sector Count
	LXI     H, IDbuffer + 12
	MOV	A, M		;(High Byte)
	ORA	A
	JNZ	PRN$DET2
	INX	H
	MOV	A, M		;(Low Byte)
	ORA	A
	JNZ	PRN$DET2	;Looks like we have a valid IDE drive
	
	LXI	D, BAD$DRIVE	;Zero sectors means something's wrong
	CALL	PSTRING
	RET

PRN$DET2:			;Print drive info
	LXI     D, msgmdl	;Drive name	
	CALL	PSTRING
	LXI     H, IDbuffer + 54
	MVI	B, 20		;Character count in words
	CALL	printSwap	;Print [HL], [B] X 2 characters
	CALL	ZCRLF

	LXI     D, msgsn	;Serial number
	CALL	PSTRING
	LXI     H, IDbuffer + 20
	MVI	B, 10		;Character count in words
	CALL	printText
	CALL	ZCRLF

	LXI     D, msgrev	;Firmware revision string
	CALL	PSTRING
	LXI     H, IDbuffer + 46
	MVI	B, 4		;Character count in words
	CALL	printSwap
	CALL	ZCRLF

	LXI     D, msgcy	;Drive specs (cyl/hd/sect)
	CALL	PSTRING
	LXI     H, IDbuffer + 2
	CALL	printparm
	LXI     D, msghd
	CALL	PSTRING
	LXI     H, IDbuffer + 6
	CALL	printparm
	LXI     D, msgsc
	CALL	PSTRING
	LXI     H, IDbuffer + 12
	CALL	printparm
	CALL	ZCRLF

	LXI	D, msgLBAsup1	;First part of LBA support message
	CALL	PSTRING
	LDA	IDbuffer+98+1	;Bits 15-10 reserved, 9 LBA, 8 DMA
	ANI	02H
	JNZ	PRN$SUP		;LBA is supported
	LXI	D, msgLBAnot	;LBA is not supported
	CALL	PSTRING

PRN$SUP:
	LXI	D, msgLBAsup2
	CALL	PSTRING
	RET

DISPLAYposition:		;Display current track, sector & head position
	LXI     D, msgCPMTRK	;Display in LBA format
	CALL	PSTRING		;---- CPM FORMAT ----
	LDA	mTRK+1		;High TRK byte
	CALL	PHEX
	LDA	mTRK		;Low TRK byte
	CALL	PHEX
		
	LXI     D, msgCPMSEC
	CALL	PSTRING		;SEC = (16 bits)
	LDA	mSEC+1		;High Sec
	CALL	PHEX
	LDA	mSEC		;Low sec
	CALL	PHEX
				;---- LBA FORMAT ----
	LXI     D, msgLBA
	CALL	PSTRING		;LBA = 00 ("Heads" = 0 for these drives)
	LDA	mDRIVE$TRK+1	;High "cylinder" byte
	CALL	PHEX
	LDA	mDRIVE$TRK	;Low "cylinder" byte
	CALL	PHEX	
	LDA	mDRIVE$SEC
	CALL	PHEX
	LXI     D, MSGBracket
	CALL	PSTRING		
	RET

printText:			;Print text up to [B] (16-bit word) byte-pairs
	MOV	C, M		;Text is contiguous byte array
	CALL	ZCO	
	INX	H
	MOV	C, M
	CALL	ZCO	
	INX	H
	DCR	B
	JNZ	printText
	RET

printSwap:			;Print text up to [B] (16-bit word) byte-pairs	
	INX	H		;Swap byte pairs - low byte, high byte
	MOV	C, M
	CALL	ZCO	
	DCX	H
	MOV	C, M
	CALL	ZCO
	INX	H
	INX	H
	DCR	B
	JNZ	printSwap
	RET

ZCRLF:
	PUSH	PSW
	MVI	C, CR
	CALL	ZCO
	MVI	C, LF
	CALL	ZCO
	POP	PSW
	RET

ZPERCRLF:
	PUSH	PSW
	MVI	C, PERIOD
	CALL	ZCO
	MVI	C, CR
	CALL	ZCO
	MVI	C, LF
	CALL	ZCO
	POP	PSW
	RET

ZEOL:				;CR and clear current line
	MVI	C, CR
	CALL	ZCO
	MVI	C, CLEAR
	CALL	ZCO
	RET

ZCSTS:
  IF CPM
	PUSH	B
	PUSH	D
	PUSH	H
	MVI	C, CONST
	CALL	BDOS		;Returns with 1 in [A] if character at keyboard
	POP	H
	POP	D
	POP	B
	CPI	1
	RET
  ENDIF
  IF NOT CPM
	IN	CONI		;Get Character in [A]
	ANI	02H
	RZ
	MVI	A, 01H
	ORA	A
	RET
  ENDIF

ZCO:				;Write character that is in [C]
  IF CPM
	PUSH	PSW
	PUSH	B
	PUSH	D
	PUSH	H
	MOV	E, C
	MVI	C, WRCON
	CALL	BDOS
	POP	H
	POP	D
	POP	B
	POP	PSW
	RET
  ENDIF
  IF NOT CPM
	PUSH	PSW	
ZCO1:	IN   	CONI		;Show Character
	ANI	04H
	JZ	ZCO1
	MOV	A, C
	OUT	CONO
	POP	PSW
	RET
  ENDIF

ZCI:				;Return keyboard character in [A]
  IF CPM
	PUSH	B
	PUSH	D
	PUSH	H
	MVI	C, RDCON
	CALL	BDOS
	POP	H
	POP	D
	POP	B
	RET
  ENDIF
  IF NOT CPM
ZCI1:	IN	CONI		;Get Character in [A]
	ANI	02H
	JZ	ZCI1
	IN	CONO
	RET
  ENDIF

;------------------------------------------------------------------------------	
;Print a string in [DE] up to '$'
;------------------------------------------------------------------------------	

PSTRING:
  IF CPM
	MVI	C, PRINT
	JMP	BDOS		;PRINT MESSAGE
  ENDIF
  IF NOT CPM
	PUSH	B
	PUSH	D
	PUSH	H
	XCHG
PSTRX:	MOV	A, M
	CPI	'$'
	JZ	DONEP
	MOV	C, A
	CALL	ZCO
	INX	H
	JMP	PSTRX
DONEP:	POP	H
	POP	D
	POP	B
	RET
  ENDIF

SHOWerrors:
  IF NOT DEBUG
	ORA	A		;Set NZ flag
	STC			;Set Carry Flag
	RET
  ENDIF
  IF DEBUG
	CALL	ZCRLF
	MVI	E, REGstatus	;Get status in status register
	CALL	IDErd8D
	MOV	A, D
	ANI	1H
	JNZ	MoreError	;Go to  REGerr register for more info
				;All OK if 01000000
	PUSH	PSW		;Save for return below
	ANI	80H
	JZ	NOT7
	LXI	D, DRIVE$BUSY	;Drive Busy (bit 7) stuck high 
	CALL	PSTRING
	JMP	DONEERR
NOT7:	ANI	40H
	JNZ	NOT6
	LXI	D, DRIVE$NOT$READY ;Drive Not Ready (bit 6) stuck low 
	CALL	PSTRING
	JMP	DONEERR
NOT6:	ANI	20H
	JNZ	NOT5
	LXI	D, DRIVE$WR$FAULT ;Drive write fault
	CALL	PSTRING
	JMP	DONEERR
NOT5	LXI	D, UNKNOWN$ERROR
	CALL	PSTRING
	JMP	DONEERR

MoreError:			;Bit 0 of the status register indicates problem
	MVI	E, REGerr	;Get error code in REGerr
	CALL	IDErd8D
	MOV	A, D
	PUSH	PSW

	ANI	10H
	JZ	NOTE4
	LXI	D, SEC$NOT$FOUND
	CALL	PSTRING
	JMP	DONEERR

NOTE4:	ANI	80H
	JZ	NOTE7
	LXI	D, BAD$BLOCK
	CALL	PSTRING
	JMP	DONEERR
NOTE7:	ANI	40H
	JZ	NOTE6
	LXI	D, UNRECOVER$ERR
	CALL	PSTRING
	JMP	DONEERR
NOTE6:	ANI	4H
	JZ	NOTE2
	LXI	D, INVALID$CMD
	CALL	PSTRING
	JMP	DONEERR
NOTE2:	ANI	2H
	JZ	NOTE1
	LXI	D, TRK0$ERR
	CALL	PSTRING
	JMP	DONEERR
NOTE1:	LXI	D, UNKNOWN$ERROR1
	CALL	PSTRING
	JMP	DONEERR

DONEERR:POP	PSW
	PUSH	PSW
	CALL	ZBITS
	CALL	ZCRLF
	POP	PSW
	ORA	A		;Set Z flag
	STC			;Set Carry flag
	RET
  ENDIF

;------------------------------------------------------------------------------	
;Print a 16-bit number in RAM located @ [HL], low-byte first for Drive ID 
;------------------------------------------------------------------------------	

printparm:
	INX	H	;Index to high byte first
	MOV	A, M
	CALL	PHEX
	DCX	H	;Now low byte
	MOV	A, M
	CALL	PHEX
	RET

;------------------------------------------------------------------------------	
;Print an 8 bit number located in [A] 
;------------------------------------------------------------------------------	

PHEX:	PUSH	PSW
	PUSH	B
	PUSH	PSW
	RRC
	RRC
	RRC
	RRC
	CALL	ZCONV
	POP	PSW
	CALL	ZCONV
	POP	B
	POP	PSW
	RET

ZCONV:	ANI	0FH		;HEX to ASCII and print it
	ADI	90H
	DAA
	ACI	40H
	DAA
	MOV	C, A
	CALL	ZCO
	RET

;------------------------------------------------------------------------------	
;Display binary in [A] 
;------------------------------------------------------------------------------	

ZBITS:	PUSH	PSW
	PUSH	B
	PUSH	D
	MOV	E, A		
	MVI	B, 8
BQ2:	DB	0CBH, 23H	;SLA A, E
	MVI	A, 18H
	ADC	A
	MOV	C, A
	CALL	ZCO
	DCR	B
	JNZ	BQ2
	POP	D
	POP	B
	POP	PSW
	RET

ghex32lba:			;Convert CPM Track & Sector to LBA format
	LXI     D,ENTER$SECL	;Enter sector number
	CALL	PSTRING
	CALL	GETHEX		;Get 2 HEX digits
	RC
	STA	mSEC
	CALL	ZCRLF

	LXI	D, ENTER$TRKH	;Enter high byte track number
	CALL	PSTRING
	CALL	GETHEX		;Get 2 HEX digits
	RC
	STA	mTRK+1
	CALL	ZCRLF

	LXI     D, ENTER$TRKL	;Enter low byte track number
	CALL	PSTRING
	CALL	GETHEX		;Get 2 more HEX digits
	RC
	STA	mTRK
	CALL	ZCRLF
	XRA	A
	ORA	A		;To return NC
	RET

;------------------------------------------------------------------------------	
;Get a HEX character from the keyboard and echo it 
;------------------------------------------------------------------------------	

GETHEX:
	CALL	GETCMD		;Get character
	CPI	ESC
	JZ	HEXABORT
	CPI	'/'		;check 0-9, A-F
	JC	HEXABORT
	CPI	'F'+1
	JNC	HEXABORT
	CALL	ASBIN		;Convert to binary
	RLC			;Shift to high nibble
	RLC
	RLC
	RLC
	MOV	B, A		;Store it
	CALL	GETCMD		;Get 2nd character from keyboard & ECHO
	CPI	ESC
	JZ	HEXABORT
	CPI	'/'		;check 0-9, A-F
	JC	HEXABORT
	CPI	'F'+1
	JNC	HEXABORT
	CALL	ASBIN		;Convert to binary
	ORA	B		;add in the first digit
	ORA	A		;To return NC
	RET
HEXABORT:
	STC			;Set Carry flag 
	RET

;------------------------------------------------------------------------------	
;Get a character from the keyboard, convert to uppercase and echo it 
;------------------------------------------------------------------------------	

GETCMD:	CALL	ZCI		;Get character
	CALL	UPPER
	CPI	ESC
	RZ			;Don't echo an ESC
  IF NOT CPM
	PUSH	PSW		;Save state of registers
	PUSH	B
 	MOV	C, A
	CALL	ZCO		;Echo it
	POP	B
	POP	PSW		;Retrieve original state
  ENDIF
	RET

;------------------------------------------------------------------------------	
;Convert lowercase to uppercase 
;------------------------------------------------------------------------------	

UPPER:	CPI	'a'		;Must be >= lowercase a
	RC			;else return as-is
	CPI	'z'+1		;Must be <= lowercase z
	RNC			;else return as-is
	SUI	'a'-'A'		;Subtract lowercase bias
	RET

ASBIN:	SUI	30H 		;ASCII to binary conversion
	CPI	0AH 
	RM
	SUI	07H 
	RET

;------------------------------------------------------------------------------	
;Print a hexdump of the data in the 512 byte buffer starting at [HL]
;------------------------------------------------------------------------------	

HEXDUMP:
	PUSH	PSW		;Save everything
	PUSH	B
	PUSH	D			 
	PUSH	H
	
	CALL	ZCRLF		;CR/LF first
	MVI	D, 32		;Print 32 lines total
	MVI	B, 16		;16 characters across
	SHLD	mStartLineHex	;Save buffer location for ASCII display below
	LXI	H, 0
	SHLD	mBYTE$COUNT
	
SF172:	CALL	ZCRLF
	LHLD	mBYTE$COUNT
	MOV	A, H
	CALL	PHEX		;Print byte count in sector
	MOV	A, L
	CALL	PHEX		
	PUSH	D
	LXI	D, 16
	DAD	D
	POP	D
	SHLD	mBYTE$COUNT	;Store for next time
	CALL	BLANK
	LHLD	mStartLineHex
	SHLD	mStartLineASCII	;Store for ASCII display below

SF175:	MOV	A, M
	CALL	LBYTE		;Display [A] on CRT/LCD
	INX	H
	DCR	B
	JNZ	SF175
	SHLD	mStartLineHex	;Save for next line later
	CALL	ShowAscii	;Now translate to ASCII and display
	MVI	B, 16		;16 characters across for next line
	DCR	D
	JNZ	SF172		;Have we done all 32 lines

	CALL	ZCRLF
	POP	H		;Get back original registers
	POP	D
	POP	B
	POP	PSW
	RET
	
ShowAscii:			;Show as ASCII info
	LHLD	mStartLineASCII
	MVI	B, 16		;16 ASCII characters across
XF172:	CALL	BLANK		;Send a space character
	CALL	BLANK
XF175:	MOV	A, M
	ANI	7FH
	CPI	' ' 		;Filter out control characters
	JNC	XT33
XT22:	MVI	A, '.'
XT33:	CPI	07CH
	JNC	XT22
	MOV	C, A		;Setup to send
	PUSH	B
	CALL	ZCO
	POP	B
	INX	H		;Next position in buffer
	DCR	B
	JNZ	XF175
	RET

BLANK:	PUSH	B
	PUSH	H
	MVI	C, ' '
	CALL	ZCO
	POP	H
	POP	B
	RET

LBYTE:	PUSH	PSW
	RRC
	RRC
	RRC
	RRC
	CALL	SF598
	POP	PSW

SF598:	CALL	ZCONV
	RET

CHK$SEC:			;Compare current "CPM" sector to max "CPM" sector
	PUSH	B		;Save
	MOV	C, A		;C <- Current Sector
	MVI	B, MAXSEC	;Retrieve max sector number
	MOV	A, C		;Get current sector back in A for compare (and return with it in A)
	CMP	B		;Current : Max
	POP	B
	RET			;Return with compare status. (Carry => Max > Current)

;------------------------------------------------------------------------------	
;IDE Drive BIOS Routines written in a format that can be used directly with CPM
;------------------------------------------------------------------------------	

IDEinit:			;Initialize the 8255 and drive then do a hard reset
	PUSH	B
	PUSH	D
  IF VERBOSE
	LXI	D, INITDRIVE
	CALL	PSTRING
  ENDIF
	MVI	A, READcfg8255	;Config 8255 chip (10010010B)
	OUT	IDEportCtrl	;for READ mode
				
	MVI	A, IDErstline	;Hard reset the disk drive
	OUT	IDEportC	;Some CF cards are sensitive to reset pulse width
	MVI	B, 20H		;Symptom is incorrect data back from a sector read

ResetDelay:
	DCR	B
	JNZ	ResetDelay	;Delay (reset pulse width)

	XRA	A
	OUT	IDEportC	;No control lines asserted (just bit 7 of port C)
	CALL	DELAY$SHORT	;Short Delay

	CALL	IDEwaitnotbusy	;Wait for drive
	JC	WaitInitErr

	MVI	D,11100000b	;Data for IDE SDH reg (512byte, LBA, single drive, hd 0)
				;For Trk, Sec, Head (non LBA) use 10100000

	MVI	E,REGshd	;00001110,(0EH) for CS0,A2,A1,  
	CALL	IDEwr8D		;Write byte to select the MASTER device

	MVI	B, 02H		;Delay time for hard disks to get up to speed (2s)

WaitInit:
  IF VERBOSE
	LXI	D, DISKSTATUS	;Print initialization status message
	CALL	PSTRING
  ENDIF
	MVI	E, REGstatus	;Get status after initilization
	CALL	IDErd8D		;Check Status (info in [D])
	MOV	A, D
  IF VERBOSE
	CALL	PHEX		;Print drive initialization status
	CALL	ZPERCRLF
  ENDIF
	ANI	80H
	JNZ	WaitInitL	;Need a longer wait...
	POP	D		;Restore registers
	POP	B
	RET			;Return. We'll check for errors when we get back
WaitInitL:
	MVI	A, 2
	CALL	DELAY$LONG	;Long delay, drive has to get up to speed
	DCR	B
	JNZ	WaitInit
	XRA	A
	DCR	A
	POP	D
	POP	B
	RET			;Return NZ. We'll check for errors when we get back
WaitInitErr:
	XRA	A
	DCR	A		;Return NZ (error)
	POP	D		;Restore Registers
	POP	B
	RET			;Return and check for errors there
	
DELAY$LONG:			;Long delay (Seconds)
	STA	mDELAYStore
	PUSH	B
	LXI	B, 0FFFFH
DELAY2:	LDA	mDELAYStore
DELAY1:	DCR	A
	JNZ	DELAY1
	DCX	B
	MOV	A, C
	ORA	B
	JNZ	DELAY2
	POP	B
	RET

DELAY$SHORT:			;Short delay (32ms)
	MVI	A, 40
DELAY3:	MVI	B, 0
M0:	DCR	B
	JNZ	M0
	DCR	A
	JNZ     DELAY3 
	RET

SELECT0:
	XRA	A		;Select drive 0
	STA	mCURRENT$DRIVE
	OUT	IDEDrive	
	RET

SELECT1:
	MVI	A, 1		;Select drive 1
	STA	mCURRENT$DRIVE
	OUT	IDEDrive	
	RET

REM$DRV:			;Remember last drive used
	LDA	mCURRENT$DRIVE
	STA	mLAST$DRIVE
	RET

RET$DRV:			;Return to last drive used
	LDA	mLAST$DRIVE
	STA	mCURRENT$DRIVE
	OUT	IDEDrive	
	RET

;------------------------------------------------------------------------------	
;Sector Read
;------------------------------------------------------------------------------	

READSECTOR:			;Read a sector, specified by the 3 bytes in LBA
				;Z on success, NZ call error routine if problem

	CALL	wrlba		;Tell which sector we want to read from.
				;Translate first in case of an error, otherewise 
				;we will get stuck on bad sector
 
	CALL	IDEwaitnotbusy	;Make sure drive is ready
	JC	SHOWerrors	;Returned with NZ set if error

	MVI	D, COMMANDread
	MVI	E, REGcommand
	CALL	IDEwr8D		;Send sec read command to drive.
	CALL	IDEwaitdrq	;Wait until it's got the data
	JC	SHOWerrors
		
	LHLD  	mDMA		;DMA address
	MVI	B, 0		;Read 512 bytes to [HL]

MoreRD16:
	MVI	A, REGdata	;REG register address
	OUT	IDEportC	

	ORI	IDErdline	;08H+40H, Pulse RD line
	OUT	IDEportC	

	IN	IDEportA	;Read the lower byte first
	MOV	M, A
	INX	H
	IN	IDEportB	;Then read the upper byte
	MOV	M, A
	INX	H
	
	MVI	A, REGdata	;Deassert RD line
	OUT	IDEportC
	DCR	B
	JNZ	MoreRD16

	MVI	E, REGstatus
	CALL	IDErd8D
	MOV	A, D
	ANI	1H
	CNZ	SHOWerrors	;If error display status
	RET

;------------------------------------------------------------------------------	
;Sector Write
;------------------------------------------------------------------------------	

WRITESECTOR:			;Write a sector, specified by the 3 bytes in LBA
				;Z on success, NZ to error routine if problem

	CALL	wrlba		;Tell which sector we want to read from.
				;Translate first in case of an error, otherewise 
				;we will get stuck on bad sector 

	CALL	IDEwaitnotbusy	;Make sure drive is ready
	JC	SHOWerrors

	MVI	D, COMMANDwrite
	MVI	E, REGcommand
	CALL	IDEwr8D		;Tell drive to write a sector
	CALL	IDEwaitdrq	;Wait unit it wants the data
	JC	SHOWerrors

	LHLD    mDMA
	MVI	B, 0

	MVI	A, WRITEcfg8255
	OUT	IDEportCtrl
	
WRSEC1:	MOV	A, M
	INX	H
	OUT	IDEportA	;Write the lower byte first
	MOV	A, M
	INX	H
	OUT	IDEportB	;Then high byte on B

	MVI	A, REGdata
	PUSH	PSW
	OUT	IDEportC	;Send write command
	ORI	IDEwrline	;Send WR pulse
	OUT	IDEportC
	POP	PSW
	OUT	IDEportC
	DCR	B
	JNZ	WRSEC1
	
	MVI	A, READcfg8255	;Set 8255 back to read mode
	OUT	IDEportCtrl	

	MVI	E, REGstatus
	CALL	IDErd8D
	MOV	A, D
	ANI	1H
	CNZ	SHOWerrors	;If error display status
	RET

;------------------------------------------------------------------------------	
;Write Logical Block Address (LBA) mode
;------------------------------------------------------------------------------	

wrlba:
	CALL	IDEwaitnotbusy	;Make sure drive isn't busy
	JC	SHOWErrors	;If error, display status	

	LHLD	mTRK		;Get the "CPM" requested track High & Low
	MOV	A, L		;Get Low byte of track
	RRC			;Get bottom two bits in high bits of A
	RRC
	ANI	0C0H		;Just what were the bottom two bits (now at the top)
	MOV	C, A		;Save in C
	LDA	mSEC		;Sector number in A
	ANI	03FH		;Take only bottom 6 bits
	ORA	C		;Add in top 2 bits of track
	STA	mDRIVE$SEC	;For diagnostic display only
	MOV	D, A		;Send info to the drive
	MVI	E, REGsector
	CALL	IDEwr8D

	MOV	A, L		;Get low byte of track again
	RRC
	RRC
	ANI	03FH
	MOV	C, A		;Save in C
	MOV	A, H		;Get high byte of track.
	RRC			;Rotate twice, leaving low 2 bits
	RRC			;In upper bits of A
	ANI	0C0H		;Mask all but the two bits we want
	ORA	C		;Add in the top 6 bits of the first track byte
	STA	mDRIVE$TRK
	MOV	D, A		;Send Low TRK#
	MVI	E, REGcylinderLSB
	CALL	IDEwr8D
	
	MOV	A, H		;Get high byte of track
	RRC			;Just the top 6 bits
	RRC
	ANI	03FH
	STA	mDRIVE$TRK+1
	MOV	D, A		;Send High TRK#
	MVI	E, REGcylinderMSB
	CALL	IDEwr8D

	MVI	D, 1		;One sector at a time
	MVI	E, REGseccnt
	CALL	IDEwr8D
	RET

;------------------------------------------------------------------------------	
;Wait for drive to come ready
;------------------------------------------------------------------------------	

IDEwaitnotbusy:			;Drive READY if status = 01000000
	MVI	B, 0FFH
	MVI	A, 0FFH		;Delay must be above 80H, longer for slow drives
	STA	mDELAYStore

MoreWait:
	MVI	E, REGstatus	;Wait for RDY bit to be set
	CALL	IDErd8D
	MOV	A, D
	ANI	11000000B
	XRI	01000000B
	JZ	DoneNotbusy
	DCR	B	
	JNZ	MoreWait
	LDA	mDELAYStore	;Check timeout delay
	DCR	A
	STA	mDELAYStore
	JNZ	MoreWait

	STC			;Set carry to indicate an error
	ret
DoneNotBusy:
	ORA	A		;Clear carry it indicate no error
	RET

;------------------------------------------------------------------------------	
;Wait for drive to assert data request (DRQ) line ready
;------------------------------------------------------------------------------	

IDEwaitdrq:
	MVI	B, 0FFH
	MVI	A, 0FFH		;Delay must be above 80H, longer for slow drives
	STA	mDELAYStore

MoreDRQ:
	MVI	E, REGstatus	;Wait for DRQ bit to be set
	CALL	IDErd8D
	MOV	A, D
	ANI	10001000B
	CPI	00001000B
	JZ	DoneDRQ
	DCR	B
	JNZ	MoreDRQ
	LDA	mDELAYStore	;Check timeout delay
	DCR	A
	STA	mDELAYStore
	JNZ	MoreDRQ
	STC			;Set carry to indicate error
	RET
DoneDRQ:
	ORA	A		;Clear carry
	RET			;Return drive status in A

;------------------------------------------------------------------------------	
;Clear the ID buffer
;------------------------------------------------------------------------------	

CLEAR$ID$BUFFER:
	LXI	H, IDBuffer
	LXI	B, 512
CLEAR2:	MVI	A, ' '
	MOV	M, A
	INX	H
	DCX	B
	MOV	A, C
	ORA	B
	JNZ	CLEAR2
	
	LXI	H, IDBuffer	;Zero for cylinder, heads, sectors
	LXI	B, 14
CLEAR3:	MVI	A, 0
	MOV	M, A
	INX	H
	DCX	B
	MOV	A, C
	ORA	B
	JNZ	CLEAR3
	RET

;------------------------------------------------------------------------------	
; Low Level 8 bit R/W to the drive controller. These are the routines that talk
; directly to the drive controller registers, via the 8255 chip.  
; Note the 16 bit I/O to the drive (which is only for SEC R/W) is done directly 
; in the routines READSECTOR & WRITESECTOR for speed reasons.
;------------------------------------------------------------------------------	

;------------------------------------------------------------------------------	
;Read One Byte
;------------------------------------------------------------------------------	

IDErd8D:				;Read 8 bits from IDE register in [E],
	MOV	A, E			;and return info in [D]
	OUT	IDEportC		;Drive address onto control lines

	ORI	IDErdline		;RD pulse pin (40H)
	OUT	IDEportC		;Assert read pin

	IN	IDEportA
	MOV	D, A			;Return with data in [D]

	MOV	A, E
	OUT	IDEportC		;Deassert RD pin

	XRA	A
	OUT	IDEportC		;Zero all port C lines
	RET

;------------------------------------------------------------------------------	
;Write One Byte
;------------------------------------------------------------------------------	

IDEwr8D:				;Write Data in [D] to IDE register [E]
	MVI	A, WRITEcfg8255		;Set 8255 to write mode
	OUT	IDEportCtrl

	MOV	A, D			;Get data put it in 8255 A port
	OUT	IDEportA

	MOV	A, E			;Select IDE register
	OUT	IDEportC

	ORI	IDEwrline		;Lower WR line
	OUT	IDEportC
	
	MOV	A, E			;Raise WR line
	OUT	IDEportC		;Deassert RD pin

	XRA	A			;Deselect all lines including WR line
	OUT	IDEportC

	MVI	A, READcfg8255		;Config 8255 chip, read mode on return
	OUT	IDEportCtrl
	RET

;------------------------------------------------------------------------------	
;This code is written to reside and run from 0H.  To re-introduce the CPMLDR,
;it must be copied from where it is stored in high memory and relocated to 100H
;in RAM, which overwrites this program.
;------------------------------------------------------------------------------	

CPM$MOVE$CODE
	LXI	H, BUFFER
	LXI	D, 100H
	LXI	B, (12*512)
	LDIR
	JMP	100H
CPM$MOVE$CODE$END:

;------------------------------------------------------------------------------	
;
;COMMAND BRANCH TABLE
;
;------------------------------------------------------------------------------	

TBL:	DW  DRIVE$0   ; "A"  Select Drive 0
	DW  DRIVE$1   ; "B"  Select Drive 1
	DW  CPMBOOT   ; "C"  LOAD CPM (if present)
	DW  DISPLAY   ; "D"  Sector contents display: ON/OFF
	DW  RAMCLEAR  ; "E"  Clear RAM buffer
	DW  FORMAT    ; "F"  Format current disk
	DW  RESTORE   ; "G"  Restore backup
	DW  BACKUP    ; "H"  Backup partition
	DW  NEXT$SECT ; "I"  Next Sector
	DW  PREV$SEC  ; "J"  Previous sector
	DW  ERROR     ; "K"  
	DW  SET$LBA   ; "L"  Set LBA value (Set track, sector)  
	DW  ERROR     ; "M"  
	DW  POWER$DOWN; "N"  Power down hard disk command
	DW  ERROR     ; "O"  
	DW  PRN$0$INFO; "P"  Print Drive 0 ID info  
	DW  PRN$1$INFO; "Q"  Print Drive 1 ID info
	DW  READ$SEC  ; "R"  Read sector to data buffer
	DW  SEQ$RD    ; "S"  Sequental sec read and display contents
	DW  ERROR     ; "T"  
	DW  POWER$UP  ; "U"  Power up hard disk command
	DW  N$RD$SEC  ; "V"  Read N sectors
	DW  WRITE$SEC ; "W"  Write data buffer to current sector
	DW  N$WR$SEC  ; "X"  Write N sectors
	DW  COPY$D0D1 ; "Y"  Copy Drive 0 to Drive 1
	DW  CMP$D0D1  ; "Z"  Verify Drive 0 = Drive 1

;------------------------------------------------------------------------------	
;
;String constants - Messages generated by this program
;
;------------------------------------------------------------------------------

SIGN$ON:	DB	CR,LF,'IDE Disk Drive Utility Program  v3.0  12-08-2022',CR,LF,LF,'$'
SEL0MSG		DB	'Selecting first IDE drive.',CR,LF,'$'
SEL1MSG		DB	'Selecting second IDE drive.',CR,LF,'$'
INITDRIVE	DB	'Initializing drive.  $'
READING$ID	DB	'Reading drive ID.  $'
GETTING$ID	DB	'Getting drive ID...',CR,LF,'$'
DISKSTATUS	DB	'Status is $'
INIT$0$ERROR:	DB	'Initialization of first drive failed. Aborting program.',BELL,CR,LF,LF,'$'
INIT$1$ERROR	DB	'Initialization of second drive failed. (Possibly not present).',BELL,CR,LF,LF,'$'
ID$ERROR:	DB	'Error obtaining drive ID.',BELL,CR,LF,'$'
INIT$DR$OK:	DB	'Drive initialized OK.',CR,LF,LF,'$'
BAD$DRIVE:	DB	CR,LF,'First Drive ID Information appears invalid.',CR,LF
		DB	'Aborting program.',BELL,CR,LF,LF,'$'
DRIVE0$INFO:	DB	'------------ Drive 0 -------------',CR,LF,'$'
DRIVE1$INFO:	DB	'------------ Drive 1 -------------',CR,LF,'$'
msgmdl:		DB	'Model: $'
msgsn:		DB	'S/N:   $'
msgrev:		DB	'Rev:   $'
msgcy:		DB	'Cyl: $'
msghd:		DB	', Hd: $'
msgsc:		DB	', Sec: $'
msgCPMTRK:	DB	'CPM TRK = $'
msgCPMSEC:	DB	' CPM SEC = $'
msgLBA:		DB	'  (LBA = 00$'
MSGBracket	DB	')$'
msgLBAsup1:	DB	'LBA is $'
msgLBAnot:	DB	'NOT $'
msgLBAsup2	DB	'supported',CR,LF,'$'
DRIVE$0$MSG	DB	CR,LF,LF,'  >>> DRIVE #0 <<<$'
DRIVE$1$MSG	DB	CR,LF,LF,'  >>> DRIVE #1 <<<$'
CMD$STRING1: 	DB	'     IDE Board Diagnostic MAIN MENU',CR,LF,LF
		DB	'(A) Select Drive 0        (P) Drive 0 Information   '
		DB	'(H) Backup Disk',CR,LF
		DB	'(B) Select Drive 1        (Q) Drive 1 Information   '
		DB	'(G) Restore Backup',CR,LF
		DB	'(L) Set LBA value         (R) Read Sector to Buffer '
		DB	'(W) Write Buffer to Sector',CR,LF
		DB	'(I) Next Sector           (V) Read N Sectors        '
		DB	'(X) Write N Sectors',CR,LF
		DB	'(J) Previous Sector       (S) Sequental Sector Read '
		DB	'(Y) Copy Drive 0 to 1',CR,LF
		DB	'(U) Power Up              (N) Power Down            '
		DB	'(Z) Verify Drive 0 = 1',CR,LF
		DB	'(F) Format Disk           (D) Set Display ON        '
		DB	'(ESC) Quit',CR,LF
		DB	LF,'Current settings: $'
CMD$STRING2: 	DB	'     IDE Board Diagnostic MAIN MENU',CR,LF,LF
		DB	'(A) Select Drive 0        (P) Drive 0 Information   '
		DB	'(H) Backup disk',CR,LF
		DB	'(B) Select Drive 1        (Q) Drive 1 Information   '
		DB	'(G) Restore Backup',CR,LF
		DB	'(L) Set LBA value         (R) Read Sector to Buffer '
		DB	'(W) Write Buffer to Sector',CR,LF
		DB	'(I) Next Sector           (V) Read N Sectors        '
		DB	'(X) Write N Sectors',CR,LF
		DB	'(J) Previous Sector       (S) Sequental Sector Read '
		DB	'(Y) Copy Drive 0 to 1',CR,LF
		DB	'(U) Power Up              (N) Power Down            '
		DB	'(Z) Verify Drive 0 = 1',CR,LF
		DB	'(F) Format Disk           (D) Set Display OFF       '
		DB	'(ESC) Quit',CR,LF
		DB	LF,'Current settings: $'
Prompt:		DB	CR,LF,LF,'Please enter command > $'
Response:	DB	CR,LF,'Command received:      $'
msgsure:	DB	CR,LF,'Warning: this will change data on the drive, '
		DB	'are you sure? (Y/N)...$'
msgrd:		DB	CR,LF,'Sector Read OK',CR,LF,'$'
msgwr:		DB	CR,LF,'Sector Write OK',CR,LF,'$'
GET$LBA:	DB	'Enter CPM style TRK & SEC values (in hex).',CR,LF,'$'
SEC$RW$ERROR	DB	'Drive Error, Status Register = $'
ERR$REG$DATA	DB	'Drive Error, Error Register = $'
ENTER$SECL	DB	'Starting sector number,(xxH) = $'
ENTER$TRKL	DB	'Track number (LOW byte, xxH) = $'
ENTER$TRKH	DB	'Track number (HIGH byte, xxH) = $'
ENTER$HEAD	DB	'Head number (01-0F) = $'
ENTER$COUNT	DB	'Number of sectors to R/W = $'
DRIVE$BUSY	DB	'Drive Busy (bit 7) stuck high.   Status = $'
DRIVE$NOT$READY	DB	'Drive Ready (bit 6) stuck low.  Status = $'
DRIVE$WR$FAULT	DB	'Drive write fault.    Status = $'
UNKNOWN$ERROR	DB	'Unknown error in status register.   Status = $'
BAD$BLOCK	DB	'Bad Sector ID.    Error Register = $'
UNRECOVER$ERR	DB	'Uncorrectable data error.  Error Register = $'
READ$ID$ERROR	DB	'Error setting up to read Drive ID',CR,LF,'$'
SEC$NOT$FOUND	DB	'Sector not found. Error Register = $'
INVALID$CMD	DB	'Invalid Command. Error Register = $'
TRK0$ERR	DB	'Track Zero not found. Error Register = $'
UNKNOWN$ERROR1	DB	'Unknown Error. Error Register = $'
CONTINUE$MSG	DB	CR,LF,'ESC to abort. Any other key to continue. $'
FORMAT$MSG	DB	'FORMAT DISK. Fill all sectors with E5'
		DB	60H,'s on the CURRENT drive/CF card.$'
ReadN$MSG	DB	CR,LF,'Read multiple sectors from current disk/CF card to RAM buffer.'
		DB	CR,LF,'How many 512 byte sectors (xx HEX):$'
WriteN$MSG	DB	CR,LF,'Write multiple sectors RAM buffer CURRENT disk/CF card.'
		DB	CR,LF,'How many 512 byte sectors (xx HEX):$'
ReadingN$MSG	DB	CR,LF,'Reading Sector at: $'
WritingN$MSG	DB	CR,LF,'Writing Sector at: $'
msgErr		DB	CR,LF,'Sorry, that was not a valid menu option!$'
FormatDone	DB	CR,LF,'Disk Format Complete.',CR,LF,'$'
BackupDone	DB	CR,LF,'Disk partition copy complete.',CR,LF,'$'
CopyMsg		DB	CR,LF,'Copy disk partition to a second area on disk (CF card).'
		DB	CR,LF,'>>> This assumes that tracks greater than MAXTRK '
		DB	'(for CPM, 0FFH) are unused <<<'
		DB	CR,LF,'>>> on this disk. Be sure you have nothing in this '
		DB	'"Backup partition area". <<<'
		DB	CR,LF,BELL,'Warning: This will change data in the partition area, '
		DB	'are you sure? (Y/N)...$ '
AtEnd		DB	CR,LF,'At end of disk partition!',CR,LF,'$'
RBackup$MSG	DB	'Reading track: $'
WBackup$MSG	DB	'H. Writing track: $'
H$Msg		DB	'H$'
RestoreMsg	DB	CR,LF,'Restore disk with data from backup partition on disk (CF card).'
		DB	CR,LF,BELL,'Warning: This will change data on disk, '
		DB	'are you sure? (Y/N)...$ '
RestoreDone	DB	CR,LF,'Restore of disk data from backup partition complete.',CR,LF,'$'
RANGE$MSG	DB	CR,LF,'Sector value out of range.',CR,LF,'$'
CPM$ERROR	DB	CR,LF,'Error reading CPMLDR.',CR,LF,'$'
CPM$ERROR1	DB	CR,LF,'Data error reading CPMLDR. (The first byte loaded was not 31H).',CR,LF,'$'
MOVE$REQUEST	DB	CR,LF,'The CPMLDR image is now at 3000H in RAM. '
		DB	'To boot CPM you will have to'
		DB	CR,LF,'overwrite this program at 100H. Do you wish to do so (Y/N)...$'
SET0$MSG	DB	CR,LF,'Current Drive is now #0 (Yellow LED)$'
SET1$MSG	DB	CR,LF,'Current Drive is now #1 (Green LED)$'
FILL$MSG	DB	CR,LF,'Sector buffer in RAM filled with 0',27H,'s$'		
DiskCopyMsg	DB	CR,LF,'Copy main disk partition of Drive 0 to Drive 1 (CF card).'
		DB	CR,LF,BELL,'Warning: This will delete all data on Drive 1, '
		DB	'are you sure? (Y/N)...$ '
CopyDone	DB	CR,LF,'Copy of CPM disk 0 to 1 complete.',CR,LF,'$'
CopyTrk$MSG	DB	'Copying track: $'
DiskVerifyMsg	DB	CR,LF,'Verify disk partition Drive 0 = Drive 1 (CF card).$'
VerifyTrk$MSG	DB	'Verifying track: $'
VerifyDone	DB	CR,LF,'Verify CPM disk 0 = 1 complete.',CR,LF,'$'
Verify$ERR	DB	CR,LF,BELL,'Verify error on Track $'
SEC$Msg		DB	'H  Sector $'

;------------------------------------------------------------------------------	
;RAM usage
;------------------------------------------------------------------------------	

RAMAREA		DB	'           RAM STORE AREA -------->'
mDMA		DW	buffer
mDRIVE$SEC	DB	0H
mDRIVE$TRK	DW	0H
mDisplayFlag	DB	0FFH		;Display of sector data initially ON
mSEC		DW	0H
mTRK		DW	0H
mSEC1		DW	0H		;For disk partition copy
mTRK1		DW	0H
mSEC2		DW	0H
mTRK2		DW	0H
mStartLineHex	DW	0H
mStartLineASCII	DW	0H
mBYTE$COUNT	DW	0H
mSECTOR$COUNT	DW	0H
mDELAYStore	DB	0H
mCURRENT$DRIVE	DB	0H
mLAST$DRIVE	DB	0H
mDriveBPresent	DB	0H		;1 if second drive is present

		DB	'          Start of ID buffer-->'
IDbuffer:	DS	512
		DB	'<--End of ID buffer            '

		ORG	BUFFER$ORG

BUFFER:		DB	76H
		DB	'<--Start buffer area' 
		DS	476
		DB	'End of buffer-->'

BUFFER2:	DB	'<--Start buffer2 area'
		DS	476
		DB	'End of buffer2-->'

		DS	100H

STACK		DW	0H

;END
TronDD
Posts: 40
Joined: November 20th, 2018, 7:51 pm
Contact:

Re: IDE interface for Altair 8800c

Post by TronDD »

I poked around with my Dual IDE card. I have the version with the GALs and had a hard time getting them programmed. Several programmers, a bunch of money, and a lot of hassle later, they seemed to program but I never trusted them. When the card didn't work, I was fed up with it and put it aside for over a year.

Taking a closer look now, I checked the output of the GALs. I'm not sure they are working correctly. GAL1, which does the address handling, seems to output regardless of the address. And GAL2 which handles the chip signals seems like it might be doing some of right thing except it never sends the chip enable signal to the 8255. So the 8255 is forever dead in the water.

I hated the GALs from the start and I no longer have access to a Windows system that the programmer software requires to check them or reprogram them. I'm just going to order the next version of the board that did away with the GALs and start over.
Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest