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:

IDE interface for Altair 8800c

Post by Wayne Parham »

If you're lookin' for an IDE interface, IDE drive and/or flash drive interface for your Altair, you've come to the right place.

The top of the ninth page in this thread has code you can download - CP/M 3.0 that supports flash drive and hard disk and IDEutil.com that formats it as well as other things - so jump past everything and go straight there if you want software downloads.

The posts below describe the software coding effort, so if you're interested in that, read on. It starts off saying the code isn't done, but it is now. Just wasn't when I started. :-)

=======================================================================================================================

I've been kicking around an IDE interface for about six weeks now, so I decided to do a little show and tell.

But this is a work in progress, not a fait accompli. So it's not ready for prime time yet.

Still, maybe you'll find it interesting to watch me unroll this ball of twine. Start the popcorn...

I purchased a couple of John Monahan's IDE boards from S100computers.com. I built them, plugged 'em into a slot, powered up and watched for smoke. No smoke, so that's good. What good luck!

Ran Monahan's test code, and it immediately complained that no drives were present. What bad luck.

So I wrote a little port I/O program to hit the board and let me watch test signals. It asserted all the right signals on the board, and I could see proper access. What good luck!

I decided it might be best to write Monahan to see if he had any suggestions. But he said he was super-busy, and didn't have time to respond. What bad luck.

My first stab was to add some code to output status at strategic places in Monahan's test program, but after a while, decided to re-write some of it in C. For one thing, the re-write would help me understand what the code was doing, and for another thing, some stuff is a little easier for me to do in C than in assembler. My goal is to understand the flow, and to find out what's wrong. When I learn that, I'll update the assembly program so that it works in the Altair.

Once I've done that, transferring the interface code into CP/M BIOS isn't terribly difficult, because the test I/O code is almost exactly what's in BIOS. The only bridge left to cross (or burn) is available memory, 'cause the interface code will increase the size of BIOS. We won't benefit from a big old disk drive if the resulting operating system only has 10Kb TPA.

What I found was the hardware and software interface Monahan chose is an implementation of an interface described by Peter Faasse, with documentation maintained by Wesley Moore. The hardware and software Monahan uses is virtually identical to what is described by Faasse. Others have chosen to use this mechanism on various other platforms too. So that helps when trying to troubleshoot the system - There's not a total vacuum of information on the subject.

So here's what I've got. My updated IDEutil code - the assembly language utility - shows the IDE interface returns status of 0x50 after initialization. It isn't able to retrieve the disk identification string, but it can at least retrieve valid status after initialization. What good luck!

The reason I know that's good is because my IDEtest program - written in C - gives the same status when it runs. That's what we want, because the two bits set that form the 0x50 status are saying that the last command was successful and that the disk is ready. The C program also successfully retrieves the disk identification string, telling me the drive name, firmware revision and number of cylinders, sectors and heads. Even more good luck!

If you look through the IDEtest program, you'll see I included a bunch of configurable delays. My initial thought was I might need to set those to some specific values. I still kind of think that. But I ended up setting them all with a fairly large delay and dialing them back, ultimately setting them all to zero and the program still works. So I'm not sure yet what that means, whether there are some additional delays in the compiled binary that aren't expressed in source - stuff like the inevitable delays caused by stack operations - or if it's something else. I'll probably compile the C code to assembler and then link in the hand-assembled functions of the low-level routines to rule that out.

I'll keep you posted on this thread.
Last edited by Wayne Parham on March 1st, 2024, 5:52 pm, edited 6 times in total.
Wayne Parham
Posts: 253
Joined: March 18th, 2022, 3:01 pm
Contact:

Re: IDE interface for Altair 8800c

Post by Wayne Parham »

IDEutil.asm

--- Note: This version is non-functional on the Altair. It cannot obtain drive ID. ---

Code: Select all

;
; Utility Program for IDE interface board
;------------------------------------------------------------------------------
;	V2.0	01/23/2011	;Updated to accommodate two CF cards
;	V2.1	02/05/2011	;Menu driven, and added code to copy & restore
;	V2.2	02/13/2011	;Added Sec++ & Sec--
;	V2.3	02/15/2011	;Re-did drive initilization
;	V2.4	02/16/2011	;Correct error for end of drive track check
;	V2.5	03/14/2011	;Added BOOT CPM option, cleaned up some areas
;	V2.6	03/15/2011	;Correct CPM boot to Track 0 sector 1
;	V2.7	04/26/2011	;Add code for two drive system	
;	V2.8	04/27/2011	;Format sectors with E5 and add warning message
;	V2.9	03/28/2011	;Fixed initialization hanging if no drive present
;	V2.9a   10/10/2022	;Added initialization info for troubleshooting
;------------------------------------------------------------------------------


;------------------------------------------------------------------------------
;Build equates:
;------------------------------------------------------------------------------
FALSE		EQU	0
TRUE		EQU	NOT FALSE

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


;------------------------------------------------------------------------------
;Drive number equates:
;------------------------------------------------------------------------------
IDE0		EQU	0	;Physical disk 0 drive number assignment
IDE1		EQU	1	;Physical disk 1 drive number assignment


;------------------------------------------------------------------------------
;Console equates:
;------------------------------------------------------------------------------
CONI		EQU	10H	;Console input port
CONO		EQU	11H	;Console output port


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


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

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

;------------------------------------------------------------------------------
;IDE control lines for use with IDEportC.  
;------------------------------------------------------------------------------
IDEa0line	EQU	01H	;direct from 8255 to IDE interface
IDEa1line	EQU	02H	;direct from 8255 to IDE interface
IDEa2line	EQU	04H	;direct from 8255 to IDE interface
IDEcs0line	EQU	08H	;inverter between 8255 and IDE interface
IDEcs1line	EQU	10H	;inverter between 8255 and IDE interface
IDEwrline	EQU	20H	;inverter between 8255 and IDE interface
IDErdline	EQU	40H	;inverter between 8255 and IDE interface
IDErstline	EQU	80H	;inverter between 8255 and IDE interface

;------------------------------------------------------------------------------
;Symbolic constants for the IDE drive registers
;------------------------------------------------------------------------------
REGdata		EQU	IDEcs0line
REGerr		EQU	IDEcs0line + IDEa0line
REGseccnt	EQU	IDEcs0line + IDEa1line
REGsector	EQU	IDEcs0line + IDEa1line + IDEa0line
REGcylinderLSB	EQU	IDEcs0line + IDEa2line
REGcylinderMSB	EQU	IDEcs0line + IDEa2line + IDEa0line
REGshd		EQU	IDEcs0line + IDEa2line + IDEa1line
REGcommand	EQU	IDEcs0line + IDEa2line + IDEa1line + IDEa0line
REGstatus	EQU	IDEcs0line + IDEa2line + IDEa1line + IDEa0line
REGcontrol	EQU	IDEcs1line + IDEa2line + IDEa1line
REGastatus	EQU	IDEcs1line + IDEa2line + IDEa1line + IDEa0line

;------------------------------------------------------------------------------
;IDE Command Constants.  These should never change.
;------------------------------------------------------------------------------
COMMANDrecal	EQU	10H
COMMANDread	EQU	20H
COMMANDwrite	EQU	30H
COMMANDinit	EQU	91H
COMMANDid	EQU	0ECH
COMMANDspindown	EQU	0E0H
COMMANDspinup	EQU	0E1H

;------------------------------------------------------------------------------
;IDE Status Register:
;------------------------------------------------------------------------------
;  bit 7: Busy	1=busy, 0=not busy
;  bit 6: Ready 1=ready for command, 0=not ready yet
;  bit 5: DF	1=fault occured insIDE drive
;  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 occured
;  bit 1: IDX	vendor specific
;  bit 0: ERR	1=error occured
;------------------------------------------------------------------------------

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

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

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


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


	ORG	100H		;<--- For CPM

begin:
	LXI	SP,STACK
	LXI     D,SIGN$ON	;print a welcome message
	CALL	PSTRING
	JMP	OVER$TBL
	
				;COMMAND BRANCH TABLE
TBL:	DW  DRIVE$A   ; "A"  Select Drive 0
	DW  DRIVE$B   ; "B"  Select Drive 1
	DW  CPMBOOT   ; "C"  LOAD CPM (if present)
	DW  DISPLAY   ; "D"  Sector contents display:- ON/OFF
	DW  RAMCLEAR  ; "E"  Clear RAM buffer
	DW  FORMAT    ; "F"  Format current disk
	DW  RESTORE   ; "G"  Restore backup
	DW  BACKUP    ; "H"  Backup partition
	DW  NEXT$SECT ; "I"  Next Sector
	DW  PREV$SEC  ; "J"  Previous sector
	DW  ERROR     ; "K"  
	DW  SET$LBA   ; "L"  Set LBA value (Set Track, sector)  
	DW  ERROR     ; "M"  
	DW  POWER$DOWN; "N"  Power down hard disk command
	DW  ERROR     ; "O"  
	DW  ERROR     ; "P"  
	DW  ERROR     ; "Q"  
	DW  READ$SEC  ; "R"  Read sector to data buffer
	DW  SEQ$RD    ; "S"  Sequental sec read and display contents
	DW  ERROR     ; "T"  
	DW  POWER$UP  ; "U"  Power up hard disk command
	DW  N$RD$SEC  ; "V"  Read N sectors
	DW  WRITE$SEC ; "W"  Write data buffer to current sector
	DW  N$WR$SEC  ; "X"  Write N sectors
	DW  COPY$AB   ; "Y"  Copy Drive 0 to Drive 1
	DW  VERIFY$AB ; "Z"  Verify Drive 0:= Drive 1:
	
OVER$TBL:
	LXI	D,SEL0MSG	;Print select drive 0 message.
	CALL	PSTRING
	MVI	A,IDE0
	STA	@CURRENT$DRIVE
	OUT	IDEDrive	;Select first drive.
	
	CALL	CLEAR$ID$BUFFER	;Clear ID Buffer.
	
	CALL	IDEinit		;Initialize the board and first drive.
	JZ	DRIVE$1OK	;Continue on Zero.
	
	LXI	D,INIT$1$ERROR	;Non-zero is error, probably no drive.	
	CALL	PSTRING
	JMP	ABORT
	
DRIVE$1OK:			;Select second drive. 
	LXI	D,SEL1MSG	;Print select drive 1 message.
	CALL	PSTRING
	MVI	A,IDE1		
	STA	@CURRENT$DRIVE
	OUT	IDEDrive	

	CALL	CLEAR$ID$BUFFER	;Clear ID Buffer.
					
	CALL	IDEinit		;Initialize the second drive.
	JZ	INIT$OK		;Continue on Zero.

	LXI	D,INIT$2$ERROR	;Non-zero is error, so print warning
	CALL	PSTRING
	JMP	INIT$OK		;...and continue.
	
ABORT:
  IF	CPM
	MVI	C,RESET$DISK	;Reset All disks in CPM
	JMP	0FF00H		;Reboot	CPM
  ELSE
	JMP	0F800H		;Transfer control to Monitor ROM	
  ENDIF
 
INIT$OK:			
	MVI	A,IDE0
	STA	@CURRENT$DRIVE
	OUT	IDEDrive	;Select drive 0 at start

	CALL	driveid		;Get the drive ID info.
	JZ	INIT$OK1
	
	LXI	D,ID$ERROR
	CALL	PSTRING
	JMP	ABORT

INIT$OK1:			;Print the drive #0 model number etc.
	LXI     H,IDbuffer + 12
	MOV	A,M		;If there are zero sectors then something's wrong
	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
	CALL	PSTRING
	JMP	ABORT		;No drive #0 so abort

INIT$OK2:
	LXI	D,DRIVE$INFO
	CALL	PSTRING
	LXI     D, msgmdl	
	CALL	PSTRING
	LXI     H,IDbuffer + 54
	MVI	B,10		;character count in words
	CALL	printname	;Print [HL], [B] X 2 characters
	CALL	ZCRLF
				;print the drive's serial number
	LXI     D, msgsn
	CALL	PSTRING
	LXI     H,IDbuffer + 20
	MVI	B, 5		;Character count in words
	CALL	printname
	CALL	ZCRLF
				;Print the drive's firmware revision string
	LXI     D, msgrev
	CALL	PSTRING
	LXI     H,IDbuffer + 46
	MVI	B, 2
	CALL	printname	;Character count in words
	CALL	ZCRLF
				;Print the drive specs (cyl/hd/sect)
	LXI     D, msgcy
	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
				;Default position will be first block 
	LXI	H,0
	SHLD	@SEC		;Default to Track 0, Sec 0
	SHLD	@TRK
	LXI	H,buffer	;Set DMA address to buffer
	SHLD	@DMA

	CALL	IDEinit


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


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


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

	CALL	READSECTOR

	JZ	main1b		;Z means the sector read was OK
	CALL	ZCRLF
	JMP	MAINLOOP
main1b:	LXI     D, msgrd	;Sector read OK
	CALL	PSTRING

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

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

	LXI	H,buffer	;Point to buffer
	SHLD	@DMA

	CALL	WRITESECTOR

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


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

NEXT$SECT:
	LDA	@SEC
	INR	A		
	CPI	MAXSEC-1
	JNC	RANGE$ERROR
	STA	@SEC
	CALL	wrlba		;Update LBA on drive
	CALL	ZCRLF
	jmp	MAINLOOP
RANGE$ERROR:
	LXI     D,RANGE$MSG	
	CALL	PSTRING
	jmp	MAINLOOP
	
PREV$SEC:
	LDA	@SEC
	ORA	A
	JZ	RANGE$ERROR
	DCR	A
	STA	@SEC
	CALL	wrlba		;Update LBA on drive
	CALL	ZCRLF
	jmp	MAINLOOP
	
POWER$UP:			;Set the drive to spin up
	CALL	spinup
	jmp	MAINLOOP

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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


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

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

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

NextWSec:	
	LXI	D,WritingN$MSG
	CALL	PSTRING
	CALL	wrlba		;Update LBA on drive
	CALL	DISPLAYposition	;Display current track, sector, head
	
	LHLD	@DMA
	CALL	WRITESECTOR	;Actully, Sector/track values are already updated
	SHLD	@DMA		;in wrlba, but WRITESECTOR is used in multiple places.
				;A repeat does no harm -- speed is not an issue here
	LDA	@SECTOR$COUNT
	DCR	A
	STA	@SECTOR$COUNT
	JZ	MAINLOOP
	
	LHLD	@SEC
	INX	H
	SHLD	@SEC	
	MOV	A,L		;0 to 62 CPM Sectors
	CPI	MAXSEC-1
	JNZ	NextWSec

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


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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

	CALL	wrlba		;Update LBA on "0:" drive

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

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

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

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

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

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

	CALL	wrlba		;Update LBA on "0:" drive

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

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

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

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

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

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

				
driveid:CALL	IDEwaitnotbusy	;Do the identify drive command, return buffer
				;filled with info about the drive
	RC			;If Busy return NZ
	MVI	D,COMMANDid
	MVI	E,REGcommand
	CALL	IDEwr8D		;issue the command

	CALL	IDEwaitdrq	;Wait for Busy=0, DRQ=1
	JC	SHOWerrors

	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:
	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:		
	CALL	IDEwaitnotbusy	;Sequentially read sectors from current position
	JC	SHOWerrors
	CALL	ZCRLF
NEXTSEC:
	LXI	H,buffer	;Point to buffer
	SHLD	@DMA

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

	LXI	H,buffer	;Point to buffer
	SHLD	@DMA

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

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

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


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

printname:			;Send text up to [B]	
	INX	H		;Text is low byte high byte format
	MOV	C,M
	CALL	ZCO	
	DCX	H
	MOV	C,M
	CALL	ZCO
	INX	H
	INX	H
	DCR	B
	JNZ	printname
	RET

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

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

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

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

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

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

;------------------------------------------------------------------------------	
;Print a string in [DE] up to '$'
;------------------------------------------------------------------------------	
PSTRING:
  IF CPM
	MVI	C,PRINT
	JMP	BDOS		;PRINT MESSAGE, 
  ELSE
	PUSH	B
	PUSH	D
	PUSH	H
	XCHG
PSTRX:	MOV	A,M
	CPI	'$'
	JZ	DONEP
	MOV	C,A
	CALL	ZCO
	INX	H
	JMP	PSTRX
DONEP:	POP	H
	POP	D
	POP	B
	RET
  ENDIF

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

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

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

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

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

;------------------------------------------------------------------------------	
;Print a 16-bit number in RAM located @ [HL], low-byte first for Drive ID 
;------------------------------------------------------------------------------	
printparm:
	INX	H	;Index to high byte first
	MOV	a,M
	CALL	PHEX
	DCX	H	;Now low byte
	MOV	a,M
	CALL	PHEX
	RET

;------------------------------------------------------------------------------	
;Print an 8 bit number located in [A] 
;------------------------------------------------------------------------------	
PHEX:	PUSH	PSW
	PUSH	B
	PUSH	PSW
	RRC
	RRC
	RRC
	RRC
	CALL	ZCONV
	POP	PSW
	CALL	ZCONV
	POP	B
	POP	PSW
	RET

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

;------------------------------------------------------------------------------	
;Display binary in [A] 
;------------------------------------------------------------------------------	
ZBITS:	PUSH	PSW
	PUSH	B
	PUSH	D
	MOV	E,A		
	MVI	B,8
BQ2:	DB	0CBH,23H	
	SLAR	E
	MVI	A,18H
	ADC	A
	MOV	C,A
	CALL	ZCO
	DJNZ	BQ2
	POP	D
	POP	B
	POP	PSW
	RET

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

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

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

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

;------------------------------------------------------------------------------	
;Get a character from the keyboard, convert to uppercase and echo it 
;------------------------------------------------------------------------------	
GETCMD:	CALL	ZCI		;Get character
	CALL	UPPER
	CPI	ESC
	RZ			;Don't echo an ESC
  IF NOT CPM
	PUSH	PSW		;Save it
	PUSH	B
 	MOV	C,A
	CALL	ZCO		;Echo it
	POP	B
	POP	PSW		;get it back
  ENDIF
	RET

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

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

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

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

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

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

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

SF598:	CALL	ZCONV
	RET

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

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

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

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

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

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

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

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

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

				;Read a sector, specified by the 3 bytes in LBA
				;Z on success, NZ call error routine if problem
READSECTOR:
	CALL	wrlba		;Tell which sector we want to read from.
				;Translate first in case of an error, otherewise 
				;we will get stuck on bad sector 
	CALL	IDEwaitnotbusy	;Make sure drive is ready
	JC	SHOWerrors	;Returned with NZ set if error

	MVI	D,COMMANDread
	MVI	E,REGcommand
	CALL	IDEwr8D		;Send sec read command to drive.
	CALL	IDEwaitdrq	;Wait until it's got the data
	JC	SHOWerrors
		
	LHLD  	@DMA		;DMA address
	MVI	B,0		;Read 512 bytes to [HL]
MoreRD16:
	MVI	A,REGdata	;REG regsiter 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
	DJNZ	MoreRD16

	MVI	E,REGstatus
	CALL	IDErd8D
	MOV	A,D
	ANI	1H
	CNZ	SHOWerrors	;If error display status
	RET
				;Write a sector, specified by the 3 bytes in LBA 
				;Z on success, NZ to error routine if problem
WRITESECTOR:
	CALL	wrlba		;Tell which sector we want to read from.
				;Translate first in case of an error, otherewise 
				;we will get stuck on bad sector 
	CALL	IDEwaitnotbusy	;Make sure drive is ready
	JC	SHOWerrors

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

	LHLD    @DMA
	MVI	B,0

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

	MVI	A,REGdata
	PUSH	PSW
	OUT	IDEportC	;Send write command
	ORI	IDEwrline	;Send WR pulse
	OUT	IDEportC
	POP	PSW
	OUT	IDEportC
	DJNZ	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

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

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

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

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

MoreWait:
	MVI	E,REGstatus	;wait for RDY bit to be set
	CALL	IDErd8D
	MOV	A,D
	ANI	11000000B
	XRI	01000000B
	JZ	DoneNotbusy
	DCR	B	
	JNZ	MoreWait
	LDA	@DELAYStore	;Check timeout delay
	DCR	A
	STA	@DELAYStore
	JNZ	MoreWait
	STC			;Set carry to indicate an error
	ret
DoneNotBusy:
	ORA	A		;Clear carry it indicate no error
	RET
				;Wait for the drive to be ready to transfer data.
				;Returns the drive's status in Acc
IDEwaitdrq:
	MVI	B,0FFH
	MVI	A,0FFH		;Delay must be above 80H, longer for slow drives
	STA	@DELAYStore

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

CLEAR$ID$BUFFER:		;Clear the ID Buffer area
	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	;Put in 0's 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.
;------------------------------------------------------------------------------	

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

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 10/10/2022 (v2.9a)',CR,LF,LF
		DB	'CPM Track, Sectors --> LBA mode',LF,CR
		DB	'Initilizing IDE Board, one moment please...',CR,LF,'$'
SEL0MSG		DB	'Selecting first IDE drive.',CR,LF,'$'
SEL1MSG		DB	'Selecting second IDE drive.',CR,LF,'$'
INITDRIVE	DB	'Initializing drive.  $'
DISKSTATUS	DB	'Status is $'
INIT$1$ERROR:	DB	'Initilizing of First Drive failed. Aborting Program.',BELL,CR,LF,LF,'$'
INIT$2$ERROR	DB	'Initilizing 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 Initilized OK.',CR,LF,LF,'$'
BAD$DRIVE:	DB	CR,LF,'First Drive ID Information appears invalid.',CR,LF
		DB	'Aborting program.',BELL,CR,LF,LF,'$'

DRIVE$INFO:	DB	'Drive #0 ID Parameter Information:-',CR,LF,'$'
msgmdl:		DB	'Model: $'
msgsn:		DB	'S/N:   $'
msgrev:		DB	'Rev:   $'
msgcy:		DB	'Cylinders: $'
msghd:		DB	', Heads: $'
msgsc:		DB	', Sectors: $'
msgCPMTRK:	DB	'CPM TRK = $'
msgCPMSEC:	DB	' CPM SEC = $'
msgLBA:		DB	'  (LBA = 00$'
MSGBracket	DB	')$'

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

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

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

		ORG	BUFFER$ORG

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

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

		DS	100H
STACK		DW	0H


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

Re: IDE interface for Altair 8800c

Post by Wayne Parham »

IDEtest.c

--- Note: This version IS functional on the Altair. It CAN obtain drive ID. ---

Code: Select all

/* -------------------------------------------------------------------------- */
/* IDE Test Program                                                           */
/*                                                                            */
/* Wayne Parham                                                               */
/*                                                                            */
/* wayne@parhamdata.com                                                       */
/* -------------------------------------------------------------------------- */


#include "stdio.h"

/*
;-------------------------------------------------------------------------------
;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 & MSDOS.
;
;-------------------------------------------------------------------------------
*/

#define FALSE            0x00
#define TRUE             0xFF

#define quiet            0x00
#define verbose          0xFF

#define IDEpA            0x30              /* Lower 8 bits of IDE interface   */
#define IDEpB            0x31              /* Upper 8 bits of IDE interface   */
#define IDEpC            0x32              /* Control lines for IDE interface */
#define IDEpCtrl         0x33              /* 8255 configuration port         */
#define IDEDrSel         0x34              /* Bit zero chooses drive 0 or 1   */

#define READcfg8255      0x92              /* IDE ports A & B input, C output */
#define WRITEcfg8255     0x80              /* IDE ports A, B and C output     */

#define IDEa0Line        0x01              /* IDE bus A0                      */
#define IDEa1Line        0x02              /* IDE bus A1                      */
#define IDEa2Line        0x04              /* IDE bus A2                      */
#define IDEcs0line       0x08              /* IDE bus /CS0                    */
#define IDEcs1Line       0x10              /* IDE bus /CS1                    */
#define IDEwrLine        0x20              /* IDE bus /WR                     */
#define IDErdLine        0x40              /* IDE bus /RD                     */
#define IDErstLine       0x80              /* IDE bus /RESET                  */

#define REGdata          0x08              /* /CS0                            */
#define REGerr           0x09              /* /CS0 + A0                       */
#define REGsecCnt        0x0A              /* /CS0 + A1                       */
#define REGsector        0x0B              /* /CS0 + A1 + A0                  */
#define REGcylLSB        0x0C              /* /CS0 + A2                       */
#define REGcylMSB        0x0D              /* /CS0 + A2 + A0                  */
#define REGshd           0x0E              /* /CS0 + A2 + A1                  */
#define REGcmd           0x0F              /* /CS0 + A2 + A1 + A0             */
#define REGstat          0x0F              /* /CS0 + A2 + A1 + A0             */
#define REGctrl          0x16              /* /CS1 + A2 + A1                  */
#define REGaStat         0x17              /* /CS1                            */

#define CMDrecal         0x10              /* Recalibrate command             */
#define CMDread          0x20              /* Read command                    */
#define CMDwrite         0x30              /* Write command                   */
#define CMDinit          0x91              /* Init command                    */
#define CMDid            0xEC              /* Retrieve ID command             */
#define CMDdown          0xE0              /* Spin-up command                 */
#define CMDup            0xE1              /* Spin-down command               */

#define drqMask          0x04              /* Data request ready mask         */
#define busyMask         0x80              /* Status busy mask                */
#define readyMask        0x40              /* Status ready mask               */

/*                
;-------------------------------------------------------------------------------
;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 insIDE drive
;  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 occurred
;-------------------------------------------------------------------------------
*/


int debug          = FALSE;

int dPulseRst      =  50;                  /* Reset pulse length              */
int dAfterRst      =   0;                  /* Delay after reset               */
int dAfterInit     =   0;                  /* Delay after initialization      */
int dAfterSelect   =   0;                  /* Delay after drive select        */
int dBeforStatus   =   0;                  /* Delay before status             */
int dStatCommand   =   0;                  /* Status read command load        */
int dStatReadPulse =   0;                  /* Status read pulse length        */
int dStatAfterRead =   0;                  /* Delay after status read         */
int dIdCommand     =   0;                  /* Delay after ID command load     */
int dIdReadCmd     =   0;                  /* Delay after word read command   */
int dIdReadPulse   =   0;                  /* ID word read pulse length       */
int dAfterId       =   0;                  /* Delay after ID transferred      */
int dReadCommand   =   0;                  /* Read command load               */
int dReadPulse     =   0;                  /* Read pulse length               */
int dAfterRead     =   0;                  /* Delay after read                */
int dReadDeassert  =   0;                  /* Delay before pulse off          */
int dWriteCommand  =   0;                  /* Write command load              */
int dWritePulse    =   0;                  /* Write pulse length              */
int dAfterWrite    =   0;                  /* Delay after write               */


struct diskId {                                  /* IDE identification struct */
   char gCfgBinf[2];   /* General configuration bit-significant info          */
   char defCyl[2];     /* Default number of cylinders                         */
   char res1[2];       /* Reserved                                            */
   char defHd[2];      /* Default number of heads                             */
   char unfBytTr[2];   /* Number of unformatted bytes per track               */
   char unfBytSc[2];   /* Unformatted bytes per sector                        */
   char defSec[2];     /* Default number of sectors per track                 */
   char defTotSc[4];   /* Number of sectors on the device                     */
   char res2[2];       /* Reserved                                            */
   char serial[20];    /* Serial number of device (ASCII)                     */
   char bufrType[2];   /* Buffer Type (dual-ported)                           */
   char bufrSize[2];   /* Buffer size in 512-byte blocks                      */
   char eccBytes[2];   /* Number of ECC bytes passed on R/W Long commands     */
   char version[8];    /* Firmware revision (ASCII)                           */
   char model[40];     /* Device model name (ASCII)                           */
   char maxOneSc[2];   /* Maximum of one sector on R/W multiple command       */
   char dblNotSp[2];   /* Double word not supported                           */
   char advConf[2];    /* Advanced config bits: DMA bit 8, LBA bit 9          */
   char res3[2];       /* Reserved                                            */
   char pioTimng[2];   /* PIO data transfer cycle timing mode                 */
   char dmaTimng[2];   /* DMA data transfer cycle timing mode                 */
   char fieldVal[2];   /* Field validity                                      */
   char cyl[2];        /* Current number of cylinders                         */
   char hd[2];         /* Current number of heads                             */
   char sec[2];        /* Current number of sectors per track                 */
   char TotSc[4];      /* Current capacity in sectors                         */
   char MultiSec[2];   /* Multiple sector setting is valid                    */
   char TotScLBA[4];   /* Total number of sectors addressable in LBA mode     */
   char res4[4];       /* Reserved                                            */
   char advPIOmd[2];   /* Advanced PIO modes supported                        */
   char res5[4];       /* Reserved                                            */
   char minPIOnh[2];   /* Minimum PIO transfer without handshaking            */
   char minPIOrd[2];   /* Minimum PIO transfer with IORDY flow control        */
   char res6[130];     /* Reserved                                            */
   char vendor[64];    /* Reserved vendor-specific unique bytes               */
   char res7[192];     /* Reserved                                            */
};


int strncasecmp( s1, s2, length )                        /* String comparison */
   char* s1;
   char* s2;
   int length;
{
   char* c1 = s1;
   char* c2 = s2;
   int   l = 0;
   int   r = 0;

   while( (r == 0) && (l < length) && (*c2 != '\0') ) {
      r = *c1 - *c2;
      c1++;
      c2++;
      l++;
   }

   return( r );
}


int twoBytesToInt( twoBytes )        /* Convert two-bytes from IDE to integer */
   char* twoBytes;
{
   int number;
   number = (twoBytes[1] * 256) + twoBytes[0];

   return( number );
}


char* swapBytes( charArray )     /* Convert char array by swapping byte-pairs */
   char* charArray;
{
   char swapped[513];
   int  i = 0;
   int  length;

   length = strlen( charArray );

   while( i < length ) {
      swapped[i] = charArray[i+1];
      swapped[i+1] = charArray[i];
      i++;
      i++;
   }
   swapped[i] = '\0';

   return( swapped );
}


void copyBytes( ar2, ar1, n )     /* Copy "n" count of bytes from ar1 to ar2  */
   char* ar1;
   char* ar2;
   int n;
{
   int i;

   for( i=0; i < n; i++ ) {
      ar2[i] = ar1[i];
   }
   ar2[i] = '\0';
}


int hex2decimal( hex )            /* Convert a hex number to a decimal number */
   char* hex;
{
   int decimal = 0;
   int length = 0;
   int value = 0;
   int base = 1;
   int i = 0;

   length = strlen( hex );

   for( i = length--; i >= 0; i-- )
   {
      if( hex[i] >= '0' && hex[i] <= '9' )
      {
         decimal += (hex[i] - 48) * base;
         base *= 16;
      }
      else if( hex[i] >= 'A' && hex[i] <= 'F' )
      {
         decimal += (hex[i] - 55) * base;
         base *= 16;
      }
      else if( hex[i] >= 'a' && hex[i] <= 'f' )
      {
         decimal += (hex[i] - 87) * base;
         base *= 16;
      }
   }

   return( decimal );
}


int interpretNumber( number )         /* Determine if value is decimal or hex */
   char* number;
{
   int lcp;
   int value;
   char n[5];
   char lastChar;

   sprintf( n, "%s", number );

   lcp = strlen( n ) - 1;  
   lastChar = n[lcp];

   if( lastChar == 'H' || lastChar == 'h' ) {
      n[lcp] = '\0';
      value = hex2decimal( n );
   } else {
      value = (unsigned char) atoi( n );
   }

   return( value );
}


char* cleanString( string )                      /* Replace  space with  null */
   char* string;                                 /* to isolate first argument */
{
   int p;
   int l;

   l = strlen( string );
   for( p = 0; p < l; p++ ) {
      if( string[p] == ' ' ) {
        string[p] = '\0';
      }
   }

   return( string );
}


void outPort( portdata, messages )                   /* Send data to I/O port */
   char* portdata;
   int messages;
{
   unsigned char port;
   unsigned char data;
   char* op;
   char* dp;

   port   = '\0';
   data   = '\0';

   if( portdata ) {
      op = portdata;
      if ( op ) {
         if( op[0] == ' ' ) {
            op++;                                     /* remove leading space */
         }
         dp = index ( op, ' ' );
         if ( dp ) {
            if( dp[0] == ' ' ) {
               dp++;                                  /* remove leading space */
            }
            op = cleanString( op );
            port = interpretNumber( op );
            data = interpretNumber( dp );
            if( debug && messages )
               printf ( "\nPort %d (%02xH) output -> %d (%02xH)\n\n", port, port, data, data );
            out ( port, data );
         } else {
            printf ( "\nInvalid out command\n\n" ); 
         }
      } else {
         printf ( "\nInvalid out command\n\n" ); 
      }
   }
}


int IDErd8d( C )                             /* Read data from IDE interface */
   int C;
{
   char outData[20];
   int pulse;
   int delay;
   int data;

   if( debug )
      printf( "\n=== IDErd8d( %02xH ) ===\n\n", C );

   if( debug )
      printf( "Selecting IDE register %02xH.\n", C );

   sprintf( outData, "%02xH %02xH", IDEpC, C );  
   outPort( outData );

   pulse = C + IDErdLine;

   if( debug )
      printf( "Sending read pulse.\n" );
   else {
      delay = dReadCommand;
      while( delay )
         delay--;
   }

   sprintf( outData, "%02xH %02xH", IDEpC, pulse );  
   outPort( outData, verbose );

   delay = dReadPulse;
   while( delay )
      delay--;

   data = in( IDEpA );

   if( debug )
      printf( "Data returned --> %02xH <--\n", data );
   else {
      delay = dAfterRead;
      while( delay )
         delay--;
   }

   sprintf( outData, "%02xH %02xH", IDEpC, C );  
   outPort( outData, verbose );

   if( debug )
      printf( "Pulse off.\n" );
   else {
      delay = dReadDeassert;
      while( delay )
         delay--;
   }
   
   if( debug )
      printf( "Clearing 8255 port C.\n" );

   sprintf( outData, "%02xH %02xH", IDEpC, 0 );  
   outPort( outData, verbose );

   if( debug )
      printf( "==========================\n\n" );

   return( data );
}


void IDEwr8d( A, C )                            /* Send data to IDE interface */
   int A;
   int C;
{
   char outData[20];
   int pulse;
   int delay;

   if( debug )
      printf( "\n=== IDEwr8d( %02xH %02xH ) ===\n\n", A, C );

   if( debug )
      printf( "Setting 8255 to write mode.\n" );

   sprintf( outData, "%02xH %02xH", IDEpCtrl, WRITEcfg8255 );  
   outPort( outData, verbose );

   if( debug )
      printf( "Putting %02xH data in 8255 port A.\n", A );

   sprintf( outData, "%02xH %02xH", IDEpA, A );  
   outPort( outData, verbose );
   
   if( debug )
      printf( "Selecting IDE register %02xH.\n", C );

   sprintf( outData, "%02xH %02xH", IDEpC, C );  
   outPort( outData, verbose );
      
   pulse = C + IDEwrLine;

   if( debug )
      printf( "Sending write pulse.\n" );
   else {
      delay = dWriteCommand;
      while( delay )
         delay--;
   }

   sprintf( outData, "%02xH %02xH", IDEpC, pulse );  
   outPort( outData, verbose );

   if( debug )
      printf( "Pulse off.\n" );
   else {
      delay = dWritePulse;
      while( delay )
         delay--;
   }

   sprintf( outData, "%02xH %02xH", IDEpC, C );  
   outPort( outData, verbose );

   if( debug )
      printf( "Clearing 8255 port C.\n" );
   else {
      delay = dAfterWrite;
      while( delay )
         delay--;
   }

   sprintf( outData, "%02xH %02xH", IDEpC, 0 );  
   outPort( outData, verbose );

   if( debug )
      printf( "Setting 8255 control port to %02xH read mode.\n", READcfg8255 );

   sprintf( outData, "%02xH %02xH", IDEpCtrl, READcfg8255 );  
   outPort( outData, verbose );

   if( debug )
      printf( "==========================\n\n" );
}


void IDEinit() {                                  /* Initialize IDE interface */
   char outData[20];
   int delay;

   if( debug )
      printf( "\nInitializing 8255.\n" );

   sprintf( outData, "%02xH %02xH", IDEpCtrl, READcfg8255 );  
   outPort( outData, verbose );

   if( debug )
      printf( "Resetting IDE interface.\n" );

   sprintf( outData, "%02xH %02xH", IDEpC, IDErstLine );  
   outPort( outData, verbose );

   delay = dPulseRst;
   while( delay )
      delay--;

   if( debug )
      printf( "Sending reset pulse.\n" );

   sprintf( outData, "%02xH %02xH", IDEpC, 0 );  
   outPort( outData, verbose );

   if( debug )
      printf( "Putting drive into mode: Master, LBA, 512, head 0\n" );
   else {
     delay = dAfterRst;
      while( delay )
         delay--;
   }

   IDEwr8d( 0xE0, REGshd );

   delay = dAfterInit;
   while( delay )
      delay--;

}


void IDEselect( drive )                                  /* Select IDE drive */
   int drive;
{
   int delay = 0;
   char outData[20];
 
   if( debug )
      printf( "Selecting IDE unit %d.\n", drive );

   sprintf( outData, "%02xH %d", IDEDrSel, drive );  
   outPort( outData, verbose );

   delay = dAfterSelect;
   while( delay )
      delay--;
}


int getIDEstatus() {                                /* Retrieve drive status */
   int data = 0;
   int pulse = 0;
   int delay = 0;
   int rdyMask = 0x80;
   char outData[20];
 
   if( debug )
      printf( "Writing 'status request' value %02xH into 8255 port C.\n", REGstat );
   else {
      delay = dBeforStatus;
      while( delay )
         delay--;
   }

   sprintf( outData, "%02xH %02xH", IDEpC, REGstat );
   outPort( outData, verbose );

   pulse = IDErdLine + REGstat;

   if( debug )
      printf( "Now pulse the read pin by ORing %02xH with %02xH to create %02xH.\n", IDErdLine, REGstat, pulse );
   else {
      delay = dStatCommand;
      while( delay )
         delay--;
   }

   sprintf( outData, "%02xH %02xH", IDEpC, pulse );
   outPort( outData, verbose );

   if( debug )
      printf( "Reading status from IDE port A at %02xH.\n", IDEpA );
   else {
      delay = dStatReadPulse;
      while( delay )
         delay--;
   }

   data = in( IDEpA );

   if( debug )
      printf( "\nStatus --> %02xH <--\n\n", data );

   if( debug )
      printf( "De-assert read pin, resetting Port C to value %02xH.\n", REGstat );

   sprintf( outData, "%02xH %02xH", IDEpC, REGstat );
   outPort( outData, verbose );

   if( debug )
      printf( "Set Port C to value %02xH.\n", 0 );
   else {
      delay = dStatAfterRead;
      while( delay )
         delay--;
   }

   sprintf( outData, "%02xH %02xH", IDEpC, 0 );
   outPort( outData, verbose );

   return( data );
}


void getIDEid( drive, status )       /* Retrieve drive identification string */
   int drive;
   int status;
{
   int i;
   int j;
   int data = 0x00;
   int pulse = 0x00;
   int delay = 0x00;
   unsigned char lb;
   unsigned char hb;
   char outData[20];
   char ident[513];
   char drdata[513];
   char model[21];
   char serial[11];
   char version[5];
   int  cylinders;
   int  heads;
   int  sectors;

   struct diskId* diskInfo = (struct diskId*) ident;

   if( status & readyMask ) {

      if( debug )
         printf( "Requesting drive information.\n" );

      IDEwr8d( CMDid, REGcmd );

      delay = dIdCommand;
      while( delay )
         delay--;

      data = IDErd8d( REGstat );

      if( debug ) {
         printf( "Drive %d returned status code %02xH, after requesting identification.\n", drive, data );     
         printf( "(Note: 58H means data is ready to be retrieved from the drive.)\n" );
      }

      if( data == 0x58 ) {

         if( debug )
            printf( "\n===============================================================================\n" );

         for( i = 0; i < 256; i++ ) {
            j = i * 2;
            sprintf( outData, "%02xH %02xH", IDEpC, REGdata );
            outPort( outData, quiet );

            delay = dIdReadCmd;
               while( delay )
                  delay--;

            pulse = REGdata + IDErdLine;

            sprintf( outData, "%02xH %02xH", IDEpC, pulse );
            outPort( outData, quiet );

            delay = dIdReadPulse;
               while( delay )
                  delay--;

            ident[j] = in( IDEpA );                             /* lower byte */
            ident[j+1] = in( IDEpB );                           /* upper byte */

            if( debug ) {
               printf( "%c", ident[j] );
               printf( "%c", ident[j+1] );
            }

            sprintf( outData, "%02xH %02xH", IDEpC, REGdata );
            outPort( outData, quiet );

            delay = dAfterId;
               while( delay )
                  delay--;
         }
         j++;
         j++;
         ident[j] = '\0';

         if( debug )
            printf( "\n===============================================================================\n" );

         printf( "\nDrive %d identifies itself as:\n\n", drive );

         copyBytes( drdata, diskInfo->model, 40 );
         sprintf( model, "%s", swapBytes(drdata) );
         printf( "Model:     %s\n", model );

         copyBytes( drdata, diskInfo->serial, 20 );
         sprintf( serial, "%s", drdata );
         printf( "Serial:    %s\n", serial );

         copyBytes( drdata, diskInfo->version, 8 );
         sprintf( version, "%s", swapBytes(drdata) );
         printf( "Version:   %s\n", version );

         copyBytes( drdata, diskInfo->cyl, 2 );
         cylinders = twoBytesToInt( drdata );
         printf( "Cylinders: %d\n", cylinders );
  
         copyBytes( drdata, diskInfo->hd, 2 );
         heads = twoBytesToInt( drdata );
         printf( "Heads:     %d\n", heads );

         copyBytes( drdata, diskInfo->sec, 2 );
         sectors = twoBytesToInt( drdata );
         printf( "Sectors:   %d\n", sectors );

      } else {

         printf( "Not attempting drive identification because of unexpected status.\n" );     
      }

      if( debug ) {
         status = getIDEstatus();
         printf( "\nCompleted with status code %02xH.\n", status );
      }     

   } else {

      printf( "Drive %d busy with status code %02xH.\n", drive, status );
   }

}


void clearScreen() {                                       /* Scroll 25 lines */
   int l;
   putchar ( '\r' );
   for ( l = 0; l < 25; l++ ) {
      putchar ( '\n' );
   }
}


int main() {
   unsigned char port;
   unsigned char data;
   unsigned char val;
   int showdata;
   int offset;
   int going;
   int instr;
   int drive;
   int start;
   int stat;
   int end;
   int top;
   int cls;
   int l;
   int v;
   char* pd;
   char cmd[80];

   val    = '\0';
   cmd[0] = '\0';
   showdata = 0;
   going  = TRUE;
   instr  = FALSE;
   start  = FALSE;
   cls    = TRUE;
   end    = 16;
   top    = 127;
   offset = 0;
   l      = 0;
   v      = 1;


   while ( going ) {

      if ( cls ) {
         clearScreen();
      }

      if ( showdata ) {
         cls = TRUE;
         puts ("================================= Input Data ==================================");

         while ( start < top ) {

            for ( l = start; l < end; l++ ) {
               data = in( l );
               printf( "P0%02x", l );
               if ( l < (end - 1) ) {
                  printf( " " );
               }
            }
            printf( "\n\r" );

            printf( " " );
            for ( l = start; l < end; l++ ) {
               data = in( l );
               printf( "%02x", data );
               if ( l < (end - 1) ) {
                  printf( "   " );
               }
            }
            printf( "\n\r" );

            start += 16;
            end += 16;
         }
         puts ("===============================================================================");
      }

      if ( instr ) {
         cls = TRUE;
         puts ("===============================================================================");
         puts ("The  Intel 8080 processor provides 256 addresses for input and output  devices.");
         puts ("This  program  exposes all I/O addresses, allowing you to see the  data  placed");
         puts ("onto  each  of the ports.   You can also send a byte out any of  the  I/O  port");
         puts ("addresses.  Of course, if there is no hardware addressed on a port,  then  data");
         puts ("read will be undetermined.  Data written to an unused port will have no effect.");
         puts ("                                                                               ");
         puts ("Available  commands  are help, show, view, out, debug, test,  idetest,  testide");
         puts ("and quit.  Help shows this message.   Quit ends the program.   Show and view do");
         puts ("the  same thing, showing the top or bottom 128 input ports.   One  argument  is");
         puts ("expected  on the show or view command - either \"high\" or \"low\" - which  can  be");
         puts ("abbreviated.");
         puts ("                                                                               ");
         puts ("Test,  idetest and testide all do the same thing, which is to check for an  IDE");
         puts ("subsystem and to report the drive status information, if present.  An  optional");
         puts ("argument - 0 or 1 - can be entered to select the drive number.");
         puts ("                                                                               ");
         puts ("The  out command requires two arguments, first the port number and second,  the");
         puts ("data  that will be presented to the port.   Both arguments are expected  to  be");
         puts ("one  byte  wide,  e.g. 0 to 255.   Numeric arguments can be either  decimal  or");
         puts ("hexadecimal.   Hex values are indicated by a trailing \"H\" character, which must");
         puts ("be present even if some digits are alpha characters.                           ");
         puts ("===============================================================================");
      }

      printf ("\nCommand:  ");

      fgets ( cmd, sizeof(cmd)-1, stdin );
      cmd[strlen(cmd)-1] = '\0';

      if ( strncasecmp(cmd, "quit", 4) == 0 ) {
         going = FALSE;  
      }
      else if ( strncasecmp(cmd, "help", 4) == 0 ) {
         showdata = FALSE;
         instr = TRUE;
      }
      else if ( strncasecmp(cmd, "show h", 6) == 0 ) {
         showdata = TRUE;
         instr = FALSE;
         v = 0;         
      }
      else if ( strncasecmp(cmd, "show l", 6) == 0 ) {
         showdata = TRUE;
         instr = FALSE;
         v = 1;         
      }
      else if ( strncasecmp(cmd, "view h", 6) == 0 ) {
         showdata = TRUE;
         v = 0;         
      }
      else if ( strncasecmp(cmd, "view l", 6) == 0 ) {
         showdata = TRUE;
         v = 1;         
      }
      else if ( strncasecmp(cmd, "out", 3) == 0 ) {
         showdata = 0;
         instr = 0;
         cls = 0;
         pd = index ( cmd, ' ' );
         outPort( pd );
      }
      else if ( strncasecmp(cmd, "testide", 7) == 0 ) {
         showdata = FALSE;
         instr = FALSE;
         cls = FALSE;
         pd = index ( cmd, ' ' );
         drive = atoi( pd );
         if( (drive == 0) || (drive == 1) ) {
            printf( "Examining drive %d...\n", drive );
            IDEinit();
            IDEselect( drive );
            stat = getIDEstatus();
            getIDEid( drive, stat );
         } else {
            printf( "Unsupported drive %d.\n", drive );
         }        
      }
      else if ( strncasecmp(cmd, "idetest", 7) == 0 ) {
         showdata = FALSE;
         instr = FALSE;
         cls = FALSE;
         pd = index ( cmd, ' ' );
         drive = atoi( pd );
         if( (drive == 0) || (drive == 1) ) {
            printf( "Examining drive %d...\n", drive );
            IDEinit();
            IDEselect( drive );
            stat = getIDEstatus();
            getIDEid( drive, stat );
         } else {
            printf( "Unsupported drive %d.\n", drive );
         }        
      }
      else if ( strncasecmp(cmd, "test", 4) == 0 ) {
         showdata = FALSE;
         instr = FALSE;
         cls = FALSE;
         pd = index ( cmd, ' ' );
         drive = atoi( pd );
         if( (drive == 0) || (drive == 1) ) {
            printf( "Examining drive %d...\n", drive );
            IDEinit();
            IDEselect( drive );
            stat = getIDEstatus();
            getIDEid( drive, stat );
         } else {
            printf( "Unsupported drive %d.\n", drive );
         }        
      }
      else if ( strncasecmp(cmd, "debug on", 8) == 0 ) {
         debug = TRUE;
         printf( "Debug is turned ON.\n" );
      }
      else if ( strncasecmp(cmd, "debug off", 8) == 0 ) {
         debug = FALSE;
         printf( "Debug is turned OFF.\n" );
      }
      else if ( strncasecmp(cmd, "dpulserst", 9) == 0 ) {
         pd = index ( cmd, ' ' );
         dPulseRst = atoi( pd );
         printf( "dPulseRst value is now set to %d.\n", dPulseRst );
      }
      else if ( strncasecmp(cmd, "dafterrst", 9) == 0 ) {
         pd = index ( cmd, ' ' );
         dAfterRst = atoi( pd );
         printf( "dAfterRst value is now set to %d.\n", dAfterRst );
      }
      else if ( strncasecmp(cmd, "dpulsereset", 11) == 0 ) {
         pd = index ( cmd, ' ' );
         dPulseRst = atoi( pd );
         printf( "dPulseRst value is now set to %d.\n", dPulseRst );
      }
      else if ( strncasecmp(cmd, "dafterreset", 11) == 0 ) {
         pd = index ( cmd, ' ' );
         dAfterRst = atoi( pd );
         printf( "dAfterRst value is now set to %d.\n", dAfterRst );
      }
      else if ( strncasecmp(cmd, "dafterinit", 10) == 0 ) {
         pd = index ( cmd, ' ' );
         dAfterInit = atoi( pd );
         printf( "dAfterInit value is now set to %d.\n", dAfterInit );
      }
      else if ( strncasecmp(cmd, "dafterselect", 12) == 0 ) {
         pd = index ( cmd, ' ' );
         dAfterSelect = atoi( pd );
         printf( "dAfterSelect value is now set to %d.\n", dAfterSelect );
      }
      else if ( strncasecmp(cmd, "dbeforstatus", 12) == 0 ) {
         pd = index ( cmd, ' ' );
         dBeforStatus = atoi( pd );
         printf( "dBeforStatus value is now set to %d.\n", dBeforStatus );
      }
      else if ( strncasecmp(cmd, "dbeforestatus", 13) == 0 ) {
         pd = index ( cmd, ' ' );
         dBeforStatus = atoi( pd );
         printf( "dBeforStatus value is now set to %d.\n", dBeforStatus );
      }
      else if ( strncasecmp(cmd, "dstatcommand", 12) == 0 ) {
         pd = index ( cmd, ' ' );
         dStatCommand = atoi( pd );
         printf( "dStatCommand value is now set to %d.\n", dStatCommand );
      }
      else if ( strncasecmp(cmd, "dstatreadpulse", 14) == 0 ) {
         pd = index ( cmd, ' ' );
         dStatReadPulse = atoi( pd );
         printf( "dStatReadPulse value is now set to %d.\n", dStatReadPulse );
      }
      else if ( strncasecmp(cmd, "dstatafterread", 14) == 0 ) {
         pd = index ( cmd, ' ' );
         dStatAfterRead = atoi( pd );
         printf( "dStatAfterRead value is now set to %d.\n", dStatAfterRead );
      }
      else if ( strncasecmp(cmd, "didcommand", 10) == 0 ) {
         pd = index ( cmd, ' ' );
         dIdCommand = atoi( pd );
         printf( "dIdCommand value is now set to %d.\n", dIdCommand );
      }
      else if ( strncasecmp(cmd, "didreadcmd", 10) == 0 ) {
         pd = index ( cmd, ' ' );
         dIdReadCmd = atoi( pd );
         printf( "dIdReadCmd value is now set to %d.\n", dIdReadCmd );
      }
      else if ( strncasecmp(cmd, "didreadpulse", 12) == 0 ) {
         pd = index ( cmd, ' ' );
         dIdReadPulse = atoi( pd );
         printf( "dIdReadPulse value is now set to %d.\n", dIdReadPulse );
      }
      else if ( strncasecmp(cmd, "dafterid", 8) == 0 ) {
         pd = index ( cmd, ' ' );
         dAfterId = atoi( pd );
         printf( "dAfterId value is now set to %d.\n", dAfterId );
      }
      else if ( strncasecmp(cmd, "dreadcommand", 12) == 0 ) {
         pd = index ( cmd, ' ' );
         dReadCommand = atoi( pd );
         printf( "dReadCommand value is now set to %d.\n", dReadCommand );
      }
      else if ( strncasecmp(cmd, "dreadpulse", 10) == 0 ) {
         pd = index ( cmd, ' ' );
         dReadPulse = atoi( pd );
         printf( "dReadPulse value is now set to %d.\n", dReadPulse );
      }
      else if ( strncasecmp(cmd, "dafterread", 10) == 0 ) {
         pd = index ( cmd, ' ' );
         dAfterRead = atoi( pd );
         printf( "dAfterRead value is now set to %d.\n", dAfterRead );
      }
      else if ( strncasecmp(cmd, "dreaddeassert", 13) == 0 ) {
         pd = index ( cmd, ' ' );
         dReadDeassert = atoi( pd );
         printf( "dReadDeassert value is now set to %d.\n", dReadDeassert );
      }
      else if ( strncasecmp(cmd, "dwritecommand", 13) == 0 ) {
         pd = index ( cmd, ' ' );
         dWriteCommand = atoi( pd );
         printf( "dWriteCommand value is now set to %d.\n", dWriteCommand );
      }
      else if ( strncasecmp(cmd, "dwritepulse", 11) == 0 ) {
         pd = index ( cmd, ' ' );
         dWritePulse = atoi( pd );
         printf( "dWritePulse value is now set to %d.\n", dWritePulse );
      }
      else if ( strncasecmp(cmd, "dafterwrite", 11) == 0 ) {
         pd = index ( cmd, ' ' );
         dAfterWrite = atoi( pd );
         printf( "dAfterWrite value is now set to %d.\n", dAfterWrite );
      }

      if ( v ) {
         start = 0;  /* view bottom 128 input ports */
         end = 16;
         top = 127;  
      } else {       /* view top 128 input ports */
         start = 128;
         end = 144;
         top = 255;  
      }
   }

   puts ("\n\nEnjoy your day!\n\n");

   return( 0 );
}
AltairClone
Site Admin
Posts: 677
Joined: April 5th, 2013, 10:55 am
Contact:

Re: IDE interface for Altair 8800c

Post by AltairClone »

IDE doesn’t appear to simplify things on these old computers, does it?

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

Re: IDE interface for Altair 8800c

Post by Wayne Parham »

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

Re: IDE interface for Altair 8800c

Post by Wayne Parham »

I have an update, albeit a small one.

Since the C code functions properly, I placed my focus back on the assembly language program. In hindsight, I probably should have stayed there. Re-writing the code was a useful exercise, but it only really served to prove that it was procedurally correct.

That being confirmed, I went back to the assembly code and added some print statements. For one thing, I added console output code to tell me what the IDE status was after submitting the 0xEC identification command. It was 0x58, just like I see in the C code. That status indicates the command was accepted, and the drive has data ready to be read.

I could see the code flows as expected up until the phase where it iterates to get the data from the drive, two bytes at a time. But that's where the problem is. Once the call to "MoreRD16" is made, the code returns without iterating to get the data. Even if I put a call to print a string at the very beginning of MoreRD16, no data is sent to the console. So something odd is happening at that point.
Wayne Parham
Posts: 253
Joined: March 18th, 2022, 3:01 pm
Contact:

Re: IDE interface for Altair 8800c

Post by Wayne Parham »

I think my problem may be the DJNZ instruction in the assembly code.

I started commenting out code chunks and doing other dubious hacking activities and found that if I removed DJNZ, the rest of the code worked. Of course, it doesn't decrement and jump if not zero - I'd need to add code to put the counter in the accumulator, decrement it and test for zero - but at least it doesn't blow right past that code block.

Not sure yet what I'll use as a replacement for DJNZ. Just seems like it shouldn't me too much trouble to fix. I just know if I comment out the DJNZ instruction in the MoreRD16 block, a CALL PRINTS statement works within that block. If the DJNZ statement is there, it doesn't. And since I see the DJNZ statement is Z80 code, it won't work on the 8080. There's no machine code equivalent.

I assumed that when ASM created a HEX file, it was happy. And I think maybe there is an assembler that would convert that mnemonic to 8080-legal instructions, sort of a translator/assembler. Not sure. I just know the machine code generated from ASM with source including the DJNZ mnemonic doesn't include any HEX instructions in its place. It happily skips over that mnemonic.

Maybe I just need to re-write the code to replace DJNZ instructions with 8080-legal instructions. There are a lot of DJNZ instructions in the program, so I need to go over it with a fine-toothed comb.
AltairClone
Site Admin
Posts: 677
Joined: April 5th, 2013, 10:55 am
Contact:

Re: IDE interface for Altair 8800c

Post by AltairClone »

You are correct, there is not a DJNZ instruction in the 8080. The Z80 instruction decrements register B, so the equivalent in 8080 code would be two instructions: DCR B, JNZ label. On a side note, you mention DCX instruction which would give you a 16 bit counter instead of 8 bit, but note that DCX/JNZ does not work with DCX since DCX does not affect the zero bit. Instead, you have to use something like:

DCX
MOV A,H
ORA L
JNZ label

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

Re: IDE interface for Altair 8800c

Post by Wayne Parham »

Thanks, Mike, I was thinking that too. So I'll replace DJNZ with DCR B and JNZ within MoreRD16 and verify that works. Then I'll do the same thing for all other occurrences of DJNZ in the code.
Wayne Parham
Posts: 253
Joined: March 18th, 2022, 3:01 pm
Contact:

Re: IDE interface for Altair 8800c

Post by Wayne Parham »

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

DCR B
JNZ MoreRD16

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

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

Once that's done, I'll be sure to post it here in this thread.
Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest