IDE interface for Altair 8800c

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

Re: IDE interface for Altair 8800c

Postby Wayne Parham » December 10th, 2022, 5:02 pm

TronDD wrote:I poked around with my Dual IDE card. I have the version with the GALs and had a hard time getting them programmed. Several programmers, a bunch of money, and a lot of hassle later, they seemed to program but I never trusted them. When the card didn't work, I was fed up with it and put it aside for over a year.

Taking a closer look now, I checked the output of the GALs. I'm not sure they are working correctly. GAL1, which does the address handling, seems to output regardless of the address. And GAL2 which handles the chip signals seems like it might be doing some of right thing except it never sends the chip enable signal to the 8255. So the 8255 is forever dead in the water.

I hated the GALs from the start and I no longer have access to a Windows system that the programmer software requires to check them or reprogram them. I'm just going to order the next version of the board that did away with the GALs and start over.


I'm so sorry to hear that! But I understand - When I saw that version with the GALs, I kind of thought the same thing. I was glad that John made the next version without them.

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

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

Re: IDE interface for Altair 8800c

Postby Wayne Parham » December 10th, 2022, 7:49 pm

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

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

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

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

FALSE      EQU   0
TRUE      EQU   NOT FALSE

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

   ORG   100H      ;<--- For CPM

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

   CALL   READSECTOR

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

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

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

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

   LXI   H, buffer   ;Point to buffer
   SHLD   mDMA

   CALL   WRITESECTOR

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

NextFormatDone:
   LXI   D, FormatDone   ;Tell us we are all done.
   CALL   PSTRING
   JMP   MAINLOOP
            
BACKUP:            ;Backup the CPM partition to another area
   LXI   D, CopyMsg
   CALL   PSTRING
   CALL   ZCI
   CALL   UPPER
   CPI   'Y'
   JNZ   MAINLOOP

   CALL   GET$PART$NUM   ;Ask user for partition number (01-FF)
   LHLD   mPART$NUM
   MOV   A, L
   CPI   0      ;Partition zero isn't allowed
   JZ   RANGE$ERROR
   
   LXI   H, 0      ;Start with CPM sector 0
   SHLD   mSEC
   SHLD   mSEC1
   SHLD   mSEC2      ;and on second partition
   SHLD   mTRK      ;and track 0
   SHLD   mTRK1

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

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

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

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

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

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

RESTORE:         ;Restore disk from backup partition
   LXI   D, RestoreMsg
   CALL   PSTRING
   CALL   ZCI
   CALL   UPPER
   CPI   'Y'
   JNZ   MAINLOOP

   CALL   GET$PART$NUM   ;Ask user for partition number (01-FF)
   LHLD   mPART$NUM
   MOV   A, L
   CPI   0      ;Partition zero isn't allowed
   JZ   RANGE$ERROR
   
   LXI   H, 0      ;Start with CPM sector 0
   SHLD   mSEC
   SHLD   mSEC1
   SHLD   mSEC2      ;and on second partition
   SHLD   mTRK      ;and track 0
   SHLD   mTRK1

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

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

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

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

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

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

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

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

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

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

   CALL   SELECT0      ;Select drive 0
   JMP   MAINLOOP

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

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

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

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

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

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

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

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

BK$V$NextDone:   
   LXI   D, VerifyDone   ;Tell us we are all done.
   CALL   PSTRING
   CALL   SELECT0      ;Select drive 0
   JMP   MAINLOOP
Wayne Parham
 
Posts: 246
Joined: March 18th, 2022, 3:01 pm

Re: IDE interface for Altair 8800c

Postby Wayne Parham » December 10th, 2022, 7:55 pm

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

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

SequentialReads:      ;Sequentially read sectors from current position
   CALL   IDEwaitnotbusy
   JC   SHOWerrors
   CALL   ZCRLF

NEXTSEC:
   LXI   H, buffer   ;Point to buffer
   SHLD   mDMA

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

   LXI   H, buffer   ;Point to buffer
   SHLD   mDMA

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

   CALL   ZCSTS      ;Any keyboard character will stop display
   CPI   01H      ;CPM Says something there
   JNZ   NEXTSEC1
   CALL   ZCI      ;Flush character
   LXI   D, CONTINUE$MSG
   CALL   PSTRING
   CALL   ZCI
   CPI   ESC
   RZ
   CALL   ZCRLF

NEXTSEC1:
   LHLD   mSEC
   MOV   A, L      ;Current sector to A
   CALL   CHK$SEC      ;Are we already at max sector?
   JZ   NextSecZero   ;Yes - back to sector 0
   INX   H      ;No - bump to next sector
   SHLD   mSEC   
   JMP   NEXTSEC

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

PRN$0$INFO:         ;Print Drive 0 identification info
   LXI   D, DRIVE0$INFO
   CALL   PSTRING
   CALL   REM$DRV
   CALL   SELECT0
   CALL   PRN$DRV$INFO
   CALL   IDEinit
   CALL   RET$DRV
   JMP   MAINLOOP

PRN$1$INFO:         ;Print Drive 1 identification info
   LXI   D, DRIVE1$INFO
   CALL   PSTRING
   CALL   REM$DRV
   CALL   SELECT1
   CALL   PRN$DRV$INFO
   CALL   IDEinit
   CALL   RET$DRV
   JMP   MAINLOOP

PRN$DRV$INFO:         ;Print drive identification info         
   CALL   driveid
   JZ   PRN$DETAILS
   LXI   D, ID$ERROR   ;On error, display message
   CALL   PSTRING
   RET

PRN$DETAILS:         ;Get Sector Count
   LXI     H, IDbuffer + 12
   MOV   A, M      ;(High Byte)
   ORA   A
   JNZ   PRN$DET2
   INX   H
   MOV   A, M      ;(Low Byte)
   ORA   A
   JNZ   PRN$DET2   ;Looks like we have a valid IDE drive
   
   LXI   D, BAD$DRIVE   ;Zero sectors means something's wrong
   CALL   PSTRING
   RET

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

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

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

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

   LXI   D, msgLBAsup1   ;First part of LBA support message
   CALL   PSTRING
   LDA   IDbuffer+98+1   ;Bits 15-10 reserved, 9 LBA, 8 DMA
   ANI   02H
   JNZ   PRN$SUP      ;LBA is supported
   LXI   D, msgLBAnot   ;LBA is not supported
   CALL   PSTRING

PRN$SUP:
   LXI   D, msgLBAsup2
   CALL   PSTRING
   RET

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

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

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

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

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

ZEOL:            ;CR and clear current line
   MVI   C, CR
   CALL   ZCO
   MVI   C, CLEAR
   CALL   ZCO
   RET

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

SF598:   CALL   ZCONV
   RET

CHK$SEC:         ;Compare current "CPM" sector to max "CPM" sector
   PUSH   B      ;Save
   MOV   C, A      ;C <- Current Sector
   MVI   B, MAXSEC   ;Retrieve max sector number
   MOV   A, C      ;Get current sector back in A for compare (and return with it in A)
   CMP   B      ;Current : Max
   POP   B
   RET         ;Return with compare status. (Carry => Max > Current)

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

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

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

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

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

   CALL   IDEwaitnotbusy   ;Wait for drive
   JC   WaitInitErr

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

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

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

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

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

SELECT0:
   XRA   A      ;Select drive 0
   STA   mCURRENT$DRIVE
   OUT   IDEDrive   
   RET

SELECT1:
   MVI   A, 1      ;Select drive 1
   STA   mCURRENT$DRIVE
   OUT   IDEDrive   
   RET

REM$DRV:         ;Remember last drive used
   LDA   mCURRENT$DRIVE
   STA   mLAST$DRIVE
   RET

RET$DRV:         ;Return to last drive used
   LDA   mLAST$DRIVE
   STA   mCURRENT$DRIVE
   OUT   IDEDrive   
   RET

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

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

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

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

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

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

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

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

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

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

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

   CALL   IDEwaitnotbusy   ;Make sure drive is ready
   JC   SHOWerrors

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

   LHLD    mDMA
   MVI   B, 0

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

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

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

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

wrlba:
   CALL   IDEwaitnotbusy   ;Make sure drive isn't busy
   JC   SHOWErrors   ;If error, display status   

   LHLD   mTRK      ;Get the "CPM" requested track High & Low
   MOV   A, L      ;Get Low byte of track
   RRC         ;Get bottom two bits in high bits of A
   RRC
   ANI   0C0H      ;Just what were the bottom two bits (now at the top)
   MOV   C, A      ;Save in C
   LDA   mSEC      ;Sector number in A
   ANI   03FH      ;Take only bottom 6 bits
   ORA   C      ;Add in top 2 bits of track
   STA   mDRIVE$SEC   ;For diagnostic display only
   MOV   D, A      ;Send info to the drive
   MVI   E, REGsector
   CALL   IDEwr8D

   MOV   A, L      ;Get low byte of track again
   RRC
   RRC
   ANI   03FH
   MOV   C, A      ;Save in C
   MOV   A, H      ;Get high byte of track.
   RRC         ;Rotate twice, leaving low 2 bits
   RRC         ;In upper bits of A
   ANI   0C0H      ;Mask all but the two bits we want
   ORA   C      ;Add in the top 6 bits of the first track byte
   STA   mDRIVE$TRK
   MOV   D, A      ;Send Low TRK#
   MVI   E, REGcylinderLSB
   CALL   IDEwr8D
   
   MOV   A, H      ;Get high byte of track
   RRC         ;Just the top 6 bits
   RRC
   ANI   03FH
   STA   mDRIVE$TRK+1
   MOV   D, A      ;Send High TRK#
   MVI   E, REGcylinderMSB
   CALL   IDEwr8D

   MVI   D, 1      ;One sector at a time
   MVI   E, REGseccnt
   CALL   IDEwr8D
   RET

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

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

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

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

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

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

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

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

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

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

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

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

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

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

   MOV   A, E
   OUT   IDEportC      ;Deassert RD pin

   XRA   A
   OUT   IDEportC      ;Zero all port C lines
   RET

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

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

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

   MOV   A, E         ;Select IDE register
   OUT   IDEportC

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

   XRA   A         ;Deselect all lines including WR line
   OUT   IDEportC

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

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

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

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

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

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

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

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

RAMAREA      DB   '           RAM STORE AREA -------->'
mDMA      DW   buffer
mDRIVE$SEC   DB   0H
mDRIVE$TRK   DW   0H
mDisplayFlag   DB   0FFH      ;Display of sector data initially ON
mSEC      DW   0H
mTRK      DW   0H
mSEC1      DW   0H      ;For disk partition copy
mTRK1      DW   0H
mSEC2      DW   0H
mTRK2      DW   0H
mPART$NUM   DB   0H      ;Backup partition (01-FF)
mStartLineHex   DW   0H
mStartLineASCII   DW   0H
mBYTE$COUNT   DW   0H
mSECTOR$COUNT   DW   0H
mDELAYStore   DB   0H
mCURRENT$DRIVE   DB   0H
mLAST$DRIVE   DB   0H
mDriveBPresent   DB   0H      ;1 if second drive is present

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

      ORG   BUFFER$ORG

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

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

      DS   100H

STACK      DW   0H

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

Re: IDE interface for Altair 8800c

Postby Wayne Parham » December 14th, 2022, 12:26 pm

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

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

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

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

Which method would you suggest?

I'm really at the front end of the learning curve, as you can probably tell. So I'd love some input.
Wayne Parham
 
Posts: 246
Joined: March 18th, 2022, 3:01 pm

Re: IDE interface for Altair 8800c

Postby AltairClone » December 16th, 2022, 10:46 am

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

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

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

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

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

Re: IDE interface for Altair 8800c

Postby Wayne Parham » December 16th, 2022, 11:59 am

Thanks so much for the reply.

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

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

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

Re: IDE interface for Altair 8800c

Postby Wayne Parham » December 21st, 2022, 4:26 pm

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

Latest IDEutil v.3.0 12/21/2022
Code: Select all
;------------------------------------------------------------------------------
; Utility Program for IDE interface board
;   v3.0         12/21/2022
;
; Wayne Parham        wayne@parhamdata.com
;
; The IDE interface board is expected to be configured for I/O base address 30H
; but can be changed by redefining IDE interface equates below.
;
; IDEutil.com can be built using the following commands:
;
; ASM IDEutil
; HEXCOM IDEutil
;
; This program is largely borrowed from John Monahan's "myIDE" used to  support
; the  IDE/CF v4 board sold by S100computers.com.   It is generally  compatible
; with myIDE except for some new features and cosmetic changes.  IDEutil allows
; 2Gb drives to store 256 "partitions," with partition 00 being the CP/M active
; data space and partitions 01 - FF available for backup and restore copies.
;
; The  IDEutil  program also differs from myIDE, avoiding the  @  character  in
; labels so it can be built with the Digital Research ASM assembler.
;
; Other credits should be given to Peter Faasse and to David Fry.  Peter Faasse
; described the 8255 interface that is implemented herein.  David Fry wrote the
; wrlba  function used to translate CP/M track and sector addresses to  logical
; block addressing.
;
;------------------------------------------------------------------------------

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

FALSE      EQU   0
TRUE      EQU   NOT FALSE

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

   ORG   100H

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

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

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

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

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

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

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

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

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

   LXI   D, INIT$1$ERROR   ;Non-zero is error, so print warning
   CALL   PSTRING
   XRA   A
   STA   mLast$Drive   ;Only drive 0 attached
   JMP   INIT$DONE

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

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

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

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

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

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

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

   LXI   D,msgLBAsup1   ;First part of LBA support message
   CALL   PSTRING
   LDA   IDbuffer+98+1   ;Bits 15-10 reserved, 9 LBA, 8 DMA
   ANI   02H
   JNZ   ID$SUP1      ;LBA is supported
   LXI   D,msgLBAnot   ;LBA is not supported
   CALL   PSTRING
ID$SUP1:
   LXI   D,msgLBAsup2
   CALL   PSTRING
  ENDIF
   MVI   A, 1
   STA   mLast$Drive   ;Both drives 0 and 1 are attached         

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

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

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

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

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

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

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

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

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

   PCHL         ;Jump to command function address

;---------------------------- COMMAND VECTOR TABLE ----------------------------   

TBL:   DW  DRIVE$0   ; "A"  Select Drive 0
   DW  DRIVE$1   ; "B"  Select Drive 1
   DW  Cpy$Partn ; "C"  Copy Partition
   DW  DISPLAY   ; "D"  Sector contents display: ON/OFF
   DW  RAMCLEAR  ; "E"  Clear RAM buffer
   DW  FORMAT    ; "F"  Format current disk
   DW  RESTORE   ; "G"  Restore backup
   DW  BACKUP    ; "H"  Backup partition
   DW  PRN$1$INFO; "I"  Print Drive 1 ID info
   DW  ERROR     ; "J"
   DW  SET$PARTN ; "K"  Set LBA value to start of selected partition
   DW  SET$LBA   ; "L"  Set LBA value using selected sector and track
   DW  SHOW$BUF  ; "M"  Show sector buffer memory without disk read
   DW  NEXT$SECT ; "N"  Next Sector
   DW  PRN$0$INFO; "O"  Print Drive 0 ID info
   DW  PREV$SEC  ; "P"  Previous sector
   DW  ERROR     ; "Q"
   DW  READ$SEC  ; "R"  Read sector to data buffer
   DW  SEQ$RD    ; "S"  Sequental sec read and display contents
   DW  POWER$DOWN; "T"  Power down hard disk command 
   DW  POWER$UP  ; "U"  Power up hard disk command
   DW  N$RD$SEC  ; "V"  Read N sectors
   DW  WRITE$SEC ; "W"  Write data buffer to current sector
   DW  N$WR$SEC  ; "X"  Write N sectors
   DW  Cmp$Partn ; "Y"  Verify Partition
   DW  CPMBOOT   ; "Z"  LOAD CP/M (if present)

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

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

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

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

   CALL   READSECTOR

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

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

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

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

   LXI   H, buffer   ;Point to buffer
   SHLD   mDMA

   CALL   WRITESECTOR

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

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

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

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

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

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

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

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

DRIVE$1:
   MVI   A, 1
   CALL   Val$Drive   ;Verify that drive 1 is connected
   JNC   Dexist
   LXI     D, DRV$NOT$FOUND   
   CALL   PSTRING
   JMP   MAINLOOP
Dexist:   CALL   SELECT1      ;Select drive 1
   LXI     D, SET1$MSG   
   CALL   PSTRING
   JMP   MAINLOOP

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

FORMAT:            ;Format (Fill sectors with E5)
   LXI   D, FORMAT$MSG
   CALL   PSTRING
   LXI     D, msgsure   ;Are you sure?
   CALL   PSTRING
   CALL   ZCI
   CALL   UPPER
   CPI   'Y'
   JNZ   MAINLOOP

   CALL   REM$DRV      ;Remember current drive position

   LXI   H, buffer   ;Fill buffer with E5's (512 of them)
   MVI   B, 0
Fill0:   MVI   A, 0E5H      ;<-- Sector fill character (E5 for CP/M)
   MOV   M, A
   INX   H
   MOV   M, A
   INX   H
   DCR   B
   JNZ   Fill0
   CALL   ZCRLF

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

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

NextFormatDone:
   LXI   D, FormatDone   ;Tell us we are all done.
   CALL   PSTRING
   JMP   MAINLOOP
            
BACKUP:            ;Backup the CP/M partition to another area
   LXI   D, PartnExpln
   CALL   PSTRING
   LXI   D, BackupMsg
   CALL   PSTRING

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

   CALL   REM$DRV      ;Remember current drive position

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

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

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

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

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

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

NextBkup1:   
   CALL   ZCR      ;Return to beginning of line
   LXI   D, RBackup$MSG   ;for each track update display
   CALL   PSTRING
   LDA   mTRK1+1      ;High TRK byte
   CALL   PHEX
   LDA   mTRK1      ;Low TRK byte
   CALL   PHEX
   LXI   D, WBackup$MSG
   CALL   PSTRING
   LDA   mTRK2+1      ;High TRK byte
   CALL   PHEX
   LDA   mTRK2      ;Low TRK byte
   CALL   PHEX
   LXI   D, H$Msg
   CALL   PSTRING

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

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

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

BKNEXTZERO:
   LXI   H, 0      ;Back to CP/M sector 0
   SHLD   mSEC1
   SHLD   mSEC2
   
   LHLD   mTRK1      ;Get current track
   MOV   A, L      ;Into A
   CPI   MAXTRK      ;Already at max?
   JZ   BKNextDone   ;If so, we are done
   INX   H
   SHLD   mTRK1
   
   LHLD   mTRK2      ;Bump to next track
   INX   H
   SHLD   mTRK2
   JMP   NextBkup1
   
BKNextDone:
   LXI   D, BackupDone   ;Tell us we are all done.
   CALL   PSTRING
   CALL   RET$DRV      ;Return to original drive and position
   JMP   MAINLOOP

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

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

   CALL   REM$DRV      ;Remember current drive position

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

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

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

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

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

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

NextRestore1:   
   CALL   ZCR      ;Return to beginning of line
   LXI   D, RBackup$MSG   ;for each track update display
   CALL   PSTRING
   LDA   mTRK2+1      ;High TRK byte
   CALL   PHEX
   LDA   mTRK2      ;Low TRK byte
   CALL   PHEX
   LXI   D, WBackup$MSG
   CALL   PSTRING
   LDA   mTRK1+1      ;High TRK byte
   CALL   PHEX
   LDA   mTRK1      ;Low TRK byte
   CALL   PHEX
   LXI   D,H$Msg
   CALL   PSTRING

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

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

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

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

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

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

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

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

   CALL   REM$DRV      ;Remember current drive position

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

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

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

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

   LXI   H, 0      ;Start with sector 0
   SHLD   mSEC1

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

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

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

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

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

   LHLD   mTRK2
   INX   H
   SHLD   mTRK2
   JMP   NextCopy1

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

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

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

   CALL   REM$DRV      ;Remember current drive position

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

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

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

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

   LXI   H, 0      ;Start with sector 0
   SHLD   mSEC1

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

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

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

   LXI   B, 512      ;Now check both buffers are identical
   LXI   H, buffer
   LXI   D, buffer2
NEXTV:   LDAX   D
   CMP   M      ;Is [DE] = [HL]?
   JNZ   COMPARE$ERROR
   INX   H
   INX   D
   DCX   B
   MOV   A, C
   ORA   B
   JZ   VERIFY$OK
   JMP   NEXTV

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

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

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

   LHLD   mTRK2
   INX   H
   SHLD   mTRK2
   JMP   NextCmp1

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

Re: IDE interface for Altair 8800c

Postby Wayne Parham » December 21st, 2022, 4:27 pm

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

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

SequentialReads:      ;Sequentially read sectors from current position
   CALL   IDEwaitnotbusy
   JC   SHOWerrors
   CALL   ZCRLF

NEXTSEC:
   LXI   H, buffer   ;Point to buffer
   SHLD   mDMA

   CALL   READSECTOR   ;Errors will show in READSECTOR
   JZ   SEQOK
   LXI   D, CONTINUE$MSG
   CALL   PSTRING
   CALL   ZCI
   CPI   ESC      ;Abort if ESC
   RZ
   
SEQOK:   CALL   ZCR      ;Return to beginning of line
   CALL   DISPLAYposition   ;Display current track, sector and head

   LXI   H, buffer   ;Point to buffer
   SHLD   mDMA

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

   CALL   ZCSTS      ;Any keyboard character will stop display
   CPI   01H      ;CP/M says something there
   JNZ   NEXTSEC1
   CALL   ZCI      ;Flush character
   LXI   D, CONTINUE$MSG
   CALL   PSTRING
   CALL   ZCI
   CPI   ESC
   RZ
   CALL   ZCRLF

NEXTSEC1:
   LHLD   mSEC
   MOV   A, L      ;Current sector to A
   CALL   CHK$SEC      ;Are we already at max sector?
   JZ   NextSecZero   ;Yes - back to sector 0
   INX   H      ;No - bump to next sector
   SHLD   mSEC   
   JMP   NEXTSEC

NextSecZero:
   LXI   H, 0      ;Back to CP/M sector 0
   SHLD   mSEC
   LHLD   mTRK      ;Bump to next track
   INX   H
   SHLD   mTRK
   JMP   NEXTSEC      ;Note will go to last track on disk unless stopped

PRN$0$INFO:         ;Print Drive 0 identification info
   LXI   D, DRIVE0$INFO
   CALL   PSTRING
   CALL   REM$DRV      ;Remember current drive position
   CALL   SELECT0
   CALL   PRN$DRV$INFO
   CALL   IDEinit
   CALL   RET$DRV      ;Return to original drive and position
   JMP   MAINLOOP

PRN$1$INFO:         ;Print Drive 1 identification info
   LXI   D, DRIVE1$INFO
   CALL   PSTRING
   CALL   REM$DRV      ;Remember current drive position
   CALL   SELECT1
   CALL   PRN$DRV$INFO
   CALL   IDEinit
   CALL   RET$DRV      ;Return to original drive and position
   JMP   MAINLOOP

PRN$DRV$INFO:         ;Print drive identification info         
   CALL   driveid
   JZ   PRN$DETAILS
   LXI   D, ID$ERROR   ;On error, display message
   CALL   PSTRING
   RET

PRN$DETAILS:         ;Get Sector Count
   LXI     H, IDbuffer + 12
   MOV   A, M      ;(High Byte)
   ORA   A
   JNZ   PRN$DET2
   INX   H
   MOV   A, M      ;(Low Byte)
   ORA   A
   JNZ   PRN$DET2   ;Looks like we have a valid IDE drive
   
   LXI   D, BAD$DRIVE   ;Zero sectors means something's wrong
   CALL   PSTRING
   RET

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

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

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

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

   LXI   D, msgLBAsup1   ;First part of LBA support message
   CALL   PSTRING
   LDA   IDbuffer+98+1   ;Bits 15-10 reserved, 9 LBA, 8 DMA
   ANI   02H
   JNZ   PRN$SUP      ;LBA is supported
   LXI   D, msgLBAnot   ;LBA is not supported
   CALL   PSTRING

PRN$SUP:
   LXI   D, msgLBAsup2
   CALL   PSTRING
   RET

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

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

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

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

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

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

ZERA:            ;Return to beginning of line and erase [B] characters
   MVI   C, CR
   CALL   ZCO
   MVI   C, SPACE
ERAX:   CALL   ZCO
   DCR   B
   JNZ   ERAX
   MVI   C, CR
   CALL   ZCO
   RET

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

ghex32lba:         ;Convert CP/M Track & Sector to LBA format
   LXI     D,ENTER$SECH
   CALL   PSTRING
   CALL   GETHEX      ;Enter high byte sector number
   RC
   STA   mSEC+1
   CALL   ZCRLF

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

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

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

   XRA   A
   ORA   A      ;Clear Accumulator and Carry bit
   RET

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

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

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

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

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

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

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

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

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

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

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

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

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

SF598:   CALL   ZCONV
   RET

CHK$SEC:         ;Compare current CP/M sector to max CP/M sector
   PUSH   B      ;Save
   MOV   C, A      ;C <- Current Sector
   MVI   B, MAXSEC   ;Retrieve max sector number
   MOV   A, C      ;Get current sector back in A for compare (and return with it in A)
   CMP   B      ;Current : Max
   POP   B
   RET         ;Return with compare status. (Carry => Max > Current)

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

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

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

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

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

Val$Drive:         ;Check if drive [A] is valid
   LHLD   mLast$Drive
   INX   H
   CMP   L      ;Is drive valid?
   JC   Vdone
   LXI   D, DRV$NOT$FOUND
   CALL   PSTRING
Vdone:   CMC
   RET

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

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

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

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

   CALL   IDEwaitnotbusy   ;Wait for drive
   JC   WaitInitErr

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

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

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

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

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

SELECT0:         ;Select drive 0
   XRA   A
   JMP   SELECTdrive

SELECT1:         ;Select drive 1
   MVI   A, 1

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

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

RET$DRV:         ;Return to last drive and position
   LDA   mREM$DRIVE
   STA   mCURRENT$DRIVE
   OUT   IDEDrive   
   LHLD   mREM$SEC
   SHLD   mSEC
   LHLD   mREM$TRK
   SHLD   mTRK
   RET

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

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

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

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

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

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

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

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

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

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

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

   CALL   IDEwaitnotbusy   ;Make sure drive is ready
   JC   SHOWerrors

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

   LHLD    mDMA

   MVI   B, 0

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

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

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

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

wrlba:
   CALL   IDEwaitnotbusy   ;Make sure drive isn't busy
   JC   SHOWErrors   ;If error, display status   

   LHLD   mTRK      ;Get the CP/M requested track High & Low
   MOV   A, L      ;Get Low byte of track
   RRC         ;Get bottom two bits in high bits of A
   RRC
   ANI   0C0H      ;Just what were the bottom two bits (now at the top)
   MOV   C, A      ;Save in C
   LDA   mSEC      ;Sector number in A
   ANI   03FH      ;Take only bottom 6 bits
   ORA   C      ;Add in top 2 bits of track
   STA   mDRIVE$SEC   ;For diagnostic display only
   MOV   D, A      ;Send info to the drive
   MVI   E, REGsector
   CALL   IDEwr8D

   MOV   A, L      ;Get low byte of track again
   RRC
   RRC
   ANI   03FH
   MOV   C, A      ;Save in C
   MOV   A, H      ;Get high byte of track.
   RRC         ;Rotate twice, leaving low 2 bits
   RRC         ;In upper bits of A
   ANI   0C0H      ;Mask all but the two bits we want
   ORA   C      ;Add in the top 6 bits of the first track byte
   STA   mDRIVE$TRK
   MOV   D, A      ;Send Low TRK#
   MVI   E, REGcylinderLSB
   CALL   IDEwr8D
   
   MOV   A, H      ;Get high byte of track
   RRC         ;Just the top 6 bits
   RRC
   ANI   03FH
   STA   mDRIVE$TRK+1
   MOV   D, A      ;Send High TRK#
   MVI   E, REGcylinderMSB
   CALL   IDEwr8D

   MVI   D, 1      ;One sector at a time
   MVI   E, REGseccnt
   CALL   IDEwr8D
   RET

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

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

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

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

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

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

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

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

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

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

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

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

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

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

   MOV   A, E
   OUT   IDEportC      ;Deassert RD pin

   XRA   A
   OUT   IDEportC      ;Zero all port C lines
   RET

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

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

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

   MOV   A, E         ;Select IDE register
   OUT   IDEportC

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

   XRA   A         ;Deselect all lines including WR line
   OUT   IDEportC

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

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

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

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

SIGN$ON:   DB   CR,LF,'IDE Disk Drive Utility Program  v3.0  12-21-2022',CR,LF,LF,'$'
SEL0MSG      DB   'Selecting first IDE drive.',CR,LF,'$'
SEL1MSG      DB   'Selecting second IDE drive.',CR,LF,'$'
INITDRIVE   DB   'Initializing drive.  $'
READING$ID   DB   'Reading drive ID.  $'
GETTING$ID   DB   'Getting drive ID...',CR,LF,'$'
DISKSTATUS   DB   'Status is $'
INIT$0$ERROR:   DB   'Initialization of first drive failed. Aborting program.',BELL,CR,LF,LF,'$'
INIT$1$ERROR   DB   'Initialization of second drive failed. (Possibly not present).',BELL,CR,LF,LF,'$'
ID$ERROR:   DB   'Error obtaining drive ID.',BELL,CR,LF,'$'
INIT$DR$OK:   DB   'Drive initialized OK.',CR,LF,LF,'$'
BAD$DRIVE:   DB   CR,LF,'First Drive ID Information appears invalid.',CR,LF
      DB   'Aborting program.',BELL,CR,LF,LF,'$'
DRIVE0$INFO:   DB   '------------ Drive 0 -------------',CR,LF,'$'
DRIVE1$INFO:   DB   '------------ Drive 1 -------------',CR,LF,'$'
msgmdl:      DB   'Model: $'
msgsn:      DB   'S/N:   $'
msgrev:      DB   'Rev:   $'
msgcy:      DB   'Cyl: $'
msghd:      DB   ', Hd: $'
msgsc:      DB   ', Sec: $'
msgCPMTRK:   DB   'CPM TRK = $'
msgCPMSEC:   DB   ' CPM SEC = $'
msgLBA:      DB   '  (LBA = 00$'
MSGBracket   DB   ')$'
msgLBAsup1:   DB   'LBA is $'
msgLBAnot:   DB   'NOT $'
msgLBAsup2   DB   'supported',CR,LF,'$'
DRIVE$0$MSG   DB   CR,LF,LF,'  >>> DRIVE #0 <<<$'
DRIVE$1$MSG   DB   CR,LF,LF,'  >>> DRIVE #1 <<<$'
CMD$STRING1:    DB   '     IDE Board Diagnostic MAIN MENU',CR,LF,LF
      DB   '(A) Select Drive 0        (O) Drive 0 Information   '
      DB   '(H) Backup Disk',CR,LF
      DB   '(B) Select Drive 1        (I) Drive 1 Information   '
      DB   '(G) Restore Backup',CR,LF
      DB   '(K) Set LBA by Partition  (M) Show Buffer w/o Read  '
      DB   '(E) Clear Buffer',CR,LF
      DB   '(L) Set LBA Track, Sector (R) Read Sector to Buffer '
      DB   '(W) Write Buffer to Sector',CR,LF
      DB   '(N) Next Sector           (V) Read N Sectors        '
      DB   '(X) Write N Sectors',CR,LF
      DB   '(P) Previous Sector       (S) Sequental Sector Read '
      DB   '(C) Copy Partition',CR,LF
      DB   '(U) Power Up              (T) Power Down            '
      DB   '(Y) Verify Partition',CR,LF
      DB   '(F) Format Disk           (D) Set Display ON        '
      DB   '(ESC) Quit',CR,LF
      DB   LF,'Current settings: $'
CMD$STRING2:    DB   '     IDE Board Diagnostic MAIN MENU',CR,LF,LF
      DB   '(A) Select Drive 0        (O) Drive 0 Information   '
      DB   '(H) Backup Disk',CR,LF
      DB   '(B) Select Drive 1        (I) Drive 1 Information   '
      DB   '(G) Restore Backup',CR,LF
      DB   '(K) Set LBA by Partition  (M) Show Buffer w/o Read  '
      DB   '(E) Clear Buffer',CR,LF
      DB   '(L) Set LBA Track, Sector (R) Read Sector to Buffer '
      DB   '(W) Write Buffer to Sector',CR,LF
      DB   '(N) Next Sector           (V) Read N Sectors        '
      DB   '(X) Write N Sectors',CR,LF
      DB   '(P) Previous Sector       (S) Sequental Sector Read '
      DB   '(C) Copy Partition',CR,LF
      DB   '(U) Power Up              (T) Power Down            '
      DB   '(Y) Verify Partition',CR,LF
      DB   '(F) Format Disk           (D) Set Display OFF       '
      DB   '(ESC) Quit',CR,LF
      DB   LF,'Current settings: $'
Prompt:      DB   CR,LF,LF,'Please enter command > $'
Response:   DB   CR,LF,'Command received:      $'
msgsure:   DB   CR,LF,'Warning: this will change data on the drive, '
      DB   'are you sure? $'
AreYouSure   DB   CR,LF,'Are you sure? $'
DoYouWant   DB   CR,LF,'Is that what you want to do? $'
msgrd:      DB   CR,LF,'Sector Read OK',CR,LF,'$'
msgwr:      DB   CR,LF,'Sector Write OK',CR,LF,'$'
GET$LBA:   DB   'Enter CPM style TRK & SEC values (in hex).',CR,LF,'$'
SEC$RW$ERROR   DB   'Drive Error, Status Register = $'
ERR$REG$DATA   DB   'Drive Error, Error Register = $'
ENTER$SECL   DB   'Sector number (LOW byte, xxH) = $'
ENTER$SECH   DB   'Sector number (HIGH byte, xxH) = $'
ENTER$TRKL   DB   'Track number (LOW byte, xxH) = $'
ENTER$TRKH   DB   'Track number (HIGH byte, xxH) = $'
ENTER$HEAD   DB   'Head number (01-0F) = $'
ENTER$COUNT   DB   'Number of sectors to R/W = $'
DRIVE$BUSY   DB   'Drive Busy (bit 7) stuck high.   Status = $'
DRIVE$NOT$READY   DB   'Drive Ready (bit 6) stuck low.  Status = $'
DRIVE$WR$FAULT   DB   'Drive write fault.    Status = $'
UNKNOWN$ERROR   DB   'Unknown error in status register.   Status = $'
BAD$BLOCK   DB   'Bad Sector ID.    Error Register = $'
UNRECOVER$ERR   DB   'Uncorrectable data error.  Error Register = $'
READ$ID$ERROR   DB   'Error setting up to read Drive ID',CR,LF,'$'
SEC$NOT$FOUND   DB   'Sector not found. Error Register = $'
INVALID$CMD   DB   'Invalid Command. Error Register = $'
TRK0$ERR   DB   'Track Zero not found. Error Register = $'
UNKNOWN$ERROR1   DB   'Unknown Error. Error Register = $'
CONTINUE$MSG   DB   CR,LF,'ESC to abort. Any other key to continue. $'
FORMAT$MSG   DB   'FORMAT DISK. Fill all sectors with E5'
      DB   60H,'s on the current drive.$'
ReadN$MSG   DB   CR,LF,'Read multiple sectors from current drive to RAM buffer.'
      DB   CR,LF,'How many 512 byte sectors (xx HEX):$'
WriteN$MSG   DB   CR,LF,'Write multiple sectors from RAM buffer to current drive.'
      DB   CR,LF,'How many 512 byte sectors (xx HEX):$'
ReadingN$MSG   DB   CR,LF,'Reading Sector at: $'
WritingN$MSG   DB   CR,LF,'Writing Sector at: $'
msgErr      DB   CR,LF,'Sorry, that was not a valid menu option!$'
FormatDone   DB   CR,LF,'Disk Format Complete.',CR,LF,'$'
BackupDone   DB   CR,LF,'Disk partition copy complete.',CR,LF,'$'
PartnExpln   DB   CR,LF,'Each 2Gb physical disk is structured as 256'
      DB   ' "partitions" of 8Mb each.  The CP/M'
      DB   CR,LF,'operating system can directly access only'
      DB   ' partition 00, but all the others can'
      DB   CR,LF,'be used as backups or archives.  The backup'
      DB   ' partitions are numbered 00 - FF.',CR,LF,'$'
BackupMsg   DB   CR,LF,'This will copy data from the main CP/M'
      DB   ' partition on the current drive to a'
      DB   CR,LF,'backup partition.',CR,LF,'$'
RestoreMsg   DB   CR,LF,'This will restore data from a backup'
      DB   ' partition to the main CP/M partition on'
      DB   CR,LF,'the current drive.',CR,LF,'$'
CopyMsg      DB   CR,LF,'This will copy data from any partition to'
      DB   ' any other partition on either drive.',CR,LF,'$'
Enter$Partition   DB   CR,LF,LF,'Choose a partition number (00-FF) $'
Enter$Bkup$Part   DB   CR,LF,LF,'Choose a backup partition (01-FF) $'
Enter$Src$Partn   DB   CR,LF,'Choose source partition (00-FF) $'
Enter$Tgt$Partn   DB   CR,LF,'Choose target partition (00-FF) $'
Enter$Src$Drive   DB   CR,LF,'Choose source drive (00 or 01) $'
Enter$Tgt$Drive   DB   CR,LF,'Choose target drive (00 or 01) $'
ConfirmCopy   DB   CR,LF,'This will copy drive $'
ConfirmCmp   DB   CR,LF,'This will compare drive $'
Partition   DB   ' partition $'
ToDrive      DB   ' to drive $'
AtEnd      DB   CR,LF,'At end of disk partition!',CR,LF,'$'
RBackup$MSG   DB   'Reading track: $'
WBackup$MSG   DB   'H. Writing track: $'
H$Msg      DB   'H$'
RestoreDone   DB   CR,LF,'Restore of disk data from backup partition complete.',CR,LF,'$'
DRV$NOT$FOUND   DB   CR,LF,LF,'Drive not connected.',CR,LF,'$'
RANGE$MSG   DB   CR,LF,LF,'Value out of range.',CR,LF,'$'
INVALID$MSG   DB   CR,LF,LF,'Invalid input.',CR,LF,'$'
CPM$ERROR   DB   CR,LF,'Error reading CPMLDR.',CR,LF,'$'
CPM$ERROR1   DB   CR,LF,'Data error reading CPMLDR. (The first byte loaded was not 31H).',CR,LF,'$'
MOVE$REQUEST   DB   CR,LF,'The CPMLDR image is now at 3000H in RAM. '
      DB   'To boot CPM you will have to'
      DB   CR,LF,'overwrite this program at 100H. Do you wish to do so? $'
SET0$MSG   DB   CR,LF,'Current drive is now #0 (Yellow LED)$'
SET1$MSG   DB   CR,LF,'Current drive is now #1 (Green LED)$'
FILL$MSG   DB   CR,LF,'Sector buffer in RAM filled with 0',27H,'s$'      
CopyDone   DB   CR,LF,LF,'Partition copy complete.',CR,LF,'$'
CopyTrk$MSG   DB   'Copying track $'
OnDrive$MSG   DB   ' on drive $'
ToTrack$MSG   DB   ' to track $'
VerifyMsg   DB   CR,LF,'This will compare any two partitions on either drive'
      DB   ' and will report any',CR,LF,'differences.',CR,LF,'$'
VerifyTrk$MSG   DB   'Comparing track $'
VerifyDone   DB   CR,LF,LF,'Partition verification complete.',CR,LF,'$'
Verify$ERR   DB   CR,LF,BELL,'Verify error on track $'
SEC$Msg      DB   'H  Sector $'

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

RAMAREA      DB   '           RAM STORE AREA -------->'
mDMA      DW   buffer
mDRIVE$SEC   DB   0H
mDRIVE$TRK   DW   0H
mDisplayFlag   DB   0FFH      ;Display of sector data initially ON
mSEC      DW   0H
mTRK      DW   0H
mSEC1      DW   0H      ;For disk partition copy
mTRK1      DW   0H
mSEC2      DW   0H
mTRK2      DW   0H
mSrc$Drive   DB   0H      ;User-inputs for copy and restore commands
mSrc$Partn   DB   0H
mTgt$Drive   DB   0H
mTgt$Partn   DB   0H
mPART$NUM   DB   0H      ;Backup partition (01-FF)
mStartLineHex   DW   0H
mStartLineASCII   DW   0H
mBYTE$COUNT   DW   0H
mSECTOR$COUNT   DW   0H
mDELAYStore   DB   0H
mCURRENT$DRIVE   DB   0H
mREM$DRIVE   DB   0H
mREM$SEC   DW   0H
mREM$TRK   DW   0H
mLast$Drive   DB   0H      ;0 or 1

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

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

      ORG   BUFFER$ORG

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

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

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

Re: IDE interface for Altair 8800c

Postby Wayne Parham » December 22nd, 2022, 7:39 pm

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

Code: Select all
;------------------------------------------------------------------------------
; Utility Program for IDE interface board
;   v3.0         12/21/2022
;
; Wayne Parham        wayne@parhamdata.com
;
; The IDE interface board is expected to be configured for I/O base address 30H
; but can be changed by redefining IDE interface equates below.
;
; IDEutil.com can be built using the following commands:
;
; ASM IDEutil
; HEXCOM IDEutil
;
; This program is largely borrowed from John Monahan's "myIDE" used to  support
; the  IDE/CF v4 board sold by S100computers.com.   It is generally  compatible
; with myIDE except for some new features and cosmetic changes.  IDEutil allows
; 2Gb drives to store 256 "partitions," with partition 00 being the CP/M active
; data space and partitions 01 - FF available for backup and restore copies.
;
; The  IDEutil  program also differs from myIDE, avoiding the  @  character  in
; labels so it can be built with the Digital Research ASM assembler.
;
; Other credits should be given to Peter Faasse and to David Fry.  Peter Faasse
; described the 8255 interface that is implemented herein.  David Fry wrote the
; wrlba  function used to translate CP/M track and sector addresses to  logical
; block addressing.
;
;------------------------------------------------------------------------------

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

FALSE      EQU   0
TRUE      EQU   NOT FALSE

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

   ORG   100H

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

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

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

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

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

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

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

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

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

   LXI   D, INIT$1$ERROR   ;Non-zero is error, so print warning
   CALL   PSTRING
   XRA   A
   STA   mLast$Drive   ;Only drive 0 attached
   JMP   INIT$DONE

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

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

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

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

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

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

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

   LXI   D,msgLBAsup1   ;First part of LBA support message
   CALL   PSTRING
   LDA   IDbuffer+98+1   ;Bits 15-10 reserved, 9 LBA, 8 DMA
   ANI   02H
   JNZ   ID$SUP1      ;LBA is supported
   LXI   D,msgLBAnot   ;LBA is not supported
   CALL   PSTRING
ID$SUP1:
   LXI   D,msgLBAsup2
   CALL   PSTRING
  ENDIF
   MVI   A, 1
   STA   mLast$Drive   ;Both drives 0 and 1 are attached         

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

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

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

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

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

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

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

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

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

   PCHL         ;Jump to command function address

;---------------------------- COMMAND VECTOR TABLE ----------------------------   

TBL:   DW  DRIVE$0   ; "A"  Select Drive 0
   DW  DRIVE$1   ; "B"  Select Drive 1
   DW  Cpy$Partn ; "C"  Copy Partition
   DW  DISPLAY   ; "D"  Sector contents display: ON/OFF
   DW  RAMCLEAR  ; "E"  Clear RAM buffer
   DW  FORMAT    ; "F"  Format current disk
   DW  RESTORE   ; "G"  Restore backup
   DW  BACKUP    ; "H"  Backup partition
   DW  PRN$1$INFO; "I"  Print Drive 1 ID info
   DW  ERROR     ; "J"
   DW  SET$PARTN ; "K"  Set LBA value to start of selected partition
   DW  SET$LBA   ; "L"  Set LBA value using selected sector and track
   DW  SHOW$BUF  ; "M"  Show sector buffer memory without disk read
   DW  NEXT$SECT ; "N"  Next Sector
   DW  PRN$0$INFO; "O"  Print Drive 0 ID info
   DW  PREV$SEC  ; "P"  Previous sector
   DW  ERROR     ; "Q"
   DW  READ$SEC  ; "R"  Read sector to data buffer
   DW  SEQ$RD    ; "S"  Sequental sec read and display contents
   DW  POWER$DOWN; "T"  Power down hard disk command 
   DW  POWER$UP  ; "U"  Power up hard disk command
   DW  Cmp$Partn ; "V"  Verify Partition
   DW  WRITE$SEC ; "W"  Write data buffer to current sector
   DW  N$WR$SEC  ; "X"  Write N sectors
   DW  N$RD$SEC  ; "Y"  Read N sectors
   DW  CPMBOOT   ; "Z"  LOAD CP/M (if present)

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

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

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

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

   CALL   READSECTOR

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

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

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

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

   LXI   H, buffer   ;Point to buffer
   SHLD   mDMA

   CALL   WRITESECTOR

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

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

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

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

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

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

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

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

DRIVE$1:
   MVI   A, 1
   CALL   Val$Drive   ;Verify that drive 1 is connected
   JNC   Dexist
   LXI     D, DRV$NOT$FOUND   
   CALL   PSTRING
   JMP   MAINLOOP
Dexist:   CALL   SELECT1      ;Select drive 1
   LXI     D, SET1$MSG   
   CALL   PSTRING
   JMP   MAINLOOP

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

FORMAT:            ;Format (Fill sectors with E5)
   LXI   D, FORMAT$MSG
   CALL   PSTRING
   LXI     D, msgsure   ;Are you sure?
   CALL   PSTRING
   CALL   ZCI
   CALL   UPPER
   CPI   'Y'
   JNZ   MAINLOOP

   CALL   REM$DRV      ;Remember current drive position

   LXI   H, buffer   ;Fill buffer with E5's (512 of them)
   MVI   B, 0
Fill0:   MVI   A, 0E5H      ;<-- Sector fill character (E5 for CP/M)
   MOV   M, A
   INX   H
   MOV   M, A
   INX   H
   DCR   B
   JNZ   Fill0
   CALL   ZCRLF

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

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

NextFormatDone:
   LXI   D, FormatDone   ;Tell us we are all done.
   CALL   PSTRING
   JMP   MAINLOOP
            
BACKUP:            ;Backup the CP/M partition to another area
   LXI   D, PartnExpln
   CALL   PSTRING
   LXI   D, BackupMsg
   CALL   PSTRING

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

   CALL   REM$DRV      ;Remember current drive position

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

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

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

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

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

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

NextBkup1:   
   CALL   ZCR      ;Return to beginning of line
   LXI   D, RBackup$MSG   ;for each track update display
   CALL   PSTRING
   LDA   mTRK1+1      ;High TRK byte
   CALL   PHEX
   LDA   mTRK1      ;Low TRK byte
   CALL   PHEX
   LXI   D, WBackup$MSG
   CALL   PSTRING
   LDA   mTRK2+1      ;High TRK byte
   CALL   PHEX
   LDA   mTRK2      ;Low TRK byte
   CALL   PHEX
   LXI   D, H$Msg
   CALL   PSTRING

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

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

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

BKNEXTZERO:
   LXI   H, 0      ;Back to CP/M sector 0
   SHLD   mSEC1
   SHLD   mSEC2
   
   LHLD   mTRK1      ;Get current track
   MOV   A, L      ;Into A
   CPI   MAXTRK      ;Already at max?
   JZ   BKNextDone   ;If so, we are done
   INX   H
   SHLD   mTRK1
   
   LHLD   mTRK2      ;Bump to next track
   INX   H
   SHLD   mTRK2
   JMP   NextBkup1
   
BKNextDone:
   LXI   D, BackupDone   ;Tell us we are all done.
   CALL   PSTRING
   CALL   RET$DRV      ;Return to original drive and position
   JMP   MAINLOOP

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

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

   CALL   REM$DRV      ;Remember current drive position

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

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

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

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

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

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

NextRestore1:   
   CALL   ZCR      ;Return to beginning of line
   LXI   D, RBackup$MSG   ;for each track update display
   CALL   PSTRING
   LDA   mTRK2+1      ;High TRK byte
   CALL   PHEX
   LDA   mTRK2      ;Low TRK byte
   CALL   PHEX
   LXI   D, WBackup$MSG
   CALL   PSTRING
   LDA   mTRK1+1      ;High TRK byte
   CALL   PHEX
   LDA   mTRK1      ;Low TRK byte
   CALL   PHEX
   LXI   D,H$Msg
   CALL   PSTRING

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

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

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

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

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

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

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

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

   CALL   REM$DRV      ;Remember current drive position

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

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

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

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

   LXI   H, 0      ;Start with sector 0
   SHLD   mSEC1

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

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

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

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

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

   LHLD   mTRK2
   INX   H
   SHLD   mTRK2
   JMP   NextCopy1

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

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

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

   CALL   REM$DRV      ;Remember current drive position

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

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

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

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

   LXI   H, 0      ;Start with sector 0
   SHLD   mSEC1

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

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

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

   LXI   B, 512      ;Now check both buffers are identical
   LXI   H, buffer
   LXI   D, buffer2
NEXTV:   LDAX   D
   CMP   M      ;Is [DE] = [HL]?
   JNZ   COMPARE$ERROR
   INX   H
   INX   D
   DCX   B
   MOV   A, C
   ORA   B
   JZ   VERIFY$OK
   JMP   NEXTV

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

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

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

   LHLD   mTRK2
   INX   H
   SHLD   mTRK2
   JMP   NextCmp1

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

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

Re: IDE interface for Altair 8800c

Postby Wayne Parham » December 22nd, 2022, 7:40 pm

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

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

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

SequentialReads:      ;Sequentially read sectors from current position
   CALL   IDEwaitnotbusy
   JC   SHOWerrors
   CALL   ZCRLF

NEXTSEC:
   LXI   H, buffer   ;Point to buffer
   SHLD   mDMA

   CALL   READSECTOR   ;Errors will show in READSECTOR
   JZ   SEQOK
   LXI   D, CONTINUE$MSG
   CALL   PSTRING
   CALL   ZCI
   CPI   ESC      ;Abort if ESC
   RZ
   
SEQOK:   CALL   ZCR      ;Return to beginning of line
   CALL   DISPLAYposition   ;Display current track, sector and head

   LXI   H, buffer   ;Point to buffer
   SHLD   mDMA

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

   CALL   ZCSTS      ;Any keyboard character will stop display
   CPI   01H      ;CP/M says something there
   JNZ   NEXTSEC1
   CALL   ZCI      ;Flush character
   LXI   D, CONTINUE$MSG
   CALL   PSTRING
   CALL   ZCI
   CPI   ESC
   RZ
   CALL   ZCRLF

NEXTSEC1:
   LHLD   mSEC
   MOV   A, L      ;Current sector to A
   CALL   CHK$SEC      ;Are we already at max sector?
   JZ   NextSecZero   ;Yes - back to sector 0
   INX   H      ;No - bump to next sector
   SHLD   mSEC   
   JMP   NEXTSEC

NextSecZero:
   LXI   H, 0      ;Back to CP/M sector 0
   SHLD   mSEC
   LHLD   mTRK      ;Bump to next track
   INX   H
   SHLD   mTRK
   JMP   NEXTSEC      ;Note will go to last track on disk unless stopped

PRN$0$INFO:         ;Print Drive 0 identification info
   LXI   D, DRIVE0$INFO
   CALL   PSTRING
   CALL   REM$DRV      ;Remember current drive position
   CALL   SELECT0
   CALL   PRN$DRV$INFO
   CALL   IDEinit
   CALL   RET$DRV      ;Return to original drive and position
   JMP   MAINLOOP

PRN$1$INFO:         ;Print Drive 1 identification info
   LXI   D, DRIVE1$INFO
   CALL   PSTRING
   CALL   REM$DRV      ;Remember current drive position
   CALL   SELECT1
   CALL   PRN$DRV$INFO
   CALL   IDEinit
   CALL   RET$DRV      ;Return to original drive and position
   JMP   MAINLOOP

PRN$DRV$INFO:         ;Print drive identification info         
   CALL   driveid
   JZ   PRN$DETAILS
   LXI   D, ID$ERROR   ;On error, display message
   CALL   PSTRING
   RET

PRN$DETAILS:         ;Get Sector Count
   LXI     H, IDbuffer + 12
   MOV   A, M      ;(High Byte)
   ORA   A
   JNZ   PRN$DET2
   INX   H
   MOV   A, M      ;(Low Byte)
   ORA   A
   JNZ   PRN$DET2   ;Looks like we have a valid IDE drive
   
   LXI   D, BAD$DRIVE   ;Zero sectors means something's wrong
   CALL   PSTRING
   RET

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

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

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

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

   LXI   D, msgLBAsup1   ;First part of LBA support message
   CALL   PSTRING
   LDA   IDbuffer+98+1   ;Bits 15-10 reserved, 9 LBA, 8 DMA
   ANI   02H
   JNZ   PRN$SUP      ;LBA is supported
   LXI   D, msgLBAnot   ;LBA is not supported
   CALL   PSTRING

PRN$SUP:
   LXI   D, msgLBAsup2
   CALL   PSTRING
   RET

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

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

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

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

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

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

ZERA:            ;Return to beginning of line and erase [B] characters
   MVI   C, CR
   CALL   ZCO
   MVI   C, SPACE
ERAX:   CALL   ZCO
   DCR   B
   JNZ   ERAX
   MVI   C, CR
   CALL   ZCO
   RET

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

ghex32lba:         ;Convert CP/M Track & Sector to LBA format
   LXI     D,ENTER$SECH
   CALL   PSTRING
   CALL   GETHEX      ;Enter high byte sector number
   RC
   STA   mSEC+1
   CALL   ZCRLF

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

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

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

   XRA   A
   ORA   A      ;Clear Accumulator and Carry bit
   RET

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

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

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

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

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

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

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

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

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

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

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

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

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

SF598:   CALL   ZCONV
   RET

CHK$SEC:         ;Compare current CP/M sector to max CP/M sector
   PUSH   B      ;Save
   MOV   C, A      ;C <- Current Sector
   MVI   B, MAXSEC   ;Retrieve max sector number
   MOV   A, C      ;Get current sector back in A for compare (and return with it in A)
   CMP   B      ;Current : Max
   POP   B
   RET         ;Return with compare status. (Carry => Max > Current)

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

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

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

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

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

Val$Drive:         ;Check if drive [A] is valid
   LHLD   mLast$Drive
   INX   H
   CMP   L      ;Is drive valid?
   JC   Vdone
   LXI   D, DRV$NOT$FOUND
   CALL   PSTRING
Vdone:   CMC
   RET

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

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

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

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

   CALL   IDEwaitnotbusy   ;Wait for drive
   JC   WaitInitErr

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

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

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

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

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

SELECT0:         ;Select drive 0
   XRA   A
   JMP   SELECTdrive

SELECT1:         ;Select drive 1
   MVI   A, 1

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

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

RET$DRV:         ;Return to last drive and position
   LDA   mREM$DRIVE
   STA   mCURRENT$DRIVE
   OUT   IDEDrive   
   LHLD   mREM$SEC
   SHLD   mSEC
   LHLD   mREM$TRK
   SHLD   mTRK
   RET

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

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

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

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

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

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

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

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

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

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

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

   CALL   IDEwaitnotbusy   ;Make sure drive is ready
   JC   SHOWerrors

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

   LHLD    mDMA

   MVI   B, 0

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

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

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

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

wrlba:
   CALL   IDEwaitnotbusy   ;Make sure drive isn't busy
   JC   SHOWErrors   ;If error, display status   

   LHLD   mTRK      ;Get the CP/M requested track High & Low
   MOV   A, L      ;Get Low byte of track
   RRC         ;Get bottom two bits in high bits of A
   RRC
   ANI   0C0H      ;Just what were the bottom two bits (now at the top)
   MOV   C, A      ;Save in C
   LDA   mSEC      ;Sector number in A
   ANI   03FH      ;Take only bottom 6 bits
   ORA   C      ;Add in top 2 bits of track
   STA   mDRIVE$SEC   ;For diagnostic display only
   MOV   D, A      ;Send info to the drive
   MVI   E, REGsector
   CALL   IDEwr8D

   MOV   A, L      ;Get low byte of track again
   RRC
   RRC
   ANI   03FH
   MOV   C, A      ;Save in C
   MOV   A, H      ;Get high byte of track.
   RRC         ;Rotate twice, leaving low 2 bits
   RRC         ;In upper bits of A
   ANI   0C0H      ;Mask all but the two bits we want
   ORA   C      ;Add in the top 6 bits of the first track byte
   STA   mDRIVE$TRK
   MOV   D, A      ;Send Low TRK#
   MVI   E, REGcylinderLSB
   CALL   IDEwr8D
   
   MOV   A, H      ;Get high byte of track
   RRC         ;Just the top 6 bits
   RRC
   ANI   03FH
   STA   mDRIVE$TRK+1
   MOV   D, A      ;Send High TRK#
   MVI   E, REGcylinderMSB
   CALL   IDEwr8D

   MVI   D, 1      ;One sector at a time
   MVI   E, REGseccnt
   CALL   IDEwr8D
   RET

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

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

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

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

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

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

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

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

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

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

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

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

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

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

   MOV   A, E
   OUT   IDEportC      ;Deassert RD pin

   XRA   A
   OUT   IDEportC      ;Zero all port C lines
   RET

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

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

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

   MOV   A, E         ;Select IDE register
   OUT   IDEportC

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

   XRA   A         ;Deselect all lines including WR line
   OUT   IDEportC

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

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

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

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

SIGN$ON:   DB   CR,LF,'IDE Disk Drive Utility Program  v3.0  12-21-2022',CR,LF,LF,'$'
SEL0MSG      DB   'Selecting first IDE drive.',CR,LF,'$'
SEL1MSG      DB   'Selecting second IDE drive.',CR,LF,'$'
INITDRIVE   DB   'Initializing drive.  $'
READING$ID   DB   'Reading drive ID.  $'
GETTING$ID   DB   'Getting drive ID...',CR,LF,'$'
DISKSTATUS   DB   'Status is $'
INIT$0$ERROR:   DB   'Initialization of first drive failed. Aborting program.',BELL,CR,LF,LF,'$'
INIT$1$ERROR   DB   'Initialization of second drive failed. (Possibly not present).',BELL,CR,LF,LF,'$'
ID$ERROR:   DB   'Error obtaining drive ID.',BELL,CR,LF,'$'
INIT$DR$OK:   DB   'Drive initialized OK.',CR,LF,LF,'$'
BAD$DRIVE:   DB   CR,LF,'First Drive ID Information appears invalid.',CR,LF
      DB   'Aborting program.',BELL,CR,LF,LF,'$'
DRIVE0$INFO:   DB   '------------ Drive 0 -------------',CR,LF,'$'
DRIVE1$INFO:   DB   '------------ Drive 1 -------------',CR,LF,'$'
msgmdl:      DB   'Model: $'
msgsn:      DB   'S/N:   $'
msgrev:      DB   'Rev:   $'
msgcy:      DB   'Cyl: $'
msghd:      DB   ', Hd: $'
msgsc:      DB   ', Sec: $'
msgCPMTRK:   DB   'CPM TRK = $'
msgCPMSEC:   DB   ' CPM SEC = $'
msgLBA:      DB   '  (LBA = 00$'
MSGBracket   DB   ')$'
msgLBAsup1:   DB   'LBA is $'
msgLBAnot:   DB   'NOT $'
msgLBAsup2   DB   'supported',CR,LF,'$'
DRIVE$0$MSG   DB   CR,LF,LF,'  >>> DRIVE #0 <<<$'
DRIVE$1$MSG   DB   CR,LF,LF,'  >>> DRIVE #1 <<<$'
CMD$STRING1:    DB   '     IDE Board Diagnostic MAIN MENU',CR,LF,LF
      DB   '(A) Select Drive 0        (O) Drive 0 Information   '
      DB   '(H) Backup Disk',CR,LF
      DB   '(B) Select Drive 1        (I) Drive 1 Information   '
      DB   '(G) Restore Backup',CR,LF
      DB   '(K) Set LBA by Partition  (M) Show Buffer w/o Read  '
      DB   '(E) Clear Buffer',CR,LF
      DB   '(L) Set LBA Track, Sector (R) Read Sector to Buffer '
      DB   '(W) Write Buffer to Sector',CR,LF
      DB   '(N) Next Sector           (Y) Read N Sectors        '
      DB   '(X) Write N Sectors',CR,LF
      DB   '(P) Previous Sector       (S) Sequental Sector Read '
      DB   '(C) Copy Partition',CR,LF
      DB   '(U) Power Up              (T) Power Down            '
      DB   '(V) Verify Partition',CR,LF
      DB   '(F) Format Disk           (D) Set Display ON        '
      DB   '(ESC) Quit',CR,LF
      DB   LF,'Current settings: $'
CMD$STRING2:    DB   '     IDE Board Diagnostic MAIN MENU',CR,LF,LF
      DB   '(A) Select Drive 0        (O) Drive 0 Information   '
      DB   '(H) Backup Disk',CR,LF
      DB   '(B) Select Drive 1        (I) Drive 1 Information   '
      DB   '(G) Restore Backup',CR,LF
      DB   '(K) Set LBA by Partition  (M) Show Buffer w/o Read  '
      DB   '(E) Clear Buffer',CR,LF
      DB   '(L) Set LBA Track, Sector (R) Read Sector to Buffer '
      DB   '(W) Write Buffer to Sector',CR,LF
      DB   '(N) Next Sector           (Y) Read N Sectors        '
      DB   '(X) Write N Sectors',CR,LF
      DB   '(P) Previous Sector       (S) Sequental Sector Read '
      DB   '(C) Copy Partition',CR,LF
      DB   '(U) Power Up              (T) Power Down            '
      DB   '(V) Verify Partition',CR,LF
      DB   '(F) Format Disk           (D) Set Display OFF       '
      DB   '(ESC) Quit',CR,LF
      DB   LF,'Current settings: $'
Prompt:      DB   CR,LF,LF,'Please enter command > $'
Response:   DB   CR,LF,'Command received:      $'
msgsure:   DB   CR,LF,'Warning: this will change data on the drive, '
      DB   'are you sure? $'
AreYouSure   DB   CR,LF,'Are you sure? $'
DoYouWant   DB   CR,LF,'Is that what you want to do? $'
msgrd:      DB   CR,LF,'Sector Read OK',CR,LF,'$'
msgwr:      DB   CR,LF,'Sector Write OK',CR,LF,'$'
GET$LBA:   DB   'Enter CPM style TRK & SEC values (in hex).',CR,LF,'$'
SEC$RW$ERROR   DB   'Drive Error, Status Register = $'
ERR$REG$DATA   DB   'Drive Error, Error Register = $'
ENTER$SECL   DB   'Sector number (LOW byte, xxH) = $'
ENTER$SECH   DB   'Sector number (HIGH byte, xxH) = $'
ENTER$TRKL   DB   'Track number (LOW byte, xxH) = $'
ENTER$TRKH   DB   'Track number (HIGH byte, xxH) = $'
ENTER$HEAD   DB   'Head number (01-0F) = $'
ENTER$COUNT   DB   'Number of sectors to R/W = $'
DRIVE$BUSY   DB   'Drive Busy (bit 7) stuck high.   Status = $'
DRIVE$NOT$READY   DB   'Drive Ready (bit 6) stuck low.  Status = $'
DRIVE$WR$FAULT   DB   'Drive write fault.    Status = $'
UNKNOWN$ERROR   DB   'Unknown error in status register.   Status = $'
BAD$BLOCK   DB   'Bad Sector ID.    Error Register = $'
UNRECOVER$ERR   DB   'Uncorrectable data error.  Error Register = $'
READ$ID$ERROR   DB   'Error setting up to read Drive ID',CR,LF,'$'
SEC$NOT$FOUND   DB   'Sector not found. Error Register = $'
INVALID$CMD   DB   'Invalid Command. Error Register = $'
TRK0$ERR   DB   'Track Zero not found. Error Register = $'
UNKNOWN$ERROR1   DB   'Unknown Error. Error Register = $'
CONTINUE$MSG   DB   CR,LF,'ESC to abort. Any other key to continue. $'
FORMAT$MSG   DB   'FORMAT DISK. Fill all sectors with E5'
      DB   60H,'s on the current drive.$'
ReadN$MSG   DB   CR,LF,'Read multiple sectors from current drive to RAM buffer.'
      DB   CR,LF,'How many 512 byte sectors (xx HEX):$'
WriteN$MSG   DB   CR,LF,'Write multiple sectors from RAM buffer to current drive.'
      DB   CR,LF,'How many 512 byte sectors (xx HEX):$'
ReadingN$MSG   DB   CR,LF,'Reading Sector at: $'
WritingN$MSG   DB   CR,LF,'Writing Sector at: $'
msgErr      DB   CR,LF,'Sorry, that was not a valid menu option!$'
FormatDone   DB   CR,LF,'Disk Format Complete.',CR,LF,'$'
BackupDone   DB   CR,LF,'Disk partition copy complete.',CR,LF,'$'
PartnExpln   DB   CR,LF,'Each 2Gb physical disk is structured as 256'
      DB   ' "partitions" of 8Mb each.  The CP/M'
      DB   CR,LF,'operating system can directly access only'
      DB   ' partition 00, but all the others can'
      DB   CR,LF,'be used as backups or archives.  The backup'
      DB   ' partitions are numbered 00 - FF.',CR,LF,'$'
BackupMsg   DB   CR,LF,'This will copy data from the main CP/M'
      DB   ' partition on the current drive to a'
      DB   CR,LF,'backup partition.',CR,LF,'$'
RestoreMsg   DB   CR,LF,'This will restore data from a backup'
      DB   ' partition to the main CP/M partition on'
      DB   CR,LF,'the current drive.',CR,LF,'$'
CopyMsg      DB   CR,LF,'This will copy data from any partition to'
      DB   ' any other partition on either drive.',CR,LF,'$'
Enter$Partition   DB   CR,LF,LF,'Choose a partition number (00-FF) $'
Enter$Bkup$Part   DB   CR,LF,LF,'Choose a backup partition (01-FF) $'
Enter$Src$Partn   DB   CR,LF,'Choose source partition (00-FF) $'
Enter$Tgt$Partn   DB   CR,LF,'Choose target partition (00-FF) $'
Enter$Src$Drive   DB   CR,LF,'Choose source drive (00 or 01) $'
Enter$Tgt$Drive   DB   CR,LF,'Choose target drive (00 or 01) $'
ConfirmCopy   DB   CR,LF,'This will copy drive $'
ConfirmCmp   DB   CR,LF,'This will compare drive $'
Partition   DB   ' partition $'
ToDrive      DB   ' to drive $'
AtEnd      DB   CR,LF,'At end of disk partition!',CR,LF,'$'
RBackup$MSG   DB   'Reading track: $'
WBackup$MSG   DB   'H. Writing track: $'
H$Msg      DB   'H$'
RestoreDone   DB   CR,LF,'Restore of disk data from backup partition complete.',CR,LF,'$'
DRV$NOT$FOUND   DB   CR,LF,LF,'Drive not connected.',CR,LF,'$'
RANGE$MSG   DB   CR,LF,LF,'Value out of range.',CR,LF,'$'
INVALID$MSG   DB   CR,LF,LF,'Invalid input.',CR,LF,'$'
CPM$ERROR   DB   CR,LF,'Error reading CPMLDR.',CR,LF,'$'
CPM$ERROR1   DB   CR,LF,'Data error reading CPMLDR. (The first byte loaded was not 31H).',CR,LF,'$'
MOVE$REQUEST   DB   CR,LF,'The CPMLDR image is now at 3000H in RAM. '
      DB   'To boot CPM you will have to'
      DB   CR,LF,'overwrite this program at 100H. Do you wish to do so? $'
SET0$MSG   DB   CR,LF,'Current drive is now #0 (Yellow LED)$'
SET1$MSG   DB   CR,LF,'Current drive is now #1 (Green LED)$'
FILL$MSG   DB   CR,LF,'Sector buffer in RAM filled with 0',27H,'s$'      
CopyDone   DB   CR,LF,LF,'Partition copy complete.',CR,LF,'$'
CopyTrk$MSG   DB   'Copying track $'
OnDrive$MSG   DB   ' on drive $'
ToTrack$MSG   DB   ' to track $'
VerifyMsg   DB   CR,LF,'This will compare any two partitions on either drive'
      DB   ' and will report any',CR,LF,'differences.',CR,LF,'$'
VerifyTrk$MSG   DB   'Comparing track $'
VerifyDone   DB   CR,LF,LF,'Partition verification complete.',CR,LF,'$'
Verify$ERR   DB   CR,LF,BELL,'Verify error on track $'
SEC$Msg      DB   'H  Sector $'

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

RAMAREA      DB   '           RAM STORE AREA -------->'
mDMA      DW   buffer
mDRIVE$SEC   DB   0H
mDRIVE$TRK   DW   0H
mDisplayFlag   DB   0FFH      ;Display of sector data initially ON
mSEC      DW   0H
mTRK      DW   0H
mSEC1      DW   0H      ;For disk partition copy
mTRK1      DW   0H
mSEC2      DW   0H
mTRK2      DW   0H
mSrc$Drive   DB   0H      ;User-inputs for copy and restore commands
mSrc$Partn   DB   0H
mTgt$Drive   DB   0H
mTgt$Partn   DB   0H
mPART$NUM   DB   0H      ;Backup partition (01-FF)
mStartLineHex   DW   0H
mStartLineASCII   DW   0H
mBYTE$COUNT   DW   0H
mSECTOR$COUNT   DW   0H
mDELAYStore   DB   0H
mCURRENT$DRIVE   DB   0H
mREM$DRIVE   DB   0H
mREM$SEC   DW   0H
mREM$TRK   DW   0H
mLast$Drive   DB   0H      ;0 or 1

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

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

      ORG   BUFFER$ORG

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

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

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

PreviousNext

Return to Altair 8800c

Who is online

Users browsing this forum: No registered users and 18 guests