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 »

TronDD wrote: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.
I'm so sorry to hear that! But I understand - When I saw that version with the GALs, I kind of thought the same thing. I was glad that John made the next version without them.

I probably should move onto the BIOS phase of this project. I'm sure the low-level functions work and are ready to be implemented into a BIOS. But I'm going through the IDEutil code thoroughly first.

Right now, I can read, write, format, backup and restore. I can copy one disk to another, or from the first 00-FF tracks to the "backup partition," which is really just the next set of contiguous 256 tracks. CP/M only sees the first 256 tracks, so the others can be used for backups.
Wayne Parham
Posts: 253
Joined: March 18th, 2022, 3:01 pm
Contact:

Re: IDE interface for Altair 8800c

Post by Wayne Parham »

Here's a fun little update. I really need to stop fooling around with this thing and move onto creating a BIOS. But while testing backup and restore, it occurred to me that I could have the program ask for a backup partition number between 01-FF instead of having just one. That way, a whole 2Gb drive can be filled with 8Mb Altair drive copies.

Note: I had to split the code to post it. So just concatenate the two sections, the one in this post and the one in the next.

Code: Select all

;------------------------------------------------------------------------------
; Utility Program for IDE interface board
;	v3.0	      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

	CALL	GET$PART$NUM	;Ask user for partition number (01-FF)
	LHLD	mPART$NUM
	MOV	A, L
	CPI	0		;Partition zero isn't allowed
	JZ	RANGE$ERROR
	
	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

	LHLD	mPART$NUM	;Convert partition number to track number (x100H)
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	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

	CALL	GET$PART$NUM	;Ask user for partition number (01-FF)
	LHLD	mPART$NUM
	MOV	A, L
	CPI	0		;Partition zero isn't allowed
	JZ	RANGE$ERROR
	
	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

	LHLD	mPART$NUM	;Convert partition number to track number (x100H)
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	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 drive 0
	
	CALL	SELECT1		;Select drive 1
	CALL	wrlba		;Update LBA on 1 drive
	LXI	H, buffer2	;Point to buffer2
	SHLD	mDMA
	CALL	READSECTOR	;Get sector data from 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
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

;----------------------------- 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)

GET$PART$NUM:			;Ask user for backup partition number (01-FF)
	LXI	D, Enter$Partition
	CALL	PSTRING
	CALL	GETHEX		;Get 2 HEX digits
	RC
	STA	mPART$NUM
	CALL	ZCRLF

;------------------------------------------------------------------------------	
;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 main 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)...$ '
Enter$Partition	DB	CR,LF,LF,'Choose a backup partition (01-FF) $'
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,'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,LF,'Drive duplication complete.',CR,LF,'$'
CopyTrk$MSG	DB	'Copying track: $'
DiskVerifyMsg	DB	CR,LF,'Verify main disk partition drive 0 = drive 1 (CF card).$'
VerifyTrk$MSG	DB	'Comparing drive 0 to drive 1.  Verifying track: $'
VerifyDone	DB	CR,LF,LF,'Drive verification 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
mPART$NUM	DB	0H		;Backup partition (01-FF)
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
Wayne Parham
Posts: 253
Joined: March 18th, 2022, 3:01 pm
Contact:

Re: IDE interface for Altair 8800c

Post by Wayne Parham »

I've started working on the BIOS3 code and have come to a place where I need to make a choice. So I have come to the group to get some input.

My first thought was to use Mike's BIOS3 code, which looks like a port from BIOS2. As I've read, one can add a few functions to the BIOS2 code to create BIOS3. And it also appears that the jump table has one place for each of the primitives, the init, logon, read and write functions.

I need to have two separate sets of functions, one for the diskettes and one for the IDE drives.

Which is where I come to the fork in the road. I could put a conditional at the beginning of each function that examines drive number and then branches to the correct primitive for the selected drive. Or I can make use of that "extended disk parameter" header definition described in the CPM3 manuals. That seems like a macro-driven mechanism.

Which method would you suggest?

I'm really at the front end of the learning curve, as you can probably tell. So I'd love some input.
AltairClone
Site Admin
Posts: 677
Joined: April 5th, 2013, 10:55 am
Contact:

Re: IDE interface for Altair 8800c

Post by AltairClone »

So you're essentially wanting a CP/M BIOS that supports two different drive types, correct? This was handled several different ways.

One way was to assign drive letters to specific drive types. For example, A: and B: could be regular disk drives and C:, D: could be IDE drives.

The BIOS can also modify drive types on the fly based on the DPH pointer returned in response to SELDSK. For example, the Kaypro 10 boot ROM could boot from hard disk or floppy. If booting from hard disk, A: and B: were assigned to the hard drive and the floppy drive was C:. If booted from the floppy, the floppy became drive A: and the hard disk was B: and C:

Some BIOS's could ran through a series of tests the first time I drive was accessed to determine it's size, single/double sided, etc., and modified the DPH that was returned based on the parameters it discovered.

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

Re: IDE interface for Altair 8800c

Post by Wayne Parham »

Thanks so much for the reply.

I have scanned through several documents and found examples of each mechanism you described. Or actually, what I mean, is, I've seen usage of the DPH and I've seen examples of code that examined drive access results and chose configuration parameters to be able to support dual formats, e.g. single density versus double density diskette drives.

My approach is going to be to create a BIOS that boots on diskette, expecting A and B to be diskettes and C and D to be IDE drives. So I'll only need to support two different drive configurations. It would also boot on diskette even if hard drives weren't present. Of course, if you don't have hard drives, you wouldn't want to run this build of CP/M because of its TPA penalty from hard disk support. But it would at least boot, and could have the IDE utility programs present on diskette for formatting, backup/restore and to help troubleshoot IDE drives for new board owners/builders.

You mentioned changing the DPH pointer in response to SELDSK. That makes sense. I'll study that next.
Wayne Parham
Posts: 253
Joined: March 18th, 2022, 3:01 pm
Contact:

Re: IDE interface for Altair 8800c

Post by Wayne Parham »

I haven't worked on BIOS for the past few days, but I did thoroughly test the IDEutil program. I've made a few fixes and improvements since the last code I uploaded here, and I'm putting a box on this one. It's wrapped-up. So discard any earlier versions of IDEutil.

Latest IDEutil v.3.0 12/21/2022

Code: Select all

;------------------------------------------------------------------------------
; Utility Program for IDE interface board
;	v3.0	      12/21/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 some new features and cosmetic changes.  IDEutil allows
; 2Gb drives to store 256 "partitions," with partition 00 being the CP/M active
; data space and partitions 01 - FF available for backup and restore copies. 
;
; The  IDEutil  program also differs from myIDE, avoiding the  @  character  in
; labels so it can be built 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.  David Fry wrote the
; wrlba  function used to translate CP/M track and sector addresses to  logical
; block addressing.
;
;------------------------------------------------------------------------------

;------------------------------------------------------------------------------
;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 troubleshooting messages
CPM$TRANSLATE	EQU	TRUE	;Translate Trk, Sec, Head to CP/M 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 CP/M disks
PRINT		EQU	9
CONST		EQU	11	;Console stat
BDOS		EQU	5

;------------------------------------------------------------------------------
;ASCII equates:
;------------------------------------------------------------------------------

BELL		EQU	07H
BS		EQU	08H
TAB		EQU	09H
LF		EQU	0AH
CR		EQU	0DH
ESC		EQU	1BH
SPACE		EQU	20H
PERIOD		EQU	2EH

;------------------------------------------------------------------------------
;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 CP/M & MS-DOS.
;
;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	;CP/M 3 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 CP/M sectors for CPMLDR
CPMLDR$ADDRESS	EQU	BUFFER$ORG

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

	ORG	100H

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	mLast$Drive	;Only drive 0 attached
	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
	STA	mLast$Drive	;Both drives 0 and 1 are attached			

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			;Return to CP/M
  ENDIF
  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 VECTOR TABLE ----------------------------	

TBL:	DW  DRIVE$0   ; "A"  Select Drive 0
	DW  DRIVE$1   ; "B"  Select Drive 1
	DW  Cpy$Partn ; "C"  Copy Partition
	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  PRN$1$INFO; "I"  Print Drive 1 ID info
	DW  ERROR     ; "J"
	DW  SET$PARTN ; "K"  Set LBA value to start of selected partition 
	DW  SET$LBA   ; "L"  Set LBA value using selected sector and track
	DW  SHOW$BUF  ; "M"  Show sector buffer memory without disk read
	DW  NEXT$SECT ; "N"  Next Sector
	DW  PRN$0$INFO; "O"  Print Drive 0 ID info
	DW  PREV$SEC  ; "P"  Previous sector
	DW  ERROR     ; "Q"
	DW  READ$SEC  ; "R"  Read sector to data buffer
	DW  SEQ$RD    ; "S"  Sequental sec read and display contents
	DW  POWER$DOWN; "T"  Power down hard disk command  
	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  Cmp$Partn ; "Y"  Verify Partition
	DW  CPMBOOT   ; "Z"  LOAD CP/M (if present)

;------------------------------ COMMAND FUNCTIONS -----------------------------	

SHOW$BUF:			;Show buffer memory without disk read
	LXI	H, buffer
	CALL	HEXDUMP
	JMP	MAINLOOP

SET$PARTN:			;Ask user for a partition number (00-FF) to set LBA
	LXI	D, Enter$Partition
	CALL	PSTRING
	CALL	GETHEX		;Get 2 HEX digits
	JNC	GdPtn
	LXI	D, INVALID$MSG
	CALL	PSTRING
	JMP	MAINLOOP
GdPtn:	LXI	H, 0		;Convert partition number to track number (x100H)
	MOV	L, A
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	SHLD	mTRK
	XRA	A		;Set sector to 0
	STA	mSEC
	STA	mSEC+1
	CALL	wrlba		;Update LBA on drive
	CALL	ZCRLF
	JMP	MAINLOOP

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 CP/M 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 CP/M 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:
	MVI	A, 1
	CALL	Val$Drive	;Verify that drive 1 is connected
	JNC	Dexist
	LXI     D, DRV$NOT$FOUND	
	CALL	PSTRING
	JMP	MAINLOOP
Dexist:	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 CP/M 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 (CP/M) 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 CP/M 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 CP/M 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

	CALL	REM$DRV		;Remember current drive position

	LXI	H, buffer	;Fill buffer with E5's (512 of them)
	MVI	B, 0
Fill0:	MVI	A, 0E5H		;<-- Sector fill character (E5 for CP/M)
	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	ZCR		;Return to beginning of line
	CALL	DISPLAYposition	;Display actual current track, sector and head
	CALL	ZCSTS		;Any keyboard character will stop display
	CPI	01H		;CP/M 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 CP/M Sectors
	JMP	NEXT$FORMAT

NextFormatZero:
	LXI	H, 0		;Back to CP/M 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 CP/M partition to another area
	LXI	D, PartnExpln
	CALL	PSTRING
	LXI	D, BackupMsg
	CALL	PSTRING

	LXI	D, DoYouWant	;Is this what you want to do?
	CALL	PSTRING
	CALL	ZCI
	CALL	UPPER
	CPI	'Y'
	JNZ	MAINLOOP

	CALL	REM$DRV		;Remember current drive position

GtBkPt:	CALL	GET$BkPt$NUM	;Ask user for partition number (01-FF)
	JNC	GdBkPt
	CPI	ESC
	JZ	MAINLOOP
	LXI	D, INVALID$MSG
	CALL	PSTRING
	JMP	GtBkPt
GdBkPt:	LHLD	mPART$NUM
	MOV	A, L
	CPI	0		;Partition zero isn't allowed
	JZ	RANGE$ERROR

	LXI	D, ConfirmCopy	;Report: 'This will copy drive xx partition xxH to drive xx partition xxH.'
	CALL	PSTRING
	LDA	mCURRENT$DRIVE
	CALL	PHEX
	LXI	D, Partition
	CALL	PSTRING
	XRA	A
	CALL	PHEX
	LXI	D, H$Msg
	CALL	PSTRING
	LXI	D, ToDrive
	CALL	PSTRING
	LDA	mCURRENT$DRIVE
	CALL	PHEX
	LXI	D, Partition
	CALL	PSTRING
	LDA	mPART$NUM
	CALL	PHEX
	LXI	D, H$Msg
	CALL	PSTRING
	CALL	ZPERCRLF

	LXI	D, DoYouWant	;Is this what you want to do?
	CALL	PSTRING
	CALL	ZCI
	CALL	UPPER
	CPI	'Y'
	JNZ	MAINLOOP

	LXI	D, AreYouSure	;Are you sure?
	CALL	PSTRING
	CALL	ZCI
	CALL	UPPER
	CPI	'Y'
	JNZ	MAINLOOP
	
	CALL	ZCRLF
	CALL	ZCRLF

	LXI	H, 0		;Start with CP/M sector 0
	SHLD	mSEC
	SHLD	mSEC1
	SHLD	mSEC2		;and on second partition
	SHLD	mTRK		;and track 0
	SHLD	mTRK1

	LHLD	mPART$NUM	;Convert partition number to track number (x100H)
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	SHLD	mTRK2

NextBkup1:	
	CALL	ZCR		;Return to beginning of line
	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

NextBkup:	
	LHLD	mSEC1
	SHLD	mSEC
	LHLD	mTRK1
	SHLD	mTRK
	CALL	wrlba		;Update LBA on source partition

	LXI	H, buffer	;Point to buffer
	SHLD	mDMA
	CALL	READSECTOR	;Get sector data to buffer
	
	LHLD	mSEC2
	SHLD	mSEC
	LHLD	mTRK2
	SHLD	mTRK
	CALL	wrlba		;Update LBA on target partition
	
	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		;CP/M 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
	INX	H
	SHLD	mSEC1
	SHLD	mSEC2	
	JMP	NextBkup

BKNEXTZERO:
	LXI	H, 0		;Back to CP/M 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	NextBkup1
	
BKNextDone:
	LXI	D, BackupDone	;Tell us we are all done.
	CALL	PSTRING
	CALL	RET$DRV		;Return to original drive and position
	JMP	MAINLOOP

RESTORE:			;Restore disk from backup partition
	LXI	D, PartnExpln
	CALL	PSTRING
	LXI	D, RestoreMsg
	CALL	PSTRING

	LXI	D, DoYouWant	;Is this what you want to do?
	CALL	PSTRING
	CALL	ZCI
	CALL	UPPER
	CPI	'Y'
	JNZ	MAINLOOP

	CALL	REM$DRV		;Remember current drive position

GtRsPt:	CALL	GET$BkPt$NUM	;Ask user for partition number (01-FF)
	JNC	GdRsPt
	CPI	ESC
	JZ	MAINLOOP
	LXI	D, INVALID$MSG
	CALL	PSTRING
	JMP	GtRsPt
GdRsPt:	LHLD	mPART$NUM
	MOV	A, L
	CPI	0		;Partition zero isn't allowed
	JZ	RANGE$ERROR

	LXI	D, ConfirmCopy	;Report: 'This will copy drive xx partition xxH to drive xx partition xxH.'
	CALL	PSTRING
	LDA	mCURRENT$DRIVE
	CALL	PHEX
	LXI	D, Partition
	CALL	PSTRING
	LDA	mPART$NUM
	CALL	PHEX
	LXI	D, H$Msg
	CALL	PSTRING
	LXI	D, ToDrive
	CALL	PSTRING
	LDA	mCURRENT$DRIVE
	CALL	PHEX
	LXI	D, Partition
	CALL	PSTRING
	XRA	A
	CALL	PHEX
	LXI	D, H$Msg
	CALL	PSTRING
	CALL	ZPERCRLF

	LXI	D, DoYouWant	;Is this what you want to do?
	CALL	PSTRING
	CALL	ZCI
	CALL	UPPER
	CPI	'Y'
	JNZ	MAINLOOP

	LXI	D, AreYouSure	;Are you sure?
	CALL	PSTRING
	CALL	ZCI
	CALL	UPPER
	CPI	'Y'
	JNZ	MAINLOOP
	
	CALL	ZCRLF
	CALL	ZCRLF

	LXI	H, 0		;Start with CP/M sector 0
	SHLD	mSEC
	SHLD	mSEC1
	SHLD	mSEC2
	SHLD	mTRK
	SHLD	mTRK1

	LHLD	mPART$NUM	;Convert partition number to track number (x100H)
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	SHLD	mTRK2 

NextRestore1:	
	CALL	ZCR		;Return to beginning of line
	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:	
	LHLD	mSEC2		;Point to backup partition
	SHLD	mSEC
	LHLD	mTRK2
	SHLD	mTRK
	CALL	wrlba		;Update LBA on source partition

	LXI	H, buffer	;Point to buffer
	SHLD	mDMA
	CALL	READSECTOR	;Get sector data to buffer
	
	LHLD	mSEC1
	SHLD	mSEC
	LHLD	mTRK1
	SHLD	mTRK
	CALL	wrlba		;Update LBA on target partition
	
	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		;CP/M 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 CP/M 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
	CALL	RET$DRV		;Return to original drive and position
	JMP	MAINLOOP

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

Cpy$Partn:			;Copy Partition
	LXI	D, PartnExpln
	CALL	PSTRING
	LXI	D, CopyMsg
	CALL	PSTRING

	LXI	D, DoYouWant	;Is this what you want to do?
	CALL	PSTRING
	CALL	ZCI
	CALL	UPPER
	CPI	'Y'
	JNZ	MAINLOOP

	CALL	REM$DRV		;Remember current drive position

	CALL	ZCRLF
GtCpSD	CALL	GET$Src$Drive	;Ask user for source drive (00 or 01)
 	JNC	GtCpSP
	CPI	ESC
	JZ	MAINLOOP
	JMP	GtCpSD
GtCpSP:	CALL	GET$SrcPt$NUM	;Ask for source partition number (00-FF)
	JNC	GtCpTD
	CPI	ESC
	JZ	MAINLOOP
	LXI	D, INVALID$MSG
	CALL	PSTRING
	JMP	GtCpSP
GtCpTD:	CALL	GET$Tgt$Drive	;Ask for target drive (00 or 01)
	JNC	GtCpTP
	CPI	ESC
	JZ	MAINLOOP
	JMP	GtCpTD
GtCpTP:	CALL	GET$TgtPt$NUM	;Ask for target partition number (00-FF)
	JNC	GdCpIn
	CPI	ESC
	JZ	MAINLOOP
	LXI	D, INVALID$MSG
	CALL	PSTRING
	JMP	GtCpTP

GdCpIn:	LXI	D, ConfirmCopy	;Report: 'This will copy drive xx partition xxH to drive xx partition xxH.'
	CALL	PSTRING
	LDA	mSrc$Drive
	CALL	PHEX
	LXI	D, Partition
	CALL	PSTRING
	LDA	mSrc$Partn
	CALL	PHEX
	LXI	D, H$Msg
	CALL	PSTRING
	LXI	D, ToDrive
	CALL	PSTRING
	LDA	mTgt$Drive
	CALL	PHEX
	LXI	D, Partition
	CALL	PSTRING
	LDA	mTgt$Partn
	CALL	PHEX
	LXI	D, H$Msg
	CALL	PSTRING
	CALL	ZPERCRLF

	LXI	D, DoYouWant	;Is this what you want to do?
	CALL	PSTRING
	CALL	ZCI
	CALL	UPPER
	CPI	'Y'
	JNZ	MAINLOOP

	LXI	D, AreYouSure	;Are you sure?
	CALL	PSTRING
	CALL	ZCI
	CALL	UPPER
	CPI	'Y'
	JNZ	MAINLOOP
	
	CALL	ZCRLF
	CALL	ZCRLF

	LXI	H, 0		;Start with sector 0
	SHLD	mSEC1

	LHLD	mSrc$Partn	;Source partition
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	SHLD	mTRK1		;Converted to track

	LHLD	mTgt$Partn	;Target partition
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	SHLD	mTRK2		;Converted to track
	
NextCopy1:	
	CALL	ZCR		;Display:
	LXI	D, CopyTrk$MSG	;"Copying track"
	CALL	PSTRING
	LDA	mTRK1+1		;High TRK byte
	CALL	PHEX
	LDA	mTRK1		;Low TRK byte
	CALL	PHEX
	LXI	D, H$Msg
	CALL	PSTRING
	LXI	D, OnDrive$MSG	;"on drive"
	CALL	PSTRING
	LDA	mSrc$Drive
	CALL	PHEX
	LXI	D, ToTrack$MSG	;"to track"
	CALL	PSTRING
	LDA	mTRK2+1		;High TRK byte
	CALL	PHEX
	LDA	mTRK2		;Low TRK byte
	CALL	PHEX
	LXI	D, H$Msg
	CALL	PSTRING
	LXI	D, OnDrive$MSG	;"on drive"
	CALL	PSTRING
	LDA	mTgt$Drive
	CALL	PHEX

NextCopy:
	LDA	mSrc$Drive	;Select source drive
	CALL	SELECTdrive
	LHLD	mSEC1		;Source track and sector
	SHLD	mSEC
	LHLD	mTRK1
	SHLD	mTRK
	CALL	wrlba		;Update LBA
	LXI	H, buffer	;Point to buffer
	SHLD	mDMA
	CALL	READSECTOR	;Read sector data into buffer
	
	LDA	mTgt$Drive	;Select target drive
	CALL	SELECTdrive
	LHLD	mSEC2		;Target track and sector
	SHLD	mSEC
	LHLD	mTRK2
	SHLD	mTRK
	CALL	wrlba		;Update LBA
	LXI	H, buffer	;Point to buffer
	SHLD	mDMA
	CALL	WRITESECTOR	;Write buffer data to target drive
	
	CALL	ZCSTS		;Any keyboard character will stop display
	CPI	01H		;CP/M says something is there
	JNZ	COPY$NEXTSEC1
	CALL	ZCI		;Flush character
	LXI	D, CONTINUE$MSG
	CALL	PSTRING		;Prompt for continue or ESC
	CALL	ZCI
	MVI	B, 40		;Clear continue message
	CALL	ZERA
	CPI	ESC
	JNZ	COPY$NEXTSEC1
	JMP	MAINLOOP

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

COPY$NextZero:
	LXI	H, 0		;Back to CP/M sector 0
	SHLD	mSEC1
	SHLD	mSEC2
	
	LHLD	mTRK1		;Get Current Track
	MOV	A, L
	CPI	MAXTRK		;Are we already at max track?
	JZ	COPY$NextDone	;Yes - done
	INX	H		;No - bump to next track
	SHLD	mTRK1

	LHLD	mTRK2
	INX	H
	SHLD	mTRK2
	JMP	NextCopy1

COPY$NextDone:	
	LXI	D, CopyDone	;Tell us we are all done
	CALL	PSTRING
	CALL	RET$DRV		;Return to original drive and position
	JMP	MAINLOOP

Cmp$Partn:			;Verify Partition
	LXI	D, PartnExpln
	CALL	PSTRING
	LXI	D, VerifyMsg
	CALL	PSTRING

	LXI	D, DoYouWant	;Is this what you want to do?
	CALL	PSTRING
	CALL	ZCI
	CALL	UPPER
	CPI	'Y'
	JNZ	MAINLOOP

	CALL	REM$DRV		;Remember current drive position

	CALL	ZCRLF
GtCmSD	CALL	GET$Src$Drive	;Ask user for source drive (00 or 01)
 	JNC	GtCmSP
	CPI	ESC
	JZ	MAINLOOP
	JMP	GtCmSD
GtCmSP:	CALL	GET$SrcPt$NUM	;Ask for source partition number (00-FF)
	JNC	GtCmTD
	CPI	ESC
	JZ	MAINLOOP
	LXI	D, INVALID$MSG
	CALL	PSTRING
	JMP	GtCmSP
GtCmTD:	CALL	GET$Tgt$Drive	;Ask for target drive (00 or 01)
	JNC	GtCmTP
	CPI	ESC
	JZ	MAINLOOP
	JMP	GtCmTD
GtCmTP:	CALL	GET$TgtPt$NUM	;Ask for target partition number (00-FF)
	JNC	GdCmIn
	CPI	ESC
	JZ	MAINLOOP
	LXI	D, INVALID$MSG
	CALL	PSTRING
	JMP	GtCmTP

GdCmIn:	LXI	D, ConfirmCmp	;Report: 'This will compare drive xx partition xxH to drive xx partition xxH.'
	CALL	PSTRING
	LDA	mSrc$Drive
	CALL	PHEX
	LXI	D, Partition
	CALL	PSTRING
	LDA	mSrc$Partn
	CALL	PHEX
	LXI	D, H$Msg
	CALL	PSTRING
	LXI	D, ToDrive
	CALL	PSTRING
	LDA	mTgt$Drive
	CALL	PHEX
	LXI	D, Partition
	CALL	PSTRING
	LDA	mTgt$Partn
	CALL	PHEX
	LXI	D, H$Msg
	CALL	PSTRING
	CALL	ZPERCRLF

	LXI	D, DoYouWant	;Is this what you want to do?
	CALL	PSTRING
	CALL	ZCI
	CALL	UPPER
	CPI	'Y'
	JNZ	MAINLOOP

	LXI	D, AreYouSure	;Are you sure?
	CALL	PSTRING
	CALL	ZCI
	CALL	UPPER
	CPI	'Y'
	JNZ	MAINLOOP
	
	CALL	ZCRLF
	CALL	ZCRLF

	LXI	H, 0		;Start with sector 0
	SHLD	mSEC1

	LHLD	mSrc$Partn	;Source partition
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	SHLD	mTRK1		;Converted to track

	LHLD	mTgt$Partn	;Target partition
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	SHLD	mTRK2		;Converted to track
	
NextCmp1:	
	CALL	ZCR		;Display:
	LXI	D, VerifyTrk$MSG ;"Comparing track"
	CALL	PSTRING
	LDA	mTRK1+1		;High TRK byte
	CALL	PHEX
	LDA	mTRK1		;Low TRK byte
	CALL	PHEX
	LXI	D, H$Msg
	CALL	PSTRING
	LXI	D, OnDrive$MSG	;"on drive"
	CALL	PSTRING
	LDA	mSrc$Drive
	CALL	PHEX
	LXI	D, ToTrack$MSG	;"to track"
	CALL	PSTRING
	LDA	mTRK2+1		;High TRK byte
	CALL	PHEX
	LDA	mTRK2		;Low TRK byte
	CALL	PHEX
	LXI	D, H$Msg
	CALL	PSTRING
	LXI	D, OnDrive$MSG	;"on drive"
	CALL	PSTRING
	LDA	mTgt$Drive
	CALL	PHEX

NextCmp:	
	LDA	mSrc$Drive	;Select source drive
	CALL	SELECTdrive
	LHLD	mSEC1		;Source track and sector
	SHLD	mSEC
	LHLD	mTRK1
	SHLD	mTRK
	CALL	wrlba		;Update LBA
	LXI	H, buffer	;Point to buffer
	SHLD	mDMA
	CALL	READSECTOR	;Read sector data into buffer
	
	LDA	mTgt$Drive	;Select target drive
	CALL	SELECTdrive
	LHLD	mSEC2		;Target track and sector
	SHLD	mSEC
	LHLD	mTRK2
	SHLD	mTRK
	CALL	wrlba		;Update LBA
	LXI	H, buffer2	;Point to buffer2
	SHLD	mDMA
	CALL	READSECTOR	;Read sector data into buffer2

	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 Track byte
	CALL	PHEX
	LDA	mTRK		;Low Track byte
	CALL	PHEX
	LXI	D, SEC$Msg
	CALL	PSTRING
	LDA	mSEC+1		;High Sector byte
	CALL	PHEX
	LDA	mSEC		;Low 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		;CP/M says something is there
	JNZ	CMP$NEXTSEC1
	CALL	ZCI		;Flush character
VER$OK1:
	LXI	D, CONTINUE$MSG
	CALL	PSTRING
	CALL	ZCI
	CPI	ESC
	JNZ	CMP$NEXTSEC1
	JMP	MAINLOOP

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

CMP$NEXTZero:
	LXI	H, 0		;Back to CP/M sector 0
	SHLD	mSEC
	SHLD	mSEC2
	
	LHLD	mTRK1		;Get Current track
	MOV	A, L
	CPI	MAXTRK		;Are we already at max
	JZ	CMP$NEXTDone	;Yes - all done
	INX	H		;no - bump to next track
	SHLD	mTRK1

	LHLD	mTRK2
	INX	H
	SHLD	mTRK2
	JMP	NextCmp1

CMP$NextDone:	
	LXI	D, VerifyDone	;Tell us we are all done.
	CALL	PSTRING
	CALL	RET$DRV		;Return to original drive and position
	JMP	MAINLOOP
Wayne Parham
Posts: 253
Joined: March 18th, 2022, 3:01 pm
Contact:

Re: IDE interface for Altair 8800c

Post by Wayne Parham »

Concatenate this section with the previous to make the full program.

Code: Select all

;----------------------------- 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	ZCR		;Return to beginning of line
	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		;CP/M 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 CP/M 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		;Remember current drive position
	CALL	SELECT0
	CALL	PRN$DRV$INFO
	CALL	IDEinit
	CALL	RET$DRV		;Return to original drive and position
	JMP	MAINLOOP

PRN$1$INFO:			;Print Drive 1 identification info
	LXI	D, DRIVE1$INFO
	CALL	PSTRING
	CALL	REM$DRV		;Remember current drive position
	CALL	SELECT1
	CALL	PRN$DRV$INFO
	CALL	IDEinit
	CALL	RET$DRV		;Return to original drive and position
	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		;---- CP/M 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:				;Print CRLF
	PUSH	PSW
	MVI	C, CR
	CALL	ZCO
	MVI	C, LF
	CALL	ZCO
	POP	PSW
	RET

ZPERCRLF:			;Print period and then CRLF
	PUSH	PSW
	MVI	C, PERIOD
	CALL	ZCO
	MVI	C, CR
	CALL	ZCO
	MVI	C, LF
	CALL	ZCO
	POP	PSW
	RET

ZCR:				;Return to beginning of line
	MVI	C, CR
	CALL	ZCO
	RET

ZERA:				;Return to beginning of line and erase [B] characters
	MVI	C, CR
	CALL	ZCO
	MVI	C, SPACE
ERAX:	CALL	ZCO
	DCR	B
	JNZ	ERAX
	MVI	C, CR
	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 CP/M Track & Sector to LBA format
	LXI     D,ENTER$SECH
	CALL	PSTRING
	CALL	GETHEX		;Enter high byte sector number
	RC
	STA	mSEC+1
	CALL	ZCRLF

	LXI     D,ENTER$SECL
	CALL	PSTRING
	CALL	GETHEX		;Enter low byte sector number
	RC
	STA	mSEC
	CALL	ZCRLF

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

	LXI     D, ENTER$TRKL
	CALL	PSTRING
	CALL	GETHEX		;Enter low byte track number
	RC
	STA	mTRK
	CALL	ZCRLF

	XRA	A
	ORA	A		;Clear Accumulator and Carry bit
	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 CP/M sector to max CP/M 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)

GET$BkPt$NUM:			;Ask user for backup partition number (01-FF)
	LXI	D, Enter$BkupPart
	CALL	PSTRING
	CALL	GETHEX		;Get 2 HEX digits
	RC
	STA	mPART$NUM
	CALL	ZCRLF
	RET

GET$SrcPt$NUM:			;Ask user for source partition number (00-FF)
	LXI	D, Enter$SrcPartn
	CALL	PSTRING
	CALL	GETHEX		;Get 2 HEX digits
	RC
	STA	mSrc$Partn
	CALL	ZCRLF
	RET

GET$TgtPt$NUM:			;Ask user for target partition number (00-FF)
	LXI	D, Enter$Tgt$Partn
	CALL	PSTRING
	CALL	GETHEX		;Get 2 HEX digits
	RC
	STA	mTgt$Partn
	CALL	ZCRLF
	RET

GET$Src$Drive:			;Ask user for source drive (00 or 01)
	LXI	D, Enter$SrcDrive
	CALL	PSTRING
	CALL	GETHEX		;Get 2 HEX digits
	JNC	GdSrDin
	CPI	ESC		;Return if ESC key pressed
	STC
	RZ
	LXI	D, INVALID$MSG	;Re-prompt if input is invalid
	CALL	PSTRING
	JMP	GET$Src$Drive
GdSrDin:
	CALL	Val$Drive	;Verify that drive is valid
	RC	
	STA	mSrc$Drive
	CALL	ZCRLF
	RET

GET$Tgt$Drive:			;Ask user for target drive (00 or 01)
	LXI	D, Enter$TgtDrive
	CALL	PSTRING
	CALL	GETHEX		;Get 2 HEX digits
	JNC	GdTgDin
	CPI	ESC		;Return if ESC key pressed
	STC
	RZ
	LXI	D, INVALID$MSG	;Re-prompt if input is invalid
	CALL	PSTRING
	JMP	GET$Tgt$Drive
GdTgDin:
	CALL	Val$Drive	;Is drive valid?
	RC	
	STA	mTgt$Drive
	CALL	ZCRLF
	RET

Val$Drive:			;Check if drive [A] is valid
	LHLD	mLast$Drive
	INX	H
	CMP	L		;Is drive valid?
	JC	Vdone
	LXI	D, DRV$NOT$FOUND
	CALL	PSTRING
Vdone:	CMC
	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
	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:			;Select drive 0
	XRA	A
	JMP	SELECTdrive

SELECT1:			;Select drive 1
	MVI	A, 1

SELECTdrive:			;Select drive [A]
	STA	mCURRENT$DRIVE
	OUT	IDEDrive	
	RET

REM$DRV:			;Remember drive and position
	LDA	mCURRENT$DRIVE
	STA	mREM$DRIVE
	LHLD	mSEC
	SHLD	mREM$SEC
	LHLD	mTRK
	SHLD	mREM$TRK
	RET

RET$DRV:			;Return to last drive and position
	LDA	mREM$DRIVE
	STA	mCURRENT$DRIVE
	OUT	IDEDrive	
	LHLD	mREM$SEC
	SHLD	mSEC
	LHLD	mREM$TRK
	SHLD	mTRK
	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 CP/M 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:

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

SIGN$ON:	DB	CR,LF,'IDE Disk Drive Utility Program  v3.0  12-21-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        (O) Drive 0 Information   '
		DB	'(H) Backup Disk',CR,LF
		DB	'(B) Select Drive 1        (I) Drive 1 Information   '
		DB	'(G) Restore Backup',CR,LF
		DB	'(K) Set LBA by Partition  (M) Show Buffer w/o Read  '
		DB	'(E) Clear Buffer',CR,LF
		DB	'(L) Set LBA Track, Sector (R) Read Sector to Buffer '
		DB	'(W) Write Buffer to Sector',CR,LF
		DB	'(N) Next Sector           (V) Read N Sectors        '
		DB	'(X) Write N Sectors',CR,LF
		DB	'(P) Previous Sector       (S) Sequental Sector Read '
		DB	'(C) Copy Partition',CR,LF
		DB	'(U) Power Up              (T) Power Down            '
		DB	'(Y) Verify Partition',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        (O) Drive 0 Information   '
		DB	'(H) Backup Disk',CR,LF
		DB	'(B) Select Drive 1        (I) Drive 1 Information   '
		DB	'(G) Restore Backup',CR,LF
		DB	'(K) Set LBA by Partition  (M) Show Buffer w/o Read  '
		DB	'(E) Clear Buffer',CR,LF
		DB	'(L) Set LBA Track, Sector (R) Read Sector to Buffer '
		DB	'(W) Write Buffer to Sector',CR,LF
		DB	'(N) Next Sector           (V) Read N Sectors        '
		DB	'(X) Write N Sectors',CR,LF
		DB	'(P) Previous Sector       (S) Sequental Sector Read '
		DB	'(C) Copy Partition',CR,LF
		DB	'(U) Power Up              (T) Power Down            '
		DB	'(Y) Verify Partition',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? $'
AreYouSure	DB	CR,LF,'Are you sure? $'
DoYouWant	DB	CR,LF,'Is that what you want to do? $'
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	'Sector number (LOW byte, xxH) = $'
ENTER$SECH	DB	'Sector number (HIGH byte, 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.$'
ReadN$MSG	DB	CR,LF,'Read multiple sectors from current drive to RAM buffer.'
		DB	CR,LF,'How many 512 byte sectors (xx HEX):$'
WriteN$MSG	DB	CR,LF,'Write multiple sectors from RAM buffer to current drive.'
		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,'$'
PartnExpln	DB	CR,LF,'Each 2Gb physical disk is structured as 256'
		DB	' "partitions" of 8Mb each.  The CP/M'
		DB	CR,LF,'operating system can directly access only'
		DB	' partition 00, but all the others can'
		DB	CR,LF,'be used as backups or archives.  The backup'
		DB	' partitions are numbered 00 - FF.',CR,LF,'$'
BackupMsg	DB	CR,LF,'This will copy data from the main CP/M'
		DB	' partition on the current drive to a'
		DB	CR,LF,'backup partition.',CR,LF,'$'
RestoreMsg	DB	CR,LF,'This will restore data from a backup'
		DB	' partition to the main CP/M partition on'
		DB	CR,LF,'the current drive.',CR,LF,'$'
CopyMsg		DB	CR,LF,'This will copy data from any partition to'
		DB	' any other partition on either drive.',CR,LF,'$'
Enter$Partition	DB	CR,LF,LF,'Choose a partition number (00-FF) $'
Enter$Bkup$Part	DB	CR,LF,LF,'Choose a backup partition (01-FF) $'
Enter$Src$Partn	DB	CR,LF,'Choose source partition (00-FF) $'
Enter$Tgt$Partn	DB	CR,LF,'Choose target partition (00-FF) $'
Enter$Src$Drive	DB	CR,LF,'Choose source drive (00 or 01) $'
Enter$Tgt$Drive	DB	CR,LF,'Choose target drive (00 or 01) $'
ConfirmCopy	DB	CR,LF,'This will copy drive $'
ConfirmCmp	DB	CR,LF,'This will compare drive $'
Partition	DB	' partition $'
ToDrive		DB	' to drive $'
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$'
RestoreDone	DB	CR,LF,'Restore of disk data from backup partition complete.',CR,LF,'$'
DRV$NOT$FOUND	DB	CR,LF,LF,'Drive not connected.',CR,LF,'$'
RANGE$MSG	DB	CR,LF,LF,'Value out of range.',CR,LF,'$'
INVALID$MSG	DB	CR,LF,LF,'Invalid input.',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? $'
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$'		
CopyDone	DB	CR,LF,LF,'Partition copy complete.',CR,LF,'$'
CopyTrk$MSG	DB	'Copying track $'
OnDrive$MSG	DB	' on drive $'
ToTrack$MSG	DB	' to track $'
VerifyMsg	DB	CR,LF,'This will compare any two partitions on either drive'
		DB	' and will report any',CR,LF,'differences.',CR,LF,'$'
VerifyTrk$MSG	DB	'Comparing track $'
VerifyDone	DB	CR,LF,LF,'Partition verification 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
mSrc$Drive	DB	0H		;User-inputs for copy and restore commands
mSrc$Partn	DB	0H
mTgt$Drive	DB	0H
mTgt$Partn	DB	0H
mPART$NUM	DB	0H		;Backup partition (01-FF)
mStartLineHex	DW	0H
mStartLineASCII	DW	0H
mBYTE$COUNT	DW	0H
mSECTOR$COUNT	DW	0H
mDELAYStore	DB	0H
mCURRENT$DRIVE	DB	0H
mREM$DRIVE	DB	0H
mREM$SEC	DW	0H
mREM$TRK	DW	0H
mLast$Drive	DB	0H		;0 or 1

		DS	100H		;Stack is 256 bytes, just before buffers
STACK:		DW	0H

		DB	'          Start of ID buffer-->'
IDbuffer:	DS	512		;IDbuffer is 512 bytes with text before and after
		DB	'<--End of ID buffer            '

		ORG	BUFFER$ORG

BUFFER:		DB	'>--Start buffer' 
		DS	481		;buffer is 512 bytes total
		DB	'End of buffer--<'

BUFFER2:	DB	'>--Start buffer2'
		DS	479		;buffer2 is 512 bytes total
		DB	'End of buffer2--<'

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

Re: IDE interface for Altair 8800c

Post by Wayne Parham »

I couldn't resist but to make one last change. It's trivial, but it bothered me. I changed the menu item for "verify" to be a "V." Realized I had forgotten to make that change right after I uploaded here, and almost didn't bother to post the update. But I thought it best so here 'tis:

Code: Select all

;------------------------------------------------------------------------------
; Utility Program for IDE interface board
;	v3.0	      12/21/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 some new features and cosmetic changes.  IDEutil allows
; 2Gb drives to store 256 "partitions," with partition 00 being the CP/M active
; data space and partitions 01 - FF available for backup and restore copies. 
;
; The  IDEutil  program also differs from myIDE, avoiding the  @  character  in
; labels so it can be built 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.  David Fry wrote the
; wrlba  function used to translate CP/M track and sector addresses to  logical
; block addressing.
;
;------------------------------------------------------------------------------

;------------------------------------------------------------------------------
;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 troubleshooting messages
CPM$TRANSLATE	EQU	TRUE	;Translate Trk, Sec, Head to CP/M 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 CP/M disks
PRINT		EQU	9
CONST		EQU	11	;Console stat
BDOS		EQU	5

;------------------------------------------------------------------------------
;ASCII equates:
;------------------------------------------------------------------------------

BELL		EQU	07H
BS		EQU	08H
TAB		EQU	09H
LF		EQU	0AH
CR		EQU	0DH
ESC		EQU	1BH
SPACE		EQU	20H
PERIOD		EQU	2EH

;------------------------------------------------------------------------------
;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 CP/M & MS-DOS.
;
;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	;CP/M 3 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 CP/M sectors for CPMLDR
CPMLDR$ADDRESS	EQU	BUFFER$ORG

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

	ORG	100H

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	mLast$Drive	;Only drive 0 attached
	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
	STA	mLast$Drive	;Both drives 0 and 1 are attached			

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			;Return to CP/M
  ENDIF
  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 VECTOR TABLE ----------------------------	

TBL:	DW  DRIVE$0   ; "A"  Select Drive 0
	DW  DRIVE$1   ; "B"  Select Drive 1
	DW  Cpy$Partn ; "C"  Copy Partition
	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  PRN$1$INFO; "I"  Print Drive 1 ID info
	DW  ERROR     ; "J"
	DW  SET$PARTN ; "K"  Set LBA value to start of selected partition 
	DW  SET$LBA   ; "L"  Set LBA value using selected sector and track
	DW  SHOW$BUF  ; "M"  Show sector buffer memory without disk read
	DW  NEXT$SECT ; "N"  Next Sector
	DW  PRN$0$INFO; "O"  Print Drive 0 ID info
	DW  PREV$SEC  ; "P"  Previous sector
	DW  ERROR     ; "Q"
	DW  READ$SEC  ; "R"  Read sector to data buffer
	DW  SEQ$RD    ; "S"  Sequental sec read and display contents
	DW  POWER$DOWN; "T"  Power down hard disk command  
	DW  POWER$UP  ; "U"  Power up hard disk command
	DW  Cmp$Partn ; "V"  Verify Partition
	DW  WRITE$SEC ; "W"  Write data buffer to current sector
	DW  N$WR$SEC  ; "X"  Write N sectors
	DW  N$RD$SEC  ; "Y"  Read N sectors
	DW  CPMBOOT   ; "Z"  LOAD CP/M (if present)

;------------------------------ COMMAND FUNCTIONS -----------------------------	

SHOW$BUF:			;Show buffer memory without disk read
	LXI	H, buffer
	CALL	HEXDUMP
	JMP	MAINLOOP

SET$PARTN:			;Ask user for a partition number (00-FF) to set LBA
	LXI	D, Enter$Partition
	CALL	PSTRING
	CALL	GETHEX		;Get 2 HEX digits
	JNC	GdPtn
	LXI	D, INVALID$MSG
	CALL	PSTRING
	JMP	MAINLOOP
GdPtn:	LXI	H, 0		;Convert partition number to track number (x100H)
	MOV	L, A
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	SHLD	mTRK
	XRA	A		;Set sector to 0
	STA	mSEC
	STA	mSEC+1
	CALL	wrlba		;Update LBA on drive
	CALL	ZCRLF
	JMP	MAINLOOP

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 CP/M 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 CP/M 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:
	MVI	A, 1
	CALL	Val$Drive	;Verify that drive 1 is connected
	JNC	Dexist
	LXI     D, DRV$NOT$FOUND	
	CALL	PSTRING
	JMP	MAINLOOP
Dexist:	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 CP/M 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 (CP/M) 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 CP/M 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 CP/M 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

	CALL	REM$DRV		;Remember current drive position

	LXI	H, buffer	;Fill buffer with E5's (512 of them)
	MVI	B, 0
Fill0:	MVI	A, 0E5H		;<-- Sector fill character (E5 for CP/M)
	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	ZCR		;Return to beginning of line
	CALL	DISPLAYposition	;Display actual current track, sector and head
	CALL	ZCSTS		;Any keyboard character will stop display
	CPI	01H		;CP/M 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 CP/M Sectors
	JMP	NEXT$FORMAT

NextFormatZero:
	LXI	H, 0		;Back to CP/M 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 CP/M partition to another area
	LXI	D, PartnExpln
	CALL	PSTRING
	LXI	D, BackupMsg
	CALL	PSTRING

	LXI	D, DoYouWant	;Is this what you want to do?
	CALL	PSTRING
	CALL	ZCI
	CALL	UPPER
	CPI	'Y'
	JNZ	MAINLOOP

	CALL	REM$DRV		;Remember current drive position

GtBkPt:	CALL	GET$BkPt$NUM	;Ask user for partition number (01-FF)
	JNC	GdBkPt
	CPI	ESC
	JZ	MAINLOOP
	LXI	D, INVALID$MSG
	CALL	PSTRING
	JMP	GtBkPt
GdBkPt:	LHLD	mPART$NUM
	MOV	A, L
	CPI	0		;Partition zero isn't allowed
	JZ	RANGE$ERROR

	LXI	D, ConfirmCopy	;Report: 'This will copy drive xx partition xxH to drive xx partition xxH.'
	CALL	PSTRING
	LDA	mCURRENT$DRIVE
	CALL	PHEX
	LXI	D, Partition
	CALL	PSTRING
	XRA	A
	CALL	PHEX
	LXI	D, H$Msg
	CALL	PSTRING
	LXI	D, ToDrive
	CALL	PSTRING
	LDA	mCURRENT$DRIVE
	CALL	PHEX
	LXI	D, Partition
	CALL	PSTRING
	LDA	mPART$NUM
	CALL	PHEX
	LXI	D, H$Msg
	CALL	PSTRING
	CALL	ZPERCRLF

	LXI	D, DoYouWant	;Is this what you want to do?
	CALL	PSTRING
	CALL	ZCI
	CALL	UPPER
	CPI	'Y'
	JNZ	MAINLOOP

	LXI	D, AreYouSure	;Are you sure?
	CALL	PSTRING
	CALL	ZCI
	CALL	UPPER
	CPI	'Y'
	JNZ	MAINLOOP
	
	CALL	ZCRLF
	CALL	ZCRLF

	LXI	H, 0		;Start with CP/M sector 0
	SHLD	mSEC
	SHLD	mSEC1
	SHLD	mSEC2		;and on second partition
	SHLD	mTRK		;and track 0
	SHLD	mTRK1

	LHLD	mPART$NUM	;Convert partition number to track number (x100H)
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	SHLD	mTRK2

NextBkup1:	
	CALL	ZCR		;Return to beginning of line
	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

NextBkup:	
	LHLD	mSEC1
	SHLD	mSEC
	LHLD	mTRK1
	SHLD	mTRK
	CALL	wrlba		;Update LBA on source partition

	LXI	H, buffer	;Point to buffer
	SHLD	mDMA
	CALL	READSECTOR	;Get sector data to buffer
	
	LHLD	mSEC2
	SHLD	mSEC
	LHLD	mTRK2
	SHLD	mTRK
	CALL	wrlba		;Update LBA on target partition
	
	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		;CP/M 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
	INX	H
	SHLD	mSEC1
	SHLD	mSEC2	
	JMP	NextBkup

BKNEXTZERO:
	LXI	H, 0		;Back to CP/M 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	NextBkup1
	
BKNextDone:
	LXI	D, BackupDone	;Tell us we are all done.
	CALL	PSTRING
	CALL	RET$DRV		;Return to original drive and position
	JMP	MAINLOOP

RESTORE:			;Restore disk from backup partition
	LXI	D, PartnExpln
	CALL	PSTRING
	LXI	D, RestoreMsg
	CALL	PSTRING

	LXI	D, DoYouWant	;Is this what you want to do?
	CALL	PSTRING
	CALL	ZCI
	CALL	UPPER
	CPI	'Y'
	JNZ	MAINLOOP

	CALL	REM$DRV		;Remember current drive position

GtRsPt:	CALL	GET$BkPt$NUM	;Ask user for partition number (01-FF)
	JNC	GdRsPt
	CPI	ESC
	JZ	MAINLOOP
	LXI	D, INVALID$MSG
	CALL	PSTRING
	JMP	GtRsPt
GdRsPt:	LHLD	mPART$NUM
	MOV	A, L
	CPI	0		;Partition zero isn't allowed
	JZ	RANGE$ERROR

	LXI	D, ConfirmCopy	;Report: 'This will copy drive xx partition xxH to drive xx partition xxH.'
	CALL	PSTRING
	LDA	mCURRENT$DRIVE
	CALL	PHEX
	LXI	D, Partition
	CALL	PSTRING
	LDA	mPART$NUM
	CALL	PHEX
	LXI	D, H$Msg
	CALL	PSTRING
	LXI	D, ToDrive
	CALL	PSTRING
	LDA	mCURRENT$DRIVE
	CALL	PHEX
	LXI	D, Partition
	CALL	PSTRING
	XRA	A
	CALL	PHEX
	LXI	D, H$Msg
	CALL	PSTRING
	CALL	ZPERCRLF

	LXI	D, DoYouWant	;Is this what you want to do?
	CALL	PSTRING
	CALL	ZCI
	CALL	UPPER
	CPI	'Y'
	JNZ	MAINLOOP

	LXI	D, AreYouSure	;Are you sure?
	CALL	PSTRING
	CALL	ZCI
	CALL	UPPER
	CPI	'Y'
	JNZ	MAINLOOP
	
	CALL	ZCRLF
	CALL	ZCRLF

	LXI	H, 0		;Start with CP/M sector 0
	SHLD	mSEC
	SHLD	mSEC1
	SHLD	mSEC2
	SHLD	mTRK
	SHLD	mTRK1

	LHLD	mPART$NUM	;Convert partition number to track number (x100H)
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	SHLD	mTRK2 

NextRestore1:	
	CALL	ZCR		;Return to beginning of line
	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:	
	LHLD	mSEC2		;Point to backup partition
	SHLD	mSEC
	LHLD	mTRK2
	SHLD	mTRK
	CALL	wrlba		;Update LBA on source partition

	LXI	H, buffer	;Point to buffer
	SHLD	mDMA
	CALL	READSECTOR	;Get sector data to buffer
	
	LHLD	mSEC1
	SHLD	mSEC
	LHLD	mTRK1
	SHLD	mTRK
	CALL	wrlba		;Update LBA on target partition
	
	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		;CP/M 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 CP/M 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
	CALL	RET$DRV		;Return to original drive and position
	JMP	MAINLOOP

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

Cpy$Partn:			;Copy Partition
	LXI	D, PartnExpln
	CALL	PSTRING
	LXI	D, CopyMsg
	CALL	PSTRING

	LXI	D, DoYouWant	;Is this what you want to do?
	CALL	PSTRING
	CALL	ZCI
	CALL	UPPER
	CPI	'Y'
	JNZ	MAINLOOP

	CALL	REM$DRV		;Remember current drive position

	CALL	ZCRLF
GtCpSD	CALL	GET$Src$Drive	;Ask user for source drive (00 or 01)
 	JNC	GtCpSP
	CPI	ESC
	JZ	MAINLOOP
	JMP	GtCpSD
GtCpSP:	CALL	GET$SrcPt$NUM	;Ask for source partition number (00-FF)
	JNC	GtCpTD
	CPI	ESC
	JZ	MAINLOOP
	LXI	D, INVALID$MSG
	CALL	PSTRING
	JMP	GtCpSP
GtCpTD:	CALL	GET$Tgt$Drive	;Ask for target drive (00 or 01)
	JNC	GtCpTP
	CPI	ESC
	JZ	MAINLOOP
	JMP	GtCpTD
GtCpTP:	CALL	GET$TgtPt$NUM	;Ask for target partition number (00-FF)
	JNC	GdCpIn
	CPI	ESC
	JZ	MAINLOOP
	LXI	D, INVALID$MSG
	CALL	PSTRING
	JMP	GtCpTP

GdCpIn:	LXI	D, ConfirmCopy	;Report: 'This will copy drive xx partition xxH to drive xx partition xxH.'
	CALL	PSTRING
	LDA	mSrc$Drive
	CALL	PHEX
	LXI	D, Partition
	CALL	PSTRING
	LDA	mSrc$Partn
	CALL	PHEX
	LXI	D, H$Msg
	CALL	PSTRING
	LXI	D, ToDrive
	CALL	PSTRING
	LDA	mTgt$Drive
	CALL	PHEX
	LXI	D, Partition
	CALL	PSTRING
	LDA	mTgt$Partn
	CALL	PHEX
	LXI	D, H$Msg
	CALL	PSTRING
	CALL	ZPERCRLF

	LXI	D, DoYouWant	;Is this what you want to do?
	CALL	PSTRING
	CALL	ZCI
	CALL	UPPER
	CPI	'Y'
	JNZ	MAINLOOP

	LXI	D, AreYouSure	;Are you sure?
	CALL	PSTRING
	CALL	ZCI
	CALL	UPPER
	CPI	'Y'
	JNZ	MAINLOOP
	
	CALL	ZCRLF
	CALL	ZCRLF

	LXI	H, 0		;Start with sector 0
	SHLD	mSEC1

	LHLD	mSrc$Partn	;Source partition
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	SHLD	mTRK1		;Converted to track

	LHLD	mTgt$Partn	;Target partition
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	SHLD	mTRK2		;Converted to track
	
NextCopy1:	
	CALL	ZCR		;Display:
	LXI	D, CopyTrk$MSG	;"Copying track"
	CALL	PSTRING
	LDA	mTRK1+1		;High TRK byte
	CALL	PHEX
	LDA	mTRK1		;Low TRK byte
	CALL	PHEX
	LXI	D, H$Msg
	CALL	PSTRING
	LXI	D, OnDrive$MSG	;"on drive"
	CALL	PSTRING
	LDA	mSrc$Drive
	CALL	PHEX
	LXI	D, ToTrack$MSG	;"to track"
	CALL	PSTRING
	LDA	mTRK2+1		;High TRK byte
	CALL	PHEX
	LDA	mTRK2		;Low TRK byte
	CALL	PHEX
	LXI	D, H$Msg
	CALL	PSTRING
	LXI	D, OnDrive$MSG	;"on drive"
	CALL	PSTRING
	LDA	mTgt$Drive
	CALL	PHEX

NextCopy:
	LDA	mSrc$Drive	;Select source drive
	CALL	SELECTdrive
	LHLD	mSEC1		;Source track and sector
	SHLD	mSEC
	LHLD	mTRK1
	SHLD	mTRK
	CALL	wrlba		;Update LBA
	LXI	H, buffer	;Point to buffer
	SHLD	mDMA
	CALL	READSECTOR	;Read sector data into buffer
	
	LDA	mTgt$Drive	;Select target drive
	CALL	SELECTdrive
	LHLD	mSEC2		;Target track and sector
	SHLD	mSEC
	LHLD	mTRK2
	SHLD	mTRK
	CALL	wrlba		;Update LBA
	LXI	H, buffer	;Point to buffer
	SHLD	mDMA
	CALL	WRITESECTOR	;Write buffer data to target drive
	
	CALL	ZCSTS		;Any keyboard character will stop display
	CPI	01H		;CP/M says something is there
	JNZ	COPY$NEXTSEC1
	CALL	ZCI		;Flush character
	LXI	D, CONTINUE$MSG
	CALL	PSTRING		;Prompt for continue or ESC
	CALL	ZCI
	MVI	B, 40		;Clear continue message
	CALL	ZERA
	CPI	ESC
	JNZ	COPY$NEXTSEC1
	JMP	MAINLOOP

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

COPY$NextZero:
	LXI	H, 0		;Back to CP/M sector 0
	SHLD	mSEC1
	SHLD	mSEC2
	
	LHLD	mTRK1		;Get Current Track
	MOV	A, L
	CPI	MAXTRK		;Are we already at max track?
	JZ	COPY$NextDone	;Yes - done
	INX	H		;No - bump to next track
	SHLD	mTRK1

	LHLD	mTRK2
	INX	H
	SHLD	mTRK2
	JMP	NextCopy1

COPY$NextDone:	
	LXI	D, CopyDone	;Tell us we are all done
	CALL	PSTRING
	CALL	RET$DRV		;Return to original drive and position
	JMP	MAINLOOP

Cmp$Partn:			;Verify Partition
	LXI	D, PartnExpln
	CALL	PSTRING
	LXI	D, VerifyMsg
	CALL	PSTRING

	LXI	D, DoYouWant	;Is this what you want to do?
	CALL	PSTRING
	CALL	ZCI
	CALL	UPPER
	CPI	'Y'
	JNZ	MAINLOOP

	CALL	REM$DRV		;Remember current drive position

	CALL	ZCRLF
GtCmSD	CALL	GET$Src$Drive	;Ask user for source drive (00 or 01)
 	JNC	GtCmSP
	CPI	ESC
	JZ	MAINLOOP
	JMP	GtCmSD
GtCmSP:	CALL	GET$SrcPt$NUM	;Ask for source partition number (00-FF)
	JNC	GtCmTD
	CPI	ESC
	JZ	MAINLOOP
	LXI	D, INVALID$MSG
	CALL	PSTRING
	JMP	GtCmSP
GtCmTD:	CALL	GET$Tgt$Drive	;Ask for target drive (00 or 01)
	JNC	GtCmTP
	CPI	ESC
	JZ	MAINLOOP
	JMP	GtCmTD
GtCmTP:	CALL	GET$TgtPt$NUM	;Ask for target partition number (00-FF)
	JNC	GdCmIn
	CPI	ESC
	JZ	MAINLOOP
	LXI	D, INVALID$MSG
	CALL	PSTRING
	JMP	GtCmTP

GdCmIn:	LXI	D, ConfirmCmp	;Report: 'This will compare drive xx partition xxH to drive xx partition xxH.'
	CALL	PSTRING
	LDA	mSrc$Drive
	CALL	PHEX
	LXI	D, Partition
	CALL	PSTRING
	LDA	mSrc$Partn
	CALL	PHEX
	LXI	D, H$Msg
	CALL	PSTRING
	LXI	D, ToDrive
	CALL	PSTRING
	LDA	mTgt$Drive
	CALL	PHEX
	LXI	D, Partition
	CALL	PSTRING
	LDA	mTgt$Partn
	CALL	PHEX
	LXI	D, H$Msg
	CALL	PSTRING
	CALL	ZPERCRLF

	LXI	D, DoYouWant	;Is this what you want to do?
	CALL	PSTRING
	CALL	ZCI
	CALL	UPPER
	CPI	'Y'
	JNZ	MAINLOOP

	LXI	D, AreYouSure	;Are you sure?
	CALL	PSTRING
	CALL	ZCI
	CALL	UPPER
	CPI	'Y'
	JNZ	MAINLOOP
	
	CALL	ZCRLF
	CALL	ZCRLF

	LXI	H, 0		;Start with sector 0
	SHLD	mSEC1

	LHLD	mSrc$Partn	;Source partition
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	SHLD	mTRK1		;Converted to track

	LHLD	mTgt$Partn	;Target partition
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	DAD	H
	SHLD	mTRK2		;Converted to track
	
NextCmp1:	
	CALL	ZCR		;Display:
	LXI	D, VerifyTrk$MSG ;"Comparing track"
	CALL	PSTRING
	LDA	mTRK1+1		;High TRK byte
	CALL	PHEX
	LDA	mTRK1		;Low TRK byte
	CALL	PHEX
	LXI	D, H$Msg
	CALL	PSTRING
	LXI	D, OnDrive$MSG	;"on drive"
	CALL	PSTRING
	LDA	mSrc$Drive
	CALL	PHEX
	LXI	D, ToTrack$MSG	;"to track"
	CALL	PSTRING
	LDA	mTRK2+1		;High TRK byte
	CALL	PHEX
	LDA	mTRK2		;Low TRK byte
	CALL	PHEX
	LXI	D, H$Msg
	CALL	PSTRING
	LXI	D, OnDrive$MSG	;"on drive"
	CALL	PSTRING
	LDA	mTgt$Drive
	CALL	PHEX

NextCmp:	
	LDA	mSrc$Drive	;Select source drive
	CALL	SELECTdrive
	LHLD	mSEC1		;Source track and sector
	SHLD	mSEC
	LHLD	mTRK1
	SHLD	mTRK
	CALL	wrlba		;Update LBA
	LXI	H, buffer	;Point to buffer
	SHLD	mDMA
	CALL	READSECTOR	;Read sector data into buffer
	
	LDA	mTgt$Drive	;Select target drive
	CALL	SELECTdrive
	LHLD	mSEC2		;Target track and sector
	SHLD	mSEC
	LHLD	mTRK2
	SHLD	mTRK
	CALL	wrlba		;Update LBA
	LXI	H, buffer2	;Point to buffer2
	SHLD	mDMA
	CALL	READSECTOR	;Read sector data into buffer2

	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 Track byte
	CALL	PHEX
	LDA	mTRK		;Low Track byte
	CALL	PHEX
	LXI	D, SEC$Msg
	CALL	PSTRING
	LDA	mSEC+1		;High Sector byte
	CALL	PHEX
	LDA	mSEC		;Low 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		;CP/M says something is there
	JNZ	CMP$NEXTSEC1
	CALL	ZCI		;Flush character
VER$OK1:
	LXI	D, CONTINUE$MSG
	CALL	PSTRING
	CALL	ZCI
	CPI	ESC
	JNZ	CMP$NEXTSEC1
	JMP	MAINLOOP

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

CMP$NEXTZero:
	LXI	H, 0		;Back to CP/M sector 0
	SHLD	mSEC
	SHLD	mSEC2
	
	LHLD	mTRK1		;Get Current track
	MOV	A, L
	CPI	MAXTRK		;Are we already at max
	JZ	CMP$NEXTDone	;Yes - all done
	INX	H		;no - bump to next track
	SHLD	mTRK1

	LHLD	mTRK2
	INX	H
	SHLD	mTRK2
	JMP	NextCmp1

CMP$NextDone:	
	LXI	D, VerifyDone	;Tell us we are all done.
	CALL	PSTRING
	CALL	RET$DRV		;Return to original drive and position
	JMP	MAINLOOP

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

Re: IDE interface for Altair 8800c

Post by Wayne Parham »

Concatenate this section with the previous to make the full program.

Code: Select all

;----------------------------- 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	ZCR		;Return to beginning of line
	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		;CP/M 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 CP/M 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		;Remember current drive position
	CALL	SELECT0
	CALL	PRN$DRV$INFO
	CALL	IDEinit
	CALL	RET$DRV		;Return to original drive and position
	JMP	MAINLOOP

PRN$1$INFO:			;Print Drive 1 identification info
	LXI	D, DRIVE1$INFO
	CALL	PSTRING
	CALL	REM$DRV		;Remember current drive position
	CALL	SELECT1
	CALL	PRN$DRV$INFO
	CALL	IDEinit
	CALL	RET$DRV		;Return to original drive and position
	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		;---- CP/M 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:				;Print CRLF
	PUSH	PSW
	MVI	C, CR
	CALL	ZCO
	MVI	C, LF
	CALL	ZCO
	POP	PSW
	RET

ZPERCRLF:			;Print period and then CRLF
	PUSH	PSW
	MVI	C, PERIOD
	CALL	ZCO
	MVI	C, CR
	CALL	ZCO
	MVI	C, LF
	CALL	ZCO
	POP	PSW
	RET

ZCR:				;Return to beginning of line
	MVI	C, CR
	CALL	ZCO
	RET

ZERA:				;Return to beginning of line and erase [B] characters
	MVI	C, CR
	CALL	ZCO
	MVI	C, SPACE
ERAX:	CALL	ZCO
	DCR	B
	JNZ	ERAX
	MVI	C, CR
	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 CP/M Track & Sector to LBA format
	LXI     D,ENTER$SECH
	CALL	PSTRING
	CALL	GETHEX		;Enter high byte sector number
	RC
	STA	mSEC+1
	CALL	ZCRLF

	LXI     D,ENTER$SECL
	CALL	PSTRING
	CALL	GETHEX		;Enter low byte sector number
	RC
	STA	mSEC
	CALL	ZCRLF

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

	LXI     D, ENTER$TRKL
	CALL	PSTRING
	CALL	GETHEX		;Enter low byte track number
	RC
	STA	mTRK
	CALL	ZCRLF

	XRA	A
	ORA	A		;Clear Accumulator and Carry bit
	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 CP/M sector to max CP/M 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)

GET$BkPt$NUM:			;Ask user for backup partition number (01-FF)
	LXI	D, Enter$BkupPart
	CALL	PSTRING
	CALL	GETHEX		;Get 2 HEX digits
	RC
	STA	mPART$NUM
	CALL	ZCRLF
	RET

GET$SrcPt$NUM:			;Ask user for source partition number (00-FF)
	LXI	D, Enter$SrcPartn
	CALL	PSTRING
	CALL	GETHEX		;Get 2 HEX digits
	RC
	STA	mSrc$Partn
	CALL	ZCRLF
	RET

GET$TgtPt$NUM:			;Ask user for target partition number (00-FF)
	LXI	D, Enter$Tgt$Partn
	CALL	PSTRING
	CALL	GETHEX		;Get 2 HEX digits
	RC
	STA	mTgt$Partn
	CALL	ZCRLF
	RET

GET$Src$Drive:			;Ask user for source drive (00 or 01)
	LXI	D, Enter$SrcDrive
	CALL	PSTRING
	CALL	GETHEX		;Get 2 HEX digits
	JNC	GdSrDin
	CPI	ESC		;Return if ESC key pressed
	STC
	RZ
	LXI	D, INVALID$MSG	;Re-prompt if input is invalid
	CALL	PSTRING
	JMP	GET$Src$Drive
GdSrDin:
	CALL	Val$Drive	;Verify that drive is valid
	RC	
	STA	mSrc$Drive
	CALL	ZCRLF
	RET

GET$Tgt$Drive:			;Ask user for target drive (00 or 01)
	LXI	D, Enter$TgtDrive
	CALL	PSTRING
	CALL	GETHEX		;Get 2 HEX digits
	JNC	GdTgDin
	CPI	ESC		;Return if ESC key pressed
	STC
	RZ
	LXI	D, INVALID$MSG	;Re-prompt if input is invalid
	CALL	PSTRING
	JMP	GET$Tgt$Drive
GdTgDin:
	CALL	Val$Drive	;Is drive valid?
	RC	
	STA	mTgt$Drive
	CALL	ZCRLF
	RET

Val$Drive:			;Check if drive [A] is valid
	LHLD	mLast$Drive
	INX	H
	CMP	L		;Is drive valid?
	JC	Vdone
	LXI	D, DRV$NOT$FOUND
	CALL	PSTRING
Vdone:	CMC
	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
	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:			;Select drive 0
	XRA	A
	JMP	SELECTdrive

SELECT1:			;Select drive 1
	MVI	A, 1

SELECTdrive:			;Select drive [A]
	STA	mCURRENT$DRIVE
	OUT	IDEDrive	
	RET

REM$DRV:			;Remember drive and position
	LDA	mCURRENT$DRIVE
	STA	mREM$DRIVE
	LHLD	mSEC
	SHLD	mREM$SEC
	LHLD	mTRK
	SHLD	mREM$TRK
	RET

RET$DRV:			;Return to last drive and position
	LDA	mREM$DRIVE
	STA	mCURRENT$DRIVE
	OUT	IDEDrive	
	LHLD	mREM$SEC
	SHLD	mSEC
	LHLD	mREM$TRK
	SHLD	mTRK
	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 CP/M 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:

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

SIGN$ON:	DB	CR,LF,'IDE Disk Drive Utility Program  v3.0  12-21-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        (O) Drive 0 Information   '
		DB	'(H) Backup Disk',CR,LF
		DB	'(B) Select Drive 1        (I) Drive 1 Information   '
		DB	'(G) Restore Backup',CR,LF
		DB	'(K) Set LBA by Partition  (M) Show Buffer w/o Read  '
		DB	'(E) Clear Buffer',CR,LF
		DB	'(L) Set LBA Track, Sector (R) Read Sector to Buffer '
		DB	'(W) Write Buffer to Sector',CR,LF
		DB	'(N) Next Sector           (Y) Read N Sectors        '
		DB	'(X) Write N Sectors',CR,LF
		DB	'(P) Previous Sector       (S) Sequental Sector Read '
		DB	'(C) Copy Partition',CR,LF
		DB	'(U) Power Up              (T) Power Down            '
		DB	'(V) Verify Partition',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        (O) Drive 0 Information   '
		DB	'(H) Backup Disk',CR,LF
		DB	'(B) Select Drive 1        (I) Drive 1 Information   '
		DB	'(G) Restore Backup',CR,LF
		DB	'(K) Set LBA by Partition  (M) Show Buffer w/o Read  '
		DB	'(E) Clear Buffer',CR,LF
		DB	'(L) Set LBA Track, Sector (R) Read Sector to Buffer '
		DB	'(W) Write Buffer to Sector',CR,LF
		DB	'(N) Next Sector           (Y) Read N Sectors        '
		DB	'(X) Write N Sectors',CR,LF
		DB	'(P) Previous Sector       (S) Sequental Sector Read '
		DB	'(C) Copy Partition',CR,LF
		DB	'(U) Power Up              (T) Power Down            '
		DB	'(V) Verify Partition',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? $'
AreYouSure	DB	CR,LF,'Are you sure? $'
DoYouWant	DB	CR,LF,'Is that what you want to do? $'
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	'Sector number (LOW byte, xxH) = $'
ENTER$SECH	DB	'Sector number (HIGH byte, 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.$'
ReadN$MSG	DB	CR,LF,'Read multiple sectors from current drive to RAM buffer.'
		DB	CR,LF,'How many 512 byte sectors (xx HEX):$'
WriteN$MSG	DB	CR,LF,'Write multiple sectors from RAM buffer to current drive.'
		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,'$'
PartnExpln	DB	CR,LF,'Each 2Gb physical disk is structured as 256'
		DB	' "partitions" of 8Mb each.  The CP/M'
		DB	CR,LF,'operating system can directly access only'
		DB	' partition 00, but all the others can'
		DB	CR,LF,'be used as backups or archives.  The backup'
		DB	' partitions are numbered 00 - FF.',CR,LF,'$'
BackupMsg	DB	CR,LF,'This will copy data from the main CP/M'
		DB	' partition on the current drive to a'
		DB	CR,LF,'backup partition.',CR,LF,'$'
RestoreMsg	DB	CR,LF,'This will restore data from a backup'
		DB	' partition to the main CP/M partition on'
		DB	CR,LF,'the current drive.',CR,LF,'$'
CopyMsg		DB	CR,LF,'This will copy data from any partition to'
		DB	' any other partition on either drive.',CR,LF,'$'
Enter$Partition	DB	CR,LF,LF,'Choose a partition number (00-FF) $'
Enter$Bkup$Part	DB	CR,LF,LF,'Choose a backup partition (01-FF) $'
Enter$Src$Partn	DB	CR,LF,'Choose source partition (00-FF) $'
Enter$Tgt$Partn	DB	CR,LF,'Choose target partition (00-FF) $'
Enter$Src$Drive	DB	CR,LF,'Choose source drive (00 or 01) $'
Enter$Tgt$Drive	DB	CR,LF,'Choose target drive (00 or 01) $'
ConfirmCopy	DB	CR,LF,'This will copy drive $'
ConfirmCmp	DB	CR,LF,'This will compare drive $'
Partition	DB	' partition $'
ToDrive		DB	' to drive $'
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$'
RestoreDone	DB	CR,LF,'Restore of disk data from backup partition complete.',CR,LF,'$'
DRV$NOT$FOUND	DB	CR,LF,LF,'Drive not connected.',CR,LF,'$'
RANGE$MSG	DB	CR,LF,LF,'Value out of range.',CR,LF,'$'
INVALID$MSG	DB	CR,LF,LF,'Invalid input.',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? $'
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$'		
CopyDone	DB	CR,LF,LF,'Partition copy complete.',CR,LF,'$'
CopyTrk$MSG	DB	'Copying track $'
OnDrive$MSG	DB	' on drive $'
ToTrack$MSG	DB	' to track $'
VerifyMsg	DB	CR,LF,'This will compare any two partitions on either drive'
		DB	' and will report any',CR,LF,'differences.',CR,LF,'$'
VerifyTrk$MSG	DB	'Comparing track $'
VerifyDone	DB	CR,LF,LF,'Partition verification 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
mSrc$Drive	DB	0H		;User-inputs for copy and restore commands
mSrc$Partn	DB	0H
mTgt$Drive	DB	0H
mTgt$Partn	DB	0H
mPART$NUM	DB	0H		;Backup partition (01-FF)
mStartLineHex	DW	0H
mStartLineASCII	DW	0H
mBYTE$COUNT	DW	0H
mSECTOR$COUNT	DW	0H
mDELAYStore	DB	0H
mCURRENT$DRIVE	DB	0H
mREM$DRIVE	DB	0H
mREM$SEC	DW	0H
mREM$TRK	DW	0H
mLast$Drive	DB	0H		;0 or 1

		DS	100H		;Stack is 256 bytes, just before buffers
STACK:		DW	0H

		DB	'          Start of ID buffer-->'
IDbuffer:	DS	512		;IDbuffer is 512 bytes with text before and after
		DB	'<--End of ID buffer            '

		ORG	BUFFER$ORG

BUFFER:		DB	'>--Start buffer' 
		DS	481		;buffer is 512 bytes total
		DB	'End of buffer--<'

BUFFER2:	DB	'>--Start buffer2'
		DS	479		;buffer2 is 512 bytes total
		DB	'End of buffer2--<'

END
Post Reply

Who is online

Users browsing this forum: Amazon [Bot] and 2 guests