Re: IDE interface for Altair 8800c
Posted: December 1st, 2022, 4:09 pm
Here's the code as it stands now. It is able to retrieve drive ID for drives or flash cards, both for drive 0 and drive 1.
It still needs some work, so we're not "cookin' with gas" yet but we definitely have the pilot light lit.
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