It still needs some work, so we're not "cookin' with gas" yet but we definitely have the pilot light lit.
- Code: Select all
;------------------------------------------------------------------------------
; Utility Program for IDE interface board
; v2.9b 12/01/2022
;------------------------------------------------------------------------------
;------------------------------------------------------------------------------
;Build equates:
;------------------------------------------------------------------------------
FALSE EQU 0
TRUE EQU NOT FALSE
CPM EQU TRUE ;TRUE if output via CPM, FALSE if hardware direct
DEBUG EQU TRUE ;TRUE for error messages
VERBOSE EQU FALSE ;TRUE for extended error messages
CPM$TRANSLATE EQU TRUE ;Translate Trk, Sec, Head to CPM TRACK# & SEC#
;------------------------------------------------------------------------------
;Drive number equates:
;------------------------------------------------------------------------------
IDE0 EQU 0 ;Physical disk 0 drive number assignment
IDE1 EQU 1 ;Physical disk 1 drive number assignment
;------------------------------------------------------------------------------
;Console equates:
;------------------------------------------------------------------------------
CONI EQU 10H ;Console input port
CONO EQU 11H ;Console output port
;------------------------------------------------------------------------------
;Display control equates:
;------------------------------------------------------------------------------
SCROLL EQU 01H ;Set scroll direction UP
LF EQU 0AH
CR EQU 0DH
BS EQU 08H ;Back space (required for sector display)
PERIOD EQU 2EH
BELL EQU 07H
SPACE EQU 20H
QUIT EQU 11H ;Turns off any screen enhancements
NO$ENHANCEMENT EQU 17H ;Turns off whatever is on
FAST EQU 10H ;High speed scrool
TAB EQU 09H ;TAB ACROSS (8 SPACES FOR SD-BOARD)
ESC EQU 1BH
CLEAR EQU 1CH ;Clear line (Use 80 spaces if not available)
;------------------------------------------------------------------------------
;IDE Interface equates:
;------------------------------------------------------------------------------
;Ports for 8255 chip. Change these to specify where your 8255 is addressed,
;The first three control which 8255 ports have the control signals,
;upper and lower data bytes. The last one (IDEportCtrl), is for mode setting
;for the 8255 to configure its actual I/O ports (A,B & C).
;
;Note most drives these days don't use the old Head,Track, Sector terminology.
;Instead we use "Logical Block Addressing" or LBA. This is what we use below.
;LBA treats the drive as one continous set of sectors, 0,1,2,3,... 3124,...etc.
;However as seen below we need to convert this LBA to heads,tracks and sectors
;to be compatible with CPM & MSDOS.
;
;NOTE: If you have only one drive/CF card, be sure it is in drive #0.
;The IDE hardware gets confused if there is only a drive in slot #1.
;------------------------------------------------------------------------------
IDEportA EQU 030H ;Lower 8 bits of IDE interface (8255)
IDEportB EQU 031H ;Upper 8 bits of IDE interface
IDEportC EQU 032H ;Control lines for IDE interface
IDEportCtrl EQU 033H ;8255 configuration port
IDEDrive EQU 034H ;Bit 0 - 0 for drive 0 and 1 for drive 1
READcfg8255 EQU 10010010b ;Set 8255 IDEportC to output, IDEportA/B input
WRITEcfg8255 EQU 10000000b ;Set all three 8255 ports to output mode
;------------------------------------------------------------------------------
;IDE control lines for use with IDEportC.
;------------------------------------------------------------------------------
IDEa0line EQU 01H ;direct from 8255 to IDE interface
IDEa1line EQU 02H ;direct from 8255 to IDE interface
IDEa2line EQU 04H ;direct from 8255 to IDE interface
IDEcs0line EQU 08H ;inverter between 8255 and IDE interface
IDEcs1line EQU 10H ;inverter between 8255 and IDE interface
IDEwrline EQU 20H ;inverter between 8255 and IDE interface
IDErdline EQU 40H ;inverter between 8255 and IDE interface
IDErstline EQU 80H ;inverter between 8255 and IDE interface
;------------------------------------------------------------------------------
;Symbolic constants for the IDE drive registers
;------------------------------------------------------------------------------
REGdata EQU IDEcs0line
REGerr EQU IDEcs0line + IDEa0line
REGseccnt EQU IDEcs0line + IDEa1line
REGsector EQU IDEcs0line + IDEa1line + IDEa0line
REGcylinderLSB EQU IDEcs0line + IDEa2line
REGcylinderMSB EQU IDEcs0line + IDEa2line + IDEa0line
REGshd EQU IDEcs0line + IDEa2line + IDEa1line
REGcommand EQU IDEcs0line + IDEa2line + IDEa1line + IDEa0line
REGstatus EQU IDEcs0line + IDEa2line + IDEa1line + IDEa0line
REGcontrol EQU IDEcs1line + IDEa2line + IDEa1line
REGastatus EQU IDEcs1line + IDEa2line + IDEa1line + IDEa0line
;------------------------------------------------------------------------------
;IDE Command Constants. These should never change.
;------------------------------------------------------------------------------
COMMANDrecal EQU 10H
COMMANDread EQU 20H
COMMANDwrite EQU 30H
COMMANDinit EQU 91H
COMMANDid EQU 0ECH
COMMANDspindown EQU 0E0H
COMMANDspinup EQU 0E1H
;------------------------------------------------------------------------------
;IDE Status Register:
;------------------------------------------------------------------------------
; bit 7: Busy 1=busy, 0=not busy
; bit 6: Ready 1=ready for command, 0=not ready yet
; bit 5: DF 1=fault occurred
; bit 4: DSC 1=seek complete
; bit 3: DRQ 1=data request ready, 0=not ready to xfer yet
; bit 2: CORR 1=correctable error occurred
; bit 1: IDX vendor specific
; bit 0: ERR 1=error occured
;------------------------------------------------------------------------------
;------------------------------------------------------------------------------
;Disk equates:
;------------------------------------------------------------------------------
SEC$SIZE EQU 512 ;Bytes per sector
MAXSEC EQU 3DH ;Sectors per track
MAXTRK EQU 0FFH ;CPM3 allows up to 8MG so 0-256 "tracks"
BUFFER$ORG EQU 3000H ;<----- Will place all sector data here
CPM$BOOT$COUNT EQU 12 ;Allow up to 12 CPM sectors for CPMLDR
CPMLDR$ADDRESS EQU BUFFER$ORG
RDCON EQU 1 ;For CP/M I/O
WRCON EQU 2
RESET$DISK EQU 0DH ;Reset all CPM disks
PRINT EQU 9
CONST EQU 11 ;CONSOLE STAT
BDOS EQU 5
;------------------------------- INITIALIZATION -------------------------------
ORG 100H ;<--- For CPM
begin:
LXI SP, STACK
LXI D, SIGN$ON ;Print welcome message
CALL PSTRING
IF VERBOSE
LXI D, SEL0MSG ;Print select drive 0 message
CALL PSTRING
ENDIF
MVI A, IDE0
STA @CURRENT$DRIVE
OUT IDEDrive ;Select first drive
CALL CLEAR$ID$BUFFER ;Clear ID Buffer
IF VERBOSE
LXI D, INITDRIVE ;Print initialization message
CALL PSTRING
ENDIF
CALL IDEinit ;Initialize the board and first drive
JZ INIT$OK ;Continue on Zero
LXI D, INIT$0$ERROR ;Non-zero is error, probably no drive
CALL PSTRING
JMP ABORT
INIT$OK: ;Get drive 0 identification info
CALL driveid
JZ INIT$OK1
LXI D, ID$ERROR ;End program on error
CALL PSTRING
JMP ABORT
INIT$OK1: ;Check sector count
LXI H, IDbuffer + 12
MOV A, M ;(High Byte)
ORA A
JNZ INIT$OK2
INX H
MOV A, M ;(Low Byte)
ORA A
JNZ INIT$OK2 ;Looks like we have a valid IDE drive
LXI D, BAD$DRIVE ;Zero sectors means something's wrong
CALL PSTRING
JMP ABORT ;No drive #0 so abort
INIT$OK2: ;Print drive 0 info
LXI D, DRIVE0$INFO
CALL PSTRING
LXI D, msgmdl ;Drive name
CALL PSTRING
LXI H, IDbuffer + 54
MVI B, 20 ;Character count in words
CALL printSwap ;Print [HL], [B] X 2 characters
CALL ZCRLF
LXI D, msgsn ;Serial number
CALL PSTRING
LXI H, IDbuffer + 20
MVI B, 10 ;Character count in words
CALL printText
CALL ZCRLF
LXI D, msgrev ;Firmware revision string
CALL PSTRING
LXI H, IDbuffer + 46
MVI B, 4 ;Character count in words
CALL printSwap
CALL ZCRLF
LXI D, msgcy ;Drive specs (cyl/hd/sect)
CALL PSTRING
LXI H, IDbuffer + 2
CALL printparm
LXI D, msghd
CALL PSTRING
LXI H, IDbuffer + 6
CALL printparm
LXI D, msgsc
CALL PSTRING
LXI H, IDbuffer + 12
CALL printparm
CALL ZCRLF
INIT$OK3: ;Move to second drive
CALL ZCRLF
IF VERBOSE
LXI D, SEL1MSG ;Print select drive 1 message
CALL PSTRING
ENDIF
MVI A, IDE1
STA @CURRENT$DRIVE
OUT IDEDrive
CALL CLEAR$ID$BUFFER ;Clear ID Buffer
IF VERBOSE
LXI D, INITDRIVE ;Print initialization message
CALL PSTRING
ENDIF
CALL IDEinit ;Initialize the second drive
JZ INIT$OK4
LXI D, INIT$1$ERROR ;Non-zero is error, so print warning
CALL PSTRING
JMP MAINLOOP
INIT$OK4: ;Get drive 1 identification info
CALL driveid
JZ INIT$OK5
LXI D, ID$ERROR ;On error, display message
CALL PSTRING
JMP MAINLOOP ;Continue to main menu
INIT$OK5: ;Check sector count
LXI H, IDbuffer + 12
MOV A, M ;(High Byte)
ORA A
JNZ INIT$OK6
INX H
MOV A, M ;(Low Byte)
ORA A
JNZ INIT$OK6 ;Looks like we have a valid IDE drive
BAD$DR1:
LXI D, BAD$DRIVE ;Zero sectors, so display error
CALL PSTRING
JMP MAINLOOP ;Continue to main menu
INIT$OK6: ;Print drive 1 info
LXI D, DRIVE1$INFO
CALL PSTRING
LXI D, msgmdl ;Drive name
CALL PSTRING
LXI H,IDbuffer + 54
MVI B, 20 ;Character count in words
CALL printSwap ;Print [HL], [B] X 2 characters
CALL ZCRLF
LXI D, msgsn ;Serial number
CALL PSTRING
LXI H, IDbuffer + 20
MVI B, 10 ;Character count in words
CALL printText
CALL ZCRLF
LXI D, msgrev ;Firmware revision string
CALL PSTRING
LXI H, IDbuffer + 46
MVI B, 4 ;Character count in words
CALL printSwap
CALL ZCRLF
LXI D, msgcy ;Drive specs (cyl/hd/sect)
CALL PSTRING
LXI H, IDbuffer + 2
CALL printparm
LXI D, msghd
CALL PSTRING
LXI H, IDbuffer + 6
CALL printparm
LXI D, msgsc
CALL PSTRING
LXI H, IDbuffer + 12
CALL printparm
CALL ZCRLF
INIT$DONE: ;Cleanup and enter main menu
CALL CLEAR$ID$BUFFER
CALL IDEinit ;Re-initialize drive 1
MVI A, IDE0
STA @CURRENT$DRIVE ;Select drive 0
OUT IDEDrive
CALL IDEinit ;Re-initialize drive 0
LXI H, 0
SHLD @SEC ;Default to track 0 and sector 0
SHLD @TRK
LXI H, buffer ;Set DMA address to buffer
SHLD @DMA
JMP MAINLOOP ;Display Main Menu
;------------------------------------------------------------------------------
ABORT: ;Controlled termination
IF CPM
MVI C, RESET$DISK ;Reset all disks
JMP 0FF00H ;Reboot CPM
ELSE
JMP 0F800H ;Transfer control to Monitor ROM
ENDIF
;--------------------------------- MAIN LOOP ----------------------------------
MAINLOOP: ;Print main menu
LDA @CURRENT$DRIVE
ORA A
JNZ DRIVE$1$MENU
LXI D, DRIVE$0$MSG
CALL PSTRING
JMP Display0
DRIVE$1$MENU:
LXI D, DRIVE$1$MSG
CALL PSTRING
Display0:
LDA @DisplayFlag ;Sector data display flag on or off
ORA A ;NZ = on (Initially 0FFH so display on)
JNZ Display1
LXI D, CMD$STRING1 ;List command options (Turn display option on)
JP Display2
Display1:
LXI D, CMD$STRING2 ;List command options (Turn display option off)
Display2:
CALL PSTRING
CALL wrlba ;Update LBA on drive
CALL DISPLAYposition ;Display current Track,sector,head#
LXI D, Prompt ;'>'
CALL PSTRING
CALL GETCMD ;Simple UC character Input
CPI ESC ;Abort if ESC
JZ ABORT
CALL upper
CALL ZCRLF
SBI '@' ;Adjust to 0,1AH
ADD A ;X2
LXI H, TBL ;Get menu selection
ADD L
MOV L, A
MOV A, M
INX HL
MOV H, M
MOV L, A ;Jump to table pointer
PCHL ;JMP (HL)
;-------------------------------- MENU OPTIONS --------------------------------
READ$SEC: ;Read Sector @ LBA to the RAM buffer
LXI H,buffer ;Point to buffer
SHLD @DMA
CALL READSECTOR
JZ main1b ;Z means the sector read was OK
CALL ZCRLF
JMP MAINLOOP
main1b: LXI D, msgrd ;Sector read OK
CALL PSTRING
LDA @DisplayFlag ;Do we have display flag on or off
ORA A ;NZ = on
JZ MAINLOOP
LXI H, buffer ;Point to buffer. Show sector data flag is on
SHLD @DMA
CALL HEXDUMP ;Show sector data
JMP MAINLOOP
WRITE$SEC: ;Write data in RAM buffer to sector @ LBA
LXI D, msgsure ;Are you sure?
CALL PSTRING
CALL ZCI
CALL upper
CPI 'Y'
JNZ main2c
CALL ZCRLF
LXI H, buffer ;Point to buffer
SHLD @DMA
CALL WRITESECTOR
JZ main2b ;Z means the sector write was OK
CALL ZCRLF
JMP MAINLOOP
main2b: LXI D, msgwr ;Sector written OK
CALL PSTRING
main2c: JMP MAINLOOP
SET$LBA: ;Set the logical block address
LXI D, GET$LBA
CALL PSTRING
CALL ghex32lba ;Get CPM style Track & Sector, put in RAM
JC main3b ;Ret C set if abort/error
CALL wrlba ;Update LBA on drive
main3b: CALL ZCRLF
JMP MAINLOOP
NEXT$SECT:
LDA @SEC
INR A
CPI MAXSEC-1
JNC RANGE$ERROR
STA @SEC
CALL wrlba ;Update LBA on drive
CALL ZCRLF
JMP MAINLOOP
RANGE$ERROR:
LXI D, RANGE$MSG
CALL PSTRING
JMP MAINLOOP
PREV$SEC:
LDA @SEC
ORA A
JZ RANGE$ERROR
DCR A
STA @SEC
CALL wrlba ;Update LBA on drive
CALL ZCRLF
JMP MAINLOOP
POWER$UP: ;Set the drive to spin up
CALL spinup
JMP MAINLOOP
POWER$DOWN: ;Set the drive to spin down
CALL spindown
JMP MAINLOOP
DISPLAY: ;Do we have display flag on or off
LDA @DisplayFlag
CMA ;flip it
STA @DisplayFlag
JMP MAINLOOP ;Update display and back to next menu command
SEQ$RD: ;Do sequential reads
CALL SequentialReads
JMP MAINLOOP
DRIVE$0:
MVI A, IDE0 ;Select Drive 0:
STA @CURRENT$DRIVE
OUT IDEDrive
LXI D, SETA$MSG
CALL PSTRING
JMP MAINLOOP
DRIVE$1:
MVI A, IDE1 ;Select Drive 1:
STA @CURRENT$DRIVE
OUT IDEDrive
LXI D,SETB$MSG
CALL PSTRING
JMP MAINLOOP
RAMCLEAR: ;Fill RAM buffer with 0's
LXI H, buffer ;Point to buffer
LXI D, 512
MVI A, 0 ;Fill area with 0's
CLEAR1: MOV M, A
INX H
DCX D
MOV A, E
ANA D
JNZ CLEAR1
LXI D, FILL$MSG
CALL PSTRING
JMP MAINLOOP
CPMBOOT: ;Boot CPM from IDE system tracks -- if present
MVI A, 0 ;Load from track 0, sec 1, head 0 (always)
STA @SEC ;Remember sectors are numbered +1
XRA A
STA @TRK+1
STA @TRK
MVI A, CPM$BOOT$COUNT ;Count of CPMLDR sectors (12)
STA @SECTOR$COUNT
LXI H, CPMLDR$ADDRESS ;DMA address where the CPMLDR resides in RAM
SHLD @DMA
NextRCPM:
CALL wrlba ;Update LBA on drive
CALL DISPLAYposition ;Display current track, sector, head
CALL ZCRLF
LHLD @DMA
CALL READSECTOR ;Read a sector
SHLD @DMA
LDA @SECTOR$COUNT
DCR A
STA @SECTOR$COUNT
JZ LOAD$DONE
LHLD @SEC
INX H
SHLD @SEC ;Stay on track 0 in this special case
JMP NextRCPM
LOAD$DONE:
MVI E, REGstatus ;Check the R/W status when done
CALL IDErd8D
BIT 0, D
JNZ CPMLoadErr ;Zero if no errors
LXI H, CPMLDR$ADDRESS
MOV A, M
CPI 31H ;EXPECT TO HAVE 31H @80H IE. LD SP,80H
JNZ CPMLoadErr1 ;Zero if no errors
LXI D, MOVE$REQUEST ;Ask if we can move data to 100H
CALL PSTRING
CALL ZCI
CALL upper
CPI 'Y'
JNZ MAINLOOP
LXI H, CPM$MOVE$CODE ;Need to move code out of the way.
LXI D, 0H
LXI B, (CPM$MOVE$CODE$END-CPM$MOVE$CODE)
LDIR
JMP 0H ;Now jump here to move the CPMLDR (@3000H) to 100H
CPMLoadErr1:
LXI D, CPM$ERROR1 ;Drive data error
CALL PSTRING
JMP MAINLOOP
CPMLoadErr:
LXI D, CPM$ERROR ;Drive Read Error
CALL PSTRING
JMP MAINLOOP
N$RD$SEC: ;Read N sectors
LXI D, ReadN$MSG ;No check for possible high RAM (CPM) overwrite
CALL PSTRING
CALL GETHEX
JC MAINLOOP ;Abort if ESC (C flag set)
STA @SECTOR$COUNT ;Store sector count
LXI H, buffer ;Point to buffer
SHLD @DMA
NextRSec:
LXI D, ReadingN$MSG
CALL PSTRING
CALL wrlba ;Update LBA on drive
CALL DISPLAYposition ;Display current track, sector, head
LHLD @DMA
CALL READSECTOR
SHLD @DMA
LDA @SECTOR$COUNT
DCR A
STA @SECTOR$COUNT
JZ MAINLOOP
LHLD @SEC
INX H
SHLD @SEC
MOV A, L ;0 to 62 CPM Sectors
CPI MAXSEC-1
JNZ NextRSec
LXI H, 0 ;Back to CPM sector 0
SHLD @SEC
LHLD @TRK ;Bump to next track
INX H
SHLD @TRK
MOV A, L ;0-FFH tracks (only)
JNZ NextRSec
LXI D, AtEnd ;Tell us we are at end of disk
CALL PSTRING
JMP MAINLOOP
N$WR$SEC: ;Write N sectors
LXI D, msgsure ;Are you sure?
CALL PSTRING
CALL ZCI
CALL upper
CPI 'Y'
JNZ main2c
LXI D, WriteN$MSG
CALL PSTRING
CALL GETHEX
JC MAINLOOP ;Abort if ESC (C flag set)
STA @SECTOR$COUNT ;Store sector count
LXI H, buffer ;Point to buffer
SHLD @DMA
NextWSec:
LXI D, WritingN$MSG
CALL PSTRING
CALL wrlba ;Update LBA on drive
CALL DISPLAYposition ;Display current track, sector, head
LHLD @DMA ;Actully, Sector/track values are already updated
CALL WRITESECTOR ;in wrlba, but WRITESECTOR is used in multiple places.
SHLD @DMA ;A repeat does no harm -- speed is not an issue here
LDA @SECTOR$COUNT
DCR A
STA @SECTOR$COUNT
JZ MAINLOOP
LHLD @SEC
INX H
SHLD @SEC
MOV A, L ;0 to 62 CPM Sectors
CPI MAXSEC-1
JNZ NextWSec
LXI H, 0 ;Back to CPM sector 0
SHLD @SEC
LHLD @TRK ;Bump to next track
INX H
SHLD @TRK
MOV A, L ;0-FFH tracks (only)
ORA A
JNZ NextWSec
LXI D, AtEnd ;Tell us we are at end of disk
CALL PSTRING
JMP MAINLOOP
FORMAT: ;Format (Fill sectors with E5)
LXI D, FORMAT$MSG
CALL PSTRING
LXI D, msgsure ;Are you sure?
CALL PSTRING
CALL ZCI
CALL upper
CPI 'Y'
JNZ MAINLOOP
LXI H, buffer ;Fill buffer with 0E5's (512 of them)
MVI B, 0
Fill0: MVI A, 0E5H ;<-- Sector fill character (E5 for CPM)
MOV M, A
INX H
MOV M, A
INX H
DCR B
JNZ Fill0
CALL ZCRLF
NEXT$FORMAT:
LXI H, buffer
SHLD @DMA
CALL WRITESECTOR ;Will return error if there was one
JZ main9b ;Z means the sector write was OK
CALL ZCRLF
JMP MAINLOOP
main9b: CALL ZEOL ;Clear line cursor is on
CALL DISPLAYposition ;Display actual current Track,sector,head#
CALL ZCSTS ;Any keyboard character will stop display
CPI 01H ;CPM Says something there
JNZ WRNEXTSEC1
CALL ZCI ;Flush character
LXI D, CONTINUE$MSG
CALL PSTRING
CALL ZCI
CPI ESC
JZ MAINLOOP
CALL ZCRLF
WRNEXTSEC1:
LHLD @SEC
INX H
SHLD @SEC ;0 to MAXSEC CPM Sectors
MOV A, L
CPI MAXSEC
JNZ NEXT$FORMAT
LXI H, 0 ;Back to CPM sector 0
SHLD @SEC
LHLD @TRK ;Bump to next track
INX H
SHLD @TRK
MOV A, L ;0-FFH tracks (only)
CPI MAXTRK
JNZ NEXT$FORMAT
LXI D, FormatDone ;Tell us we are all done.
CALL PSTRING
JMP MAINLOOP
BACKUP: ;Backup the CPM partition to another area
LXI D, CopyMsg
CALL PSTRING
CALL ZCI
CALL upper
CPI 'Y'
JNZ MAINLOOP
LXI H, 0 ;Start with CPM sector 0
SHLD @SEC
SHLD @SEC1
SHLD @SEC2 ;and on second partition
SHLD @TRK ;and track 0
SHLD @TRK1
LXI H, MAXTRK+0200H+1
SHLD @TRK2
CALL ZCRLF
CALL ZCRLF
NextCopy1:
CALL ZEOL ;Clear line cursor is on
LXI D, RBackup$MSG ;for each track update display
CALL PSTRING
LDA @TRK1+1 ;High TRK byte
CALL phex
LDA @TRK1 ;Low TRK byte
CALL phex
LXI D, WBackup$MSG
CALL PSTRING
LDA @TRK2+1 ;High TRK byte
CALL phex
LDA @TRK2 ;Low TRK byte
CALL phex
LXI D, H$Msg
CALL PSTRING
NextCopy:
LDA @SEC1
STA @SEC
LHLD @TRK1
SHLD @TRK
CALL wrlba ;Update LBA on "1st" drive
LXI H, buffer ;Point to buffer
SHLD @DMA
CALL READSECTOR ;Get sector data to buffer
LDA @SEC2
STA @SEC
LHLD @TRK2
SHLD @TRK
CALL wrlba ;Update LBA on "2nd" drive
LXI H,buffer ;Point to buffer
SHLD @DMA
CALL WRITESECTOR ;Write buffer data to sector
CALL ZCSTS ;Any keyboard character will stop display
CPI 01H ;CPM Says something there
JNZ BKNEXTSEC1
CALL ZCI ;Flush character
LXI D, CONTINUE$MSG
CALL PSTRING
CALL ZCI
CPI ESC
JZ MAINLOOP
BKNEXTSEC1:
LHLD @SEC
INX H
SHLD @SEC1
SHLD @SEC2
MOV A, L ;0 to 62 CPM Sectors
CPI MAXSEC-1
JNZ NextCopy
LXI H, 0 ;Back to CPM sector 0
SHLD @SEC1
SHLD @SEC2
LHLD @TRK1 ;Bump to next track
INX H
SHLD @TRK1
LHLD @TRK2 ;Bump to next track
INX H
SHLD @TRK2
LHLD @TRK1 ;Check if we are done
MOV A, L ;0-FFH tracks (only)
CPI MAXTRK
JNZ NextCopy1
LXI D, BackupDone ;Tell us we are all done.
CALL PSTRING
JMP MAINLOOP
RESTORE: ;Restore disk from backup partition
LXI D, RestoreMsg
CALL PSTRING
CALL ZCI
CALL upper
CPI 'Y'
JNZ MAINLOOP
LXI H, 0 ;Start with CPM sector 0
SHLD @SEC
SHLD @SEC1
SHLD @SEC2 ;and on second partition
SHLD @TRK ;and track 0
SHLD @TRK1
LXI H, MAXTRK+0200H+1
SHLD @TRK2
CALL ZCRLF
CALL ZCRLF
NextRestore1:
CALL ZEOL ;Clear line cursor is on
LXI D, RBackup$MSG ;for each track update display
CALL PSTRING
LDA @TRK2+1 ;High TRK byte
CALL phex
LDA @TRK2 ;Low TRK byte
CALL phex
LXI D, WBackup$MSG
CALL PSTRING
LDA @TRK1+1 ;High TRK byte
CALL phex
LDA @TRK1 ;Low TRK byte
CALL phex
LXI D,H$Msg
CALL PSTRING
NextRestore:
LDA @SEC2 ;Point to backup partition
STA @SEC
LHLD @TRK2
SHLD @TRK
CALL wrlba ;Update LBA on "1st" drive
LXI H, buffer ;Point to buffer
SHLD @DMA
CALL READSECTOR ;Get sector data to buffer
LDA @SEC1
STA @SEC
LHLD @TRK1
SHLD @TRK
CALL wrlba ;Update LBA on "2nd" drive
LXI H,buffer ;Point to buffer
SHLD @DMA
CALL WRITESECTOR ;Write buffer data to sector
CALL ZCSTS ;Any keyboard character will stop display
CPI 01H ;CPM Says something there
JNZ RESNEXTSEC1
CALL ZCI ;Flush character
LXI D, CONTINUE$MSG
CALL PSTRING
CALL ZCI
CPI ESC
JZ MAINLOOP
RESNEXTSEC1:
LHLD @SEC
INX H
SHLD @SEC1
SHLD @SEC2
MOV A, L ;0 to 62 CPM Sectors
CPI MAXSEC-1
JNZ NextRestore
LXI H, 0 ;Back to CPM sector 0
SHLD @SEC1
SHLD @SEC2
LHLD @TRK1 ;Bump to next track
INX H
SHLD @TRK1
LHLD @TRK2 ;Bump to next track
INX H
SHLD @TRK2
LHLD @TRK2 ;Check if we are done
MOV A, L ;0-FFH tracks (only)
CPI MAXTRK
JNZ NextRestore1
LXI D, RestoreDone ;Inform user restore complete
CALL PSTRING
JMP MAINLOOP
ERROR: LXI D, msgErr ;CMD error msg
CALL PSTRING
JMP MAINLOOP
COPY$AB: ;Copy Drive 0: to Drive 1:
LXI D, DiskCopyMsg
CALL PSTRING
CALL ZCI
CALL upper
CPI 'Y'
JNZ MAINLOOP
LXI H, 0 ;Start with CPM sector 0
SHLD @SEC
SHLD @TRK ;and track 0
CALL ZCRLF
CALL ZCRLF
NextDCopy1:
CALL ZEOL ;Clear line cursor is on
LXI D, CopyTrk$MSG ;for each track update display
CALL PSTRING
LDA @TRK+1 ;High TRK byte
CALL phex
LDA @TRK ;Low TRK byte
CALL phex
LXI D, H$Msg
CALL PSTRING
NextDCopy:
MVI A, IDE0 ;Login drive 0:
STA @CURRENT$DRIVE
OUT IDEDrive
CALL wrlba ;Update LBA on "0:" drive
LXI H, buffer ;Point to buffer
SHLD @DMA
CALL READSECTOR ;Get sector data from 0: drive to buffer
MVI A, IDE1 ;Login drive 1:
STA @CURRENT$DRIVE
OUT IDEDrive
CALL wrlba ;Update LBA on "1:" drive
LXI H, buffer ;Point to buffer
SHLD @DMA
CALL WRITESECTOR ;Write buffer data to sector on 1: drive
CALL ZCSTS ;Any keyboard character will stop display
CPI 01H ;CPM says something is there
JNZ BK$D$NEXTSEC1
CALL ZCI ;Flush character
LXI D, CONTINUE$MSG
CALL PSTRING
CALL ZCI
CPI ESC
JNZ BK$D$NEXTSEC1
MVI A,IDE0 ;Login drive 1:
STA @CURRENT$DRIVE
OUT IDEDrive
JMP MAINLOOP
BK$D$NEXTSEC1:
LHLD @SEC
INX H
SHLD @SEC
MOV A, L ;0 to 62 CPM Sectors
CPI MAXSEC-1
JNZ NextDCopy
LXI H, 0 ;Back to CPM sector 0
SHLD @SEC
LHLD @TRK ;Bump to next track
INX H
SHLD @TRK
;Check if we are done
MOV A, L ;0-FFH tracks (only)
CPI MAXTRK
JNZ NextDCopy1
LXI D, CopyDone ;Inform user copy complete
CALL PSTRING
MVI A, IDE0 ;Login drive 0:
STA @CURRENT$DRIVE
OUT IDEDrive
JMP MAINLOOP
VERIFY$AB: ;Verify Drive 0: = 1:
LXI D, DiskVerifyMsg
CALL PSTRING
LXI H, 0 ;Start with CPM sector 0
SHLD @SEC
SHLD @TRK ;and track 0
CALL ZCRLF
CALL ZCRLF
NextVCopy1:
CALL ZEOL ;Clear line cursor is on
LXI D, VerifyTrk$MSG ;for each track update display
CALL PSTRING
LDA @TRK+1 ;High TRK byte
CALL phex
LDA @TRK ;Low TRK byte
CALL phex
LXI D, H$Msg
CALL PSTRING
NextVCopy:
MVI A, IDE0 ;Login drive 0:
STA @CURRENT$DRIVE
OUT IDEDrive
CALL wrlba ;Update LBA on "0:" drive
LXI H, buffer ;Point to buffer
SHLD @DMA
CALL READSECTOR ;Get sector data from buffer 0: drive
MVI A, IDE1 ;Login drive 1:
STA @CURRENT$DRIVE
OUT IDEDrive
CALL wrlba ;Update LBA on "1:" drive
LXI H, buffer2 ;Point to buffer2
SHLD @DMA
CALL READSECTOR ;Read buffer data from sector of 1 drive
LXI BC, 512 ;Now check both buffers are identical
LXI H, buffer
LXI D, buffer2
NEXTV: LDAX D
CMP M ;Is [DE] = [HL]
JNZ COMPARE$ERROR
INX H
INX D
DCX B
MOV A,C
ANA B
JZ VERIFY$OK
JMP NEXTV
COMPARE$ERROR:
LXI D, VERIFY$ERR ;Indicate an error
CALL PSTRING
LDA @TRK+1 ;High TRK byte
CALL phex
LDA @TRK ;Low TRK byte
CALL phex
LXI D, SEC$Msg
CALL PSTRING
LDA @SEC ;Sector byte
CALL phex
LXI D, H$Msg
CALL PSTRING
JMP VER$OK1
VERIFY$OK:
CALL ZCSTS ;Any keyboard character will stop display
CPI 01H ;CPM says something is there
JNZ BK$V$NEXTSEC1
CALL ZCI ;Flush character
VER$OK1:
LXI D, CONTINUE$MSG
CALL PSTRING
CALL ZCI
CPI ESC
JNZ BK$V$NEXTSEC1
MVI A,IDE0 ;Login drive 0:
STA @CURRENT$DRIVE
OUT IDEDrive
JMP MAINLOOP
BK$V$NEXTSEC1:
LHLD @SEC
INX H
SHLD @SEC
MOV A, L ;0 to 62 CPM Sectors
CPI MAXSEC-1
JNZ NextVCopy
LXI H, 0 ;Back to CPM sector 0
SHLD @SEC
LHLD @TRK ;Bump to next track
INX H
SHLD @TRK
;Check if we are done
MOV A, L ;0-FFH tracks (only)
CPI MAXTRK
JNZ NextVCopy1
LXI D, VerifyDone ;Tell us we are all done.
CALL PSTRING
MVI A, IDE0 ;Login drive 0:
STA @CURRENT$DRIVE
OUT IDEDrive
JMP MAINLOOP
;----------------------------- SUPPORT FUNCTIONS ------------------------------
driveid:CALL IDEwaitnotbusy ;Retrieve drive info
RC
MVI D, COMMANDid
MVI E, REGcommand
CALL IDEwr8D ;Issue the ID command
IF VERBOSE
LXI D, READING$ID
CALL PSTRING
LXI D, DISKSTATUS ;Print status message
CALL PSTRING
ENDIF
MVI E, REGstatus ;Get status after ID command
CALL IDErd8D ;Check Status (info in [D])
IF VERBOSE
MOV A, D
CALL PHEX ;Print status
CALL ZPERCRLF
ENDIF
CALL IDEwaitdrq ;Wait for Busy=0, DRQ=1
JC SHOWerrors
IF VERBOSE
LXI D, GETTING$ID
CALL PSTRING
ENDIF
MVI B, 0 ;256 words
LXI H, IDbuffer ;Store data here
CALL MoreRD16 ;Get 256 words of data from REGdata port to [HL]
RET
spinup: ;Start the drive
MVI D, COMMANDspinup
spup2: MVI E, REGcommand
CALL IDEwr8D
CALL IDEwaitnotbusy
JC SHOWerrors
ORA A ;Clear carry
RET
spindown: ;Tell the drive to spin down
CALL IDEwaitnotbusy
JC SHOWerrors
MVI D,COMMANDspindown
JMP spup2
SequentialReads: ;Sequentially read sectors from current position
CALL IDEwaitnotbusy
JC SHOWerrors
CALL ZCRLF
NEXTSEC:
LXI H, buffer ;Point to buffer
SHLD @DMA
CALL READSECTOR ;Errors will show in READSECTOR
JZ SEQOK
LXI D, CONTINUE$MSG
CALL PSTRING
CALL ZCI
CPI ESC ;Abort if ESC
RZ
SEQOK: CALL ZEOL ;Clear line cursor is on
CALL DISPLAYposition ;Display current track, sector, head
LXI H, buffer ;Point to buffer
SHLD @DMA
LDA @DisplayFlag ;Do we have display flag on or off
ORA A ;NZ = on
CNZ HEXDUMP
CALL ZCRLF
CALL ZCRLF
CALL ZCRLF
CALL ZCSTS ;Any keyboard character will stop display
CPI 01H ;CPM Says something there
JNZ NEXTSEC1
CALL ZCI ;Flush character
LXI D, CONTINUE$MSG
CALL PSTRING
CALL ZCI
CPI ESC
RZ
CALL ZCRLF
NEXTSEC1:
LHLD @SEC
INX H
SHLD @SEC
MOV A, L ;0 to 62 CPM Sectors
CPI MAXSEC-1
JNZ NEXTSEC
LXI H, 0 ;Back to CPM sector 0
SHLD @SEC
LHLD @TRK ;Bump to next track
INX H
SHLD @TRK
JMP NEXTSEC ;Note will go to last sec on disk unless stopped
DISPLAYposition: ;Display current track, sector & head position
LXI D, msgCPMTRK ;Display in LBA format
CALL PSTRING ;---- CPM FORMAT ----
LDA @TRK+1 ;High TRK byte
CALL phex
LDA @TRK ;Low TRK byte
CALL phex
LXI D, msgCPMSEC
CALL PSTRING ;SEC = (16 bits)
LDA @SEC+1 ;High Sec
CALL phex
LDA @SEC ;Low sec
CALL phex
;---- LBA FORMAT ----
LXI D, msgLBA
CALL PSTRING ;LBA = 00 ("Heads" = 0 for these drives)
LDA @DRIVE$TRK+1 ;High "cylinder" byte
CALL phex
LDA @DRIVE$TRK ;Low "cylinder" byte
CALL phex
LDA @DRIVE$SEC
CALL phex
LXI D, MSGBracket
CALL PSTRING
RET
printText: ;Print text up to [B] (16-bit word) byte-pairs
MOV C, M ;Text is contiguous byte array
CALL ZCO
INX H
MOV C, M
CALL ZCO
INX H
DCR B
JNZ printText
RET
printSwap: ;Print text up to [B] (16-bit word) byte-pairs
INX H ;Swap byte pairs - low byte, high byte
MOV C, M
CALL ZCO
DCX H
MOV C, M
CALL ZCO
INX H
INX H
DCR B
JNZ printSwap
RET
ZCRLF:
PUSH PSW
MVI C, CR
CALL ZCO
MVI C, LF
CALL ZCO
POP PSW
RET
ZPERCRLF:
PUSH PSW
MVI C, PERIOD
CALL ZCO
MVI C, CR
CALL ZCO
MVI C, LF
CALL ZCO
POP PSW
RET
ZEOL: ;CR and clear current line
MVI C, CR
CALL ZCO
MVI C, CLEAR ;Use 80 spaces if necessary
CALL ZCO
RET
ZCSTS:
IF CPM
PUSH B
PUSH D
PUSH H
MVI C, CONST
CALL BDOS ;Returns with 1 in [A] if character at keyboard
POP H
POP D
POP B
CPI 1
RET
ELSE
IN COMI ;Get Character in [A]
ANI 02H
RZ
MVI A, 01H
ORA A
RET
ENDIF
ZCO: ;Write character that is in [C]
IF CPM
PUSH PSW
PUSH B
PUSH D
PUSH H
MOV E, C
MVI C, WRCON
CALL BDOS
POP H
POP D
POP B
POP PSW
RET
ELSE
PUSH PSW
ZCO1: IN CONI ;Show Character
ANI 04H
JZ ZCO1
MOV A, C
OUT CONO
POP PSW
RET
ENDIF
ZCI: ;Return keyboard character in [A]
IF CPM
PUSH B
PUSH D
PUSH H
MVI C, RDCON
CALL BDOS
POP H
POP D
POP B
RET
ELSE
ZCI1: IN CONI ;Get Character in [A]
ANI 02H
JZ ZCI1
IN CONO
RET
ENDIF
;------------------------------------------------------------------------------
;Print a string in [DE] up to '$'
;------------------------------------------------------------------------------
PSTRING:
IF CPM
MVI C, PRINT
JMP BDOS ;PRINT MESSAGE,
ELSE
PUSH B
PUSH D
PUSH H
XCHG
PSTRX: MOV A, M
CPI '$'
JZ DONEP
MOV C, A
CALL ZCO
INX H
JMP PSTRX
DONEP: POP H
POP D
POP B
RET
ENDIF
SHOWerrors:
IF NOT DEBUG
ORA A ;Set NZ flag
STC ;Set Carry Flag
RET
ELSE
CALL ZCRLF
MVI E, REGstatus ;Get status in status register
CALL IDErd8D
MOV A, D
ANI 1H
JNZ MoreError ;Go to REGerr register for more info
;All OK if 01000000
PUSH PSW ;save for return below
ANI 80H
JZ NOT7
LXI D, DRIVE$BUSY ;Drive Busy (bit 7) stuck high
CALL PSTRING
JMP DONEERR
NOT7: ANI 40H
JNZ NOT6
LXI D, DRIVE$NOT$READY ;Drive Not Ready (bit 6) stuck low
CALL PSTRING
JMP DONEERR
NOT6: ANI 20H
JNZ NOT5
LXI D, DRIVE$WR$FAULT ;Drive write fault
CALL PSTRING
JMP DONEERR
NOT5 LXI D, UNKNOWN$ERROR
CALL PSTRING
JMP DONEERR
MoreError: ;Bit 0 of the status register indicates problem
MVI E, REGerr ;Get error code in REGerr
CALL IDErd8D
MOV A, D
PUSH PSW
ANI 10H
JZ NOTE4
LXI D, SEC$NOT$FOUND
CALL PSTRING
JMP DONEERR
NOTE4: ANI 80H
JZ NOTE7
LXI D, BAD$BLOCK
CALL PSTRING
JMP DONEERR
NOTE7: ANI 40H
JZ NOTE6
LXI D, UNRECOVER$ERR
CALL PSTRING
JMP DONEERR
NOTE6: ANI 4H
JZ NOTE2
LXI D, INVALID$CMD
CALL PSTRING
JMP DONEERR
NOTE2: ANI 2H
JZ NOTE1
LXI D, TRK0$ERR
CALL PSTRING
JMP DONEERR
NOTE1: LXI D, UNKNOWN$ERROR1
CALL PSTRING
JMP DONEERR
DONEERR:POP PSW
PUSH PSW
CALL ZBITS
CALL ZCRLF
POP PSW
ORA A ;Set Z flag
STC ;Set Carry flag
RET
ENDIF
;------------------------------------------------------------------------------
;Print a 16-bit number in RAM located @ [HL], low-byte first for Drive ID
;------------------------------------------------------------------------------
printparm:
INX H ;Index to high byte first
MOV A, M
CALL PHEX
DCX H ;Now low byte
MOV A, M
CALL PHEX
RET
;------------------------------------------------------------------------------
;Print an 8 bit number located in [A]
;------------------------------------------------------------------------------
PHEX: PUSH PSW
PUSH B
PUSH PSW
RRC
RRC
RRC
RRC
CALL ZCONV
POP PSW
CALL ZCONV
POP B
POP PSW
RET
ZCONV: ANI 0FH ;HEX to ASCII and print it
ADI 90H
DAA
ACI 40H
DAA
MOV C, A
CALL ZCO
RET
;------------------------------------------------------------------------------
;Display binary in [A]
;------------------------------------------------------------------------------
ZBITS: PUSH PSW
PUSH B
PUSH D
MOV E, A
MVI B, 8
BQ2: DB 0CBH, 23H
SLAR E
MVI A, 18H
ADC A
MOV C, A
CALL ZCO
DCR B
JNZ BQ2
POP D
POP B
POP PSW
RET
ghex32lba: ;Convert CPM Track & Sector to LBA format
LXI D,ENTER$SECL ;Enter sector number
CALL PSTRING
CALL GETHEX ;Get 2 HEX digits
RC
STA @SEC
CALL ZCRLF
LXI D, ENTER$TRKH ;Enter high byte track number
CALL PSTRING
CALL GETHEX ;Get 2 HEX digits
RC
STA @TRK+1
CALL ZCRLF
LXI D, ENTER$TRKL ;Enter low byte track number
CALL PSTRING
CALL GETHEX ;Get 2 more HEX digits
RC
STA @TRK
CALL ZCRLF
XRA A
ORA A ;To return NC
RET
;------------------------------------------------------------------------------
;Get a HEX character from the keyboard and echo it
;------------------------------------------------------------------------------
GETHEX:
CALL GETCMD ;Get character
CPI ESC
JZ HEXABORT
CPI '/' ;check 0-9, A-F
JC HEXABORT
CPI 'F'+1
JNC HEXABORT
CALL ASBIN ;Convert to binary
RLC ;Shift to high nibble
RLC
RLC
RLC
MOV B, A ;Store it
CALL GETCMD ;Get 2nd character from keyboard & ECHO
CPI ESC
JZ HEXABORT
CPI '/' ;check 0-9, A-F
JC HEXABORT
CPI 'F'+1
JNC HEXABORT
CALL ASBIN ;Convert to binary
ORA B ;add in the first digit
ORA A ;To return NC
RET
HEXABORT:
STC ;Set Carry flag
RET
;------------------------------------------------------------------------------
;Get a character from the keyboard, convert to uppercase and echo it
;------------------------------------------------------------------------------
GETCMD: CALL ZCI ;Get character
CALL UPPER
CPI ESC
RZ ;Don't echo an ESC
IF NOT CPM
PUSH PSW ;Save it
PUSH B
MOV C, A
CALL ZCO ;Echo it
POP B
POP PSW ;get it back
ENDIF
RET
;------------------------------------------------------------------------------
;Convert lowercase to uppercase
;------------------------------------------------------------------------------
UPPER: CPI 'a' ;must be >= lowercase a
RC ;else go back...
CPI 'z'+1 ;must be <= lowercase z
RNC ;else go back...
SUI 'a'-'A' ;subtract lowercase bias
RET
ASBIN: SUI 30H ;ASCII to binary conversion
CPI 0AH
RM
SUI 07H
RET
;------------------------------------------------------------------------------
;Print a hexdump of the data in the 512 byte buffer @[HL]
;------------------------------------------------------------------------------
HEXDUMP:
PUSH PSW ;Save everything
PUSH B
PUSH D
PUSH H
CALL ZCRLF ;CR/LF first
MVI D, 32 ;Print 32 lines total
MVI B, 16 ;16 characters across
SHLD @StartLineHex ;Save buffer location for ASCII display below
LXI H, 0
SHLD @BYTE$COUNT
SF172: CALL ZCRLF
LHLD @BYTE$COUNT
MOV A, H
CALL PHEX ;Print byte count in sector
MOV A, L
CALL PHEX
PUSH D
LXI D, 16
DAD D
POP D
SHLD @BYTE$COUNT ;Store for next time
CALL BLANK
LHLD @StartLineHex
SHLD @StartLineASCII ;Store for ASCII display below
SF175: MOV A, M
CALL LBYTE ;Display [A] on CRT/LCD
INX H
DCR B
JNZ SF175
SHLD @StartLineHex ;Save for next line later
CALL ShowAscii ;Now translate to ASCII and display
MVI B, 16 ;16 characters across for next line
DCR D
JNZ SF172 ;Have we done all 32 lines
CALL ZCRLF
POP H ;Get back original registers
POP D
POP B
POP PSW
RET
ShowAscii: ;Show as ASCII info
LHLD @StartLineASCII
MVI B, 16 ;16 ASCII characters across
XF172: CALL BLANK ;Send a space character
CALL BLANK
XF175: MOV A, M
ANI 7FH
CPI ' ' ;Filter out control characters
JNC XT33
XT22: MVI A, '.'
XT33: CPI 07CH
JNC XT22
MOV C, A ;Setup to send
PUSH B
CALL ZCO
POP B
INX H ;Next position in buffer
DCR B
JNZ XF175
RET
BLANK: PUSH B
PUSH H
MVI C, ' '
CALL ZCO
POP H
POP B
RET
LBYTE: PUSH PSW
RRC
RRC
RRC
RRC
CALL SF598
POP PSW
SF598: CALL ZCONV
RET
;------------------------------------------------------------------------------
;IDE Drive BIOS Routines written in a format that can be used directly with CPM
;------------------------------------------------------------------------------
IDEinit: ;Initialize the 8255 and drive then do a hard reset
IF VERBOSE
LXI D, INITDRIVE
CALL PSTRING
ENDIF
MVI A, READcfg8255 ;Config 8255 chip (10010010B)
OUT IDEportCtrl ;for READ mode
MVI A, IDErstline ;Hard reset the disk drive
OUT IDEportC ;Some CF cards are sensitive to reset pulse width
MVI B, 20H ;Symptom is incorrect data back from a sector read
ResetDelay:
DCR B
JNZ ResetDelay ;Delay (reset pulse width)
XRA A
OUT IDEportC ;No control lines asserted (just bit 7 of port C)
CALL DELAY$SHORT ;Short Delay
MVI D,11100000b ;Data for IDE SDH reg (512byte, LBA, single drive, hd 0)
;For Trk, Sec, Head (non LBA) use 10100000
MVI E,REGshd ;00001110,(0EH) for CS0,A2,A1,
CALL IDEwr8D ;Write byte to select the MASTER device
MVI B, 02H ;Delay time for hard disks to get up to speed (2s)
WaitInit:
IF VERBOSE
LXI D, DISKSTATUS ;Print initialization status message
CALL PSTRING
ENDIF
MVI E, REGstatus ;Get status after initilization
CALL IDErd8D ;Check Status (info in [D])
MOV A, D
IF VERBOSE
CALL PHEX ;Print drive initialization status
CALL ZPERCRLF
ENDIF
ANI 80H
RZ ;Return. We'll check for errors when we get back
MVI A, 2
CALL DELAY$LONG ;Long delay, drive has to get up to speed
DCR B
JNZ WaitInit
XRA A
DCR A
RET ;Return NZ. We'll check for errors when we get back
DELAY$LONG: ;Long delay (Seconds)
STA @DELAYStore
PUSH B
LXI B, 0FFFFH
DELAY2: LDA @DELAYStore
DELAY1: DCR A
JNZ DELAY1
DCX B
MOV A, C
ORA B
JNZ DELAY2
POP B
RET
DELAY$SHORT: ;Short delay (32ms)
MVI A, 40
DELAY3: MVI B, 0
M0: DCR B
JNZ M0
DCR A
JNZ DELAY3
RET
;------------------------------------------------------------------------------
;Sector Read
;------------------------------------------------------------------------------
READSECTOR: ;Read a sector, specified by the 3 bytes in LBA
;Z on success, NZ call error routine if problem
CALL wrlba ;Tell which sector we want to read from.
;Translate first in case of an error, otherewise
;we will get stuck on bad sector
CALL IDEwaitnotbusy ;Make sure drive is ready
JC SHOWerrors ;Returned with NZ set if error
MVI D, COMMANDread
MVI E, REGcommand
CALL IDEwr8D ;Send sec read command to drive.
CALL IDEwaitdrq ;Wait until it's got the data
JC SHOWerrors
LHLD @DMA ;DMA address
MVI B, 0 ;Read 512 bytes to [HL]
MoreRD16:
MVI A, REGdata ;REG register address
OUT IDEportC
ORI IDErdline ;08H+40H, Pulse RD line
OUT IDEportC
IN IDEportA ;Read the lower byte first
MOV M, A
INX H
IN IDEportB ;Then read the upper byte
MOV M, A
INX H
MVI A, REGdata ;Deassert RD line
OUT IDEportC
DCR B
JNZ MoreRD16
MVI E, REGstatus
CALL IDErd8D
MOV A, D
ANI 1H
CNZ SHOWerrors ;If error display status
RET
;------------------------------------------------------------------------------
;Sector Write
;------------------------------------------------------------------------------
WRITESECTOR: ;Write a sector, specified by the 3 bytes in LBA
;Z on success, NZ to error routine if problem
CALL wrlba ;Tell which sector we want to read from.
;Translate first in case of an error, otherewise
;we will get stuck on bad sector
CALL IDEwaitnotbusy ;Make sure drive is ready
JC SHOWerrors
MVI D, COMMANDwrite
MVI E, REGcommand
CALL IDEwr8D ;Tell drive to write a sector
CALL IDEwaitdrq ;Wait unit it wants the data
JC SHOWerrors
LHLD @DMA
MVI B, 0
MVI A, WRITEcfg8255
OUT IDEportCtrl
WRSEC1: MOV A, M
INX H
OUT IDEportA ;Write the lower byte first
MOV A, M
INX H
OUT IDEportB ;Then high byte on B
MVI A, REGdata
PUSH PSW
OUT IDEportC ;Send write command
ORI IDEwrline ;Send WR pulse
OUT IDEportC
POP PSW
OUT IDEportC
DCR B
JNZ WRSEC1
MVI A, READcfg8255 ;Set 8255 back to read mode
OUT IDEportCtrl
MVI E, REGstatus
CALL IDErd8D
MOV A, D
ANI 1H
CNZ SHOWerrors ;If error display status
RET
;------------------------------------------------------------------------------
;Write Logical Block Address (LBA) mode
;------------------------------------------------------------------------------
wrlba:
LDA @SEC ;LBA mode low sectors go directly
INR A ;Sectors are numbered 1 -- MAXSEC
STA @DRIVE$SEC ;For Diagnostic Display Only
MOV D, A
MVI E, REGsector ;Send info to drive
CALL IDEwr8D
LHLD @TRK
MOV A, L
STA @DRIVE$TRK
MOV D, L ;Send Low TRK#
MVI E, REGcylinderLSB
CALL IDEwr8D
MOV A, H
STA @DRIVE$TRK+1
MOV D, H ;Send High TRK#
MVI E, REGcylinderMSB
CALL IDEwr8D
MVI D, 1 ;For now, one sector at a time
MVI E, REGseccnt
CALL IDEwr8D
RET
;------------------------------------------------------------------------------
;Wait for drive to come ready
;------------------------------------------------------------------------------
IDEwaitnotbusy: ;Drive READY if status = 01000000
MVI B, 0FFH
MVI A, 0FFH ;Delay must be above 80H, longer for slow drives
STA @DELAYStore
MoreWait:
MVI E, REGstatus ;Wait for RDY bit to be set
CALL IDErd8D
MOV A, D
ANI 11000000B
XRI 01000000B
JZ DoneNotbusy
DCR B
JNZ MoreWait
LDA @DELAYStore ;Check timeout delay
DCR A
STA @DELAYStore
JNZ MoreWait
STC ;Set carry to indicate an error
ret
DoneNotBusy:
ORA A ;Clear carry it indicate no error
RET
;------------------------------------------------------------------------------
;Wait for drive to assert data request (DRQ) line is ready
;------------------------------------------------------------------------------
IDEwaitdrq:
MVI B, 0FFH
MVI A, 0FFH ;Delay must be above 80H, longer for slow drives
STA @DELAYStore
MoreDRQ:
MVI E, REGstatus ;Wait for DRQ bit to be set
CALL IDErd8D
MOV A, D
ANI 10001000B
CPI 00001000B
JZ DoneDRQ
DCR B
JNZ MoreDRQ
LDA @DELAYStore ;Check timeout delay
DCR A
STA @DELAYStore
JNZ MoreDRQ
STC ;Set carry to indicate error
RET
DoneDRQ:
ORA A ;Clear carry
RET ;Return drive status in A
;------------------------------------------------------------------------------
;Clear the ID buffer
;------------------------------------------------------------------------------
CLEAR$ID$BUFFER:
LXI H, IDBuffer
LXI B, 512
CLEAR2: MVI A, ' '
MOV M, A
INX H
DCX B
MOV A, C
ORA B
JNZ CLEAR2
LXI H, IDBuffer ;Zero for cylinder, heads, sectors
LXI B, 14
CLEAR3: MVI A, 0
MOV M, A
INX H
DCX B
MOV A, C
ORA B
JNZ CLEAR3
RET
;------------------------------------------------------------------------------
; Low Level 8 bit R/W to the drive controller. These are the routines that talk
; directly to the drive controller registers, via the 8255 chip.
; Note the 16 bit I/O to the drive (which is only for SEC R/W) is done directly
; in the routines READSECTOR & WRITESECTOR for speed reasons.
;------------------------------------------------------------------------------
;------------------------------------------------------------------------------
;Read One Byte
;------------------------------------------------------------------------------
IDErd8D: ;Read 8 bits from IDE register in [E],
MOV A, E ;and return info in [D]
OUT IDEportC ;Drive address onto control lines
ORI IDErdline ;RD pulse pin (40H)
OUT IDEportC ;Assert read pin
IN IDEportA
MOV D, A ;Return with data in [D]
MOV A, E
OUT IDEportC ;Deassert RD pin
XRA A
OUT IDEportC ;Zero all port C lines
RET
;------------------------------------------------------------------------------
;Write One Byte
;------------------------------------------------------------------------------
IDEwr8D: ;Write Data in [D] to IDE register [E]
MVI A, WRITEcfg8255 ;Set 8255 to write mode
OUT IDEportCtrl
MOV A, D ;Get data put it in 8255 A port
OUT IDEportA
MOV A, E ;Select IDE register
OUT IDEportC
ORI IDEwrline ;Lower WR line
OUT IDEportC
MOV A, E ;Raise WR line
OUT IDEportC ;Deassert RD pin
XRA A ;Deselect all lines including WR line
OUT IDEportC
MVI A, READcfg8255 ;Config 8255 chip, read mode on return
OUT IDEportCtrl
RET
;------------------------------------------------------------------------------
;This code is written to reside and run from 0H. To re-introduce the CPMLDR,
;it must be copied from where it is stored in high memory and relocated to 100H
;in RAM, which overwrites this program.
;------------------------------------------------------------------------------
CPM$MOVE$CODE
LXI H,BUFFER
LXI D,100H
LXI B,(12*512)
LDIR
JMP 100H
CPM$MOVE$CODE$END:
;------------------------------------------------------------------------------
;
;COMMAND BRANCH TABLE
;
;------------------------------------------------------------------------------
TBL: DW DRIVE$0 ; "A" Select Drive 0
DW DRIVE$1 ; "B" Select Drive 1
DW CPMBOOT ; "C" LOAD CPM (if present)
DW DISPLAY ; "D" Sector contents display:- ON/OFF
DW RAMCLEAR ; "E" Clear RAM buffer
DW FORMAT ; "F" Format current disk
DW RESTORE ; "G" Restore backup
DW BACKUP ; "H" Backup partition
DW NEXT$SECT ; "I" Next Sector
DW PREV$SEC ; "J" Previous sector
DW ERROR ; "K"
DW SET$LBA ; "L" Set LBA value (Set Track, sector)
DW ERROR ; "M"
DW POWER$DOWN; "N" Power down hard disk command
DW ERROR ; "O"
DW ERROR ; "P"
DW ERROR ; "Q"
DW READ$SEC ; "R" Read sector to data buffer
DW SEQ$RD ; "S" Sequental sec read and display contents
DW ERROR ; "T"
DW POWER$UP ; "U" Power up hard disk command
DW N$RD$SEC ; "V" Read N sectors
DW WRITE$SEC ; "W" Write data buffer to current sector
DW N$WR$SEC ; "X" Write N sectors
DW COPY$AB ; "Y" Copy Drive 0 to Drive 1
DW VERIFY$AB ; "Z" Verify Drive 0:= Drive 1:
;------------------------------------------------------------------------------
;
;String constants - Messages generated by this program
;
;------------------------------------------------------------------------------
SIGN$ON: DB CR,LF,'IDE Disk Drive Utility Program 12/01/2022 (v2.9b)',CR,LF,LF,'$'
SEL0MSG DB 'Selecting first IDE drive.',CR,LF,'$'
SEL1MSG DB 'Selecting second IDE drive.',CR,LF,'$'
INITDRIVE DB 'Initializing drive. $'
READING$ID DB 'Reading drive ID. $'
GETTING$ID DB 'Getting drive ID...',CR,LF,'$'
DISKSTATUS DB 'Status is $'
INIT$0$ERROR: DB 'Initialization of First Drive failed. Aborting Program.',BELL,CR,LF,LF,'$'
INIT$1$ERROR DB 'Initialization of Second Drive failed. (Possibly not present).',BELL,CR,LF,LF,'$'
ID$ERROR: DB 'Error obtaining Drive ID.',BELL,CR,LF,'$'
INIT$DR$OK: DB 'Drive Initialized OK.',CR,LF,LF,'$'
BAD$DRIVE: DB CR,LF,'First Drive ID Information appears invalid.',CR,LF
DB 'Aborting program.',BELL,CR,LF,LF,'$'
DRIVE0$INFO: DB '------------ Drive 0 -------------',CR,LF,'$'
DRIVE1$INFO: DB '------------ Drive 1 -------------',CR,LF,'$'
msgmdl: DB 'Model: $'
msgsn: DB 'S/N: $'
msgrev: DB 'Rev: $'
msgcy: DB 'Cyl: $'
msghd: DB ', Hd: $'
msgsc: DB ', Sec: $'
msgCPMTRK: DB 'CPM TRK = $'
msgCPMSEC: DB ' CPM SEC = $'
msgLBA: DB ' (LBA = 00$'
MSGBracket DB ')$'
DRIVE$0$MSG DB CR,LF,LF,' >>> DRIVE #0 <<<$'
DRIVE$1$MSG DB CR,LF,LF,' >>> DRIVE #1 <<<$'
CMD$STRING1: DB ' IDE Board Diagnostic MAIN MENU',CR,LF,LF
DB '(L) Set LBA value (R) Read Sector to Buffer (W) Write Buffer '
DB 'to Sector',CR,LF
DB '(D) Set Display ON (S) Sequental Sec Read (F) Format Disk',CR,LF
DB '(V) Read N Sectors (X) Write N Sectors (H) Backup disk',CR,LF
DB '(G) Restore Backup (I) Next Sector '
DB '(J) Previous Sector',CR,LF
DB '(U) Power Up (N) Power Down (C) Boot CPM',CR,LF
DB '(A) Select Drive 0 (B) Select Drive 1 '
DB '(E) Clear Sector Buffer',CR,LF
DB '(Y) Copy d0 to d1 (Z) Verify d0 = d1 (ESC) Quit',CR,LF
DB LF,'Current settings: $'
CMD$STRING2: DB ' IDE Board Diagnostic MAIN MENU',CR,LF,LF
DB '(L) Set LBA value (R) Read Sector to Buffer (W) Write Buffer '
DB 'to Sector',CR,LF
DB '(D) Set Display OFF (S) Sequental Sec Read (F) Format Disk',CR,LF
DB '(V) Read N Sectors (X) Write N Sectors (H) Backup disk',CR,LF
DB '(G) Restore Backup (I) Next Sector '
DB '(J) Previous Sector',CR,LF
DB '(U) Power Up (N) Power Down (C) Boot CPM',CR,LF
DB '(A) Select Drive 0 (B) Select Drive 1 '
DB '(E) Clear Sector Buffer',CR,LF
DB '(Y) Copy d0 to d1 (Z) Verify d0 = d1 (ESC) Quit',CR,LF
DB LF,'Current settings:- $'
Prompt: DB CR,LF,LF,'Please enter command > $'
msgsure: DB CR,LF,'Warning: this will change data on the drive, '
DB 'are you sure? (Y/N)...$'
msgrd: DB CR,LF,'Sector Read OK',CR,LF,'$'
msgwr: DB CR,LF,'Sector Write OK',CR,LF,'$'
GET$LBA: DB 'Enter CPM style TRK & SEC values (in hex).',CR,LF,'$'
SEC$RW$ERROR DB 'Drive Error, Status Register = $'
ERR$REG$DATA DB 'Drive Error, Error Register = $'
ENTER$SECL DB 'Starting sector number,(xxH) = $'
ENTER$TRKL DB 'Track number (LOW byte, xxH) = $'
ENTER$TRKH DB 'Track number (HIGH byte, xxH) = $'
ENTER$HEAD DB 'Head number (01-0F) = $'
ENTER$COUNT DB 'Number of sectors to R/W = $'
DRIVE$BUSY DB 'Drive Busy (bit 7) stuck high. Status = $'
DRIVE$NOT$READY DB 'Drive Ready (bit 6) stuck low. Status = $'
DRIVE$WR$FAULT DB 'Drive write fault. Status = $'
UNKNOWN$ERROR DB 'Unknown error in status register. Status = $'
BAD$BLOCK DB 'Bad Sector ID. Error Register = $'
UNRECOVER$ERR DB 'Uncorrectable data error. Error Register = $'
READ$ID$ERROR DB 'Error setting up to read Drive ID',CR,LF,'$'
SEC$NOT$FOUND DB 'Sector not found. Error Register = $'
INVALID$CMD DB 'Invalid Command. Error Register = $'
TRK0$ERR DB 'Track Zero not found. Error Register = $'
UNKNOWN$ERROR1 DB 'Unknown Error. Error Register = $'
CONTINUE$MSG DB CR,LF,'To Abort enter ESC. Any other key to continue. $'
FORMAT$MSG DB 'FORMAT DISK. Fill all sectors with E5'
DB 60H,'s on the CURRENT drive/CF card.$'
ReadN$MSG DB CR,LF,'Read multiple sectors from current disk/CF card to RAM buffer.'
DB CR,LF,'How many 512 byte sectores (xx HEX):$'
WriteN$MSG DB CR,LF,'Write multiple sectors RAM buffer CURRENT disk/CF card.'
DB CR,LF,'How many 512 byte sectores (xx HEX):$'
ReadingN$MSG DB CR,LF,'Reading Sector at: $'
WritingN$MSG DB CR,LF,'Writing Sector at: $'
msgErr DB CR,LF,'Sorry, that was not a valid menu option!$'
FormatDone DB CR,LF,'Disk Format Complete.',CR,LF,'$'
BackupDone DB CR,LF,'Disk partition copy complete.',CR,LF,'$'
CopyMsg DB CR,LF,'Copy disk partition to a second area on disk (CF card).'
DB CR,LF,'>>> This assumes that tracks greater than MAXTRK '
DB '(for CPM, 0FFH) are unused <<<'
DB CR,LF,'>>> on this disk. Be sure you have nothing in this '
DB '"Backup partition area". <<<'
DB CR,LF,BELL,'Warning: This will change data in the partition area, '
DB 'are you sure? (Y/N)...$ '
AtEnd DB CR,LF,'At end of disk partition!',CR,LF,'$'
RBackup$MSG DB 'Reading track: $'
WBackup$MSG DB 'H. Writing track: $'
H$Msg DB 'H$'
RestoreMsg DB CR,LF,'Restore disk with data from backup partition on disk (CF card).'
DB CR,LF,BELL,'Warning: This will change data on disk, '
DB 'are you sure? (Y/N)...$ '
RestoreDone DB CR,LF,'Restore of disk data from backup partition complete.',CR,LF,'$'
RANGE$MSG DB CR,LF,'Sector value out of range.',CR,LF,'$'
CPM$ERROR DB CR,LF,'Error reading CPMLDR.',CR,LF,'$'
CPM$ERROR1 DB CR,LF,'Data error reading CPMLDR. (The first byte loaded was not 31H).',CR,LF,'$'
MOVE$REQUEST DB CR,LF,'The CPMLDR image is now at 3000H in RAM. '
DB 'To boot CPM you will have to'
DB CR,LF,'overwrite this program at 100H. Do you wish to do so (Y/N)...$'
SETA$MSG DB CR,LF,'Current Drive is now #0 (Yellow LED)$'
SETB$MSG DB CR,LF,'Current Drive is now #1 (Green LED)$'
FILL$MSG DB CR,LF,'Sector buffer in RAM filled with 0',27H,'s$'
DiskCopyMsg DB CR,LF,'Copy disk partition of Drive 0 to Drive 1 (CF card).'
DB CR,LF,BELL,'Warning: This will delete all data on Drive 1, '
DB 'are you sure? (Y/N)...$ '
CopyDone DB CR,LF,'Disk copy of CPM disk 0 to 1 complete.',CR,LF,'$'
CopyTrk$MSG DB 'Copying track: $'
DiskVerifyMsg DB CR,LF,'Verify disk partition Drive 0 = Drive 1 (CF card).$'
VerifyTrk$MSG DB 'Verifying track: $'
VerifyDone DB CR,LF,'Verify CPM disk 0 = 1 complete.',CR,LF,'$'
Verify$ERR DB CR,LF,BELL,'Verify error on Track $'
SEC$Msg DB 'H Sector $'
;------------------------------------------------------------------------------
;RAM usage
;------------------------------------------------------------------------------
RAMAREA DB ' RAM STORE AREA -------->'
@DMA DW buffer
@DRIVE$SEC DB 0H
@DRIVE$TRK DW 0H
@DisplayFlag DB 0FFH ;Display of sector data initially ON
@SEC DW 0H
@TRK DW 0H
@SEC1 DW 0H ;For disk partition copy
@TRK1 DW 0H
@SEC2 DW 0H
@TRK2 DW 0H
@StartLineHex DW 0H
@StartLineASCII DW 0H
@BYTE$COUNT DW 0H
@SECTOR$COUNT DW 0H
@DELAYStore DB 0H
@CURRENT$DRIVE DB 0H
DB ' Start of ID buffer-->'
IDbuffer DS 512
DB '<--End of ID buffer '
ORG BUFFER$ORG
BUFFER: DB 76H
DB '<--Start buffer area'
DS 476
DB 'End of buffer-->'
BUFFER2: DB '<--Start buffer2 area'
DS 476
DB 'End of buffer2-->'
DS 100H
STACK DW 0H
;END