IDE interface for Altair 8800c

Discuss construction, troubleshooting, and operation of the Altair 8800c computer

IDE interface for Altair 8800c

Postby Wayne Parham » November 25th, 2022, 9:09 pm

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, 6:52 pm, edited 6 times in total.
Wayne Parham
 
Posts: 239
Joined: March 18th, 2022, 3:01 pm

Re: IDE interface for Altair 8800c

Postby Wayne Parham » November 25th, 2022, 9:12 pm

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: 239
Joined: March 18th, 2022, 3:01 pm

Re: IDE interface for Altair 8800c

Postby Wayne Parham » November 25th, 2022, 9:15 pm

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 );
}
Wayne Parham
 
Posts: 239
Joined: March 18th, 2022, 3:01 pm

Re: IDE interface for Altair 8800c

Postby AltairClone » November 26th, 2022, 7:27 pm

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

Mike
AltairClone
Site Admin
 
Posts: 631
Joined: April 5th, 2013, 10:55 am

Re: IDE interface for Altair 8800c

Postby Wayne Parham » November 26th, 2022, 7:43 pm

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

Re: IDE interface for Altair 8800c

Postby Wayne Parham » November 28th, 2022, 4:36 pm

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: 239
Joined: March 18th, 2022, 3:01 pm

Re: IDE interface for Altair 8800c

Postby Wayne Parham » November 29th, 2022, 5:31 pm

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.
Wayne Parham
 
Posts: 239
Joined: March 18th, 2022, 3:01 pm

Re: IDE interface for Altair 8800c

Postby AltairClone » November 29th, 2022, 5:55 pm

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
AltairClone
Site Admin
 
Posts: 631
Joined: April 5th, 2013, 10:55 am

Re: IDE interface for Altair 8800c

Postby Wayne Parham » November 29th, 2022, 6:24 pm

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: 239
Joined: March 18th, 2022, 3:01 pm

Re: IDE interface for Altair 8800c

Postby Wayne Parham » November 30th, 2022, 2:59 pm

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.
Wayne Parham
 
Posts: 239
Joined: March 18th, 2022, 3:01 pm

Next

Return to Altair 8800c

Who is online

Users browsing this forum: No registered users and 1 guest

cron