We will write software for all these steps. Normally
I have seen the process written from the bottom up. I think it is in fact easier
to understand if we go from the top down.
THE EPROM MULTI SECTOR HARD DISK BOOT LOADER.
The first difference between a Floppy and Hard Disk system is that the CPMLDR
(see below) boot process is a little simpler. Rather than read in one sector
from the disk (to get disk parameters etc.) and then from that sector load
in the remaining sectors, in the case of Hard disks it is common to read
in all of the CPMLDR sectors at once via the EPROM Boot Loader. This is because
in the case of a hard disk the format is fixed and we always know how many
actual sectors are required. In the case of floppy disks the number varies
depending on the sector size. Once the CPMLDR is present in RAM at 100H
upwards control is transferred to it.
The code at 100H is RAM is really a stripped down very compact CPM operating
system (called CPMLDR.COM) that's only function in life is to read is a
single CPM file on the disk named CPM3.SYS. It has to be a "proper" CPM
operating system however because the CPM3.SYS file is a "normal" CPM file and can
exist anywhere on the disk. In a Non-Banked system this CPM3.SYS
code is placed in the top of RAM. In a Banked system much of it is placed in
another RAM bank - thus freeing up much of the TPA. More on that later.
We are almost home. The last step in the process is the CPM3.SYS code
itself reads in a further CPM3 file called CCP.COM. This Console Command
Processor file has the code that links the operating system with the
outside world (console, printer etc). When this is done the CPM3.SYS code
transfers control to the CCP.COM code and the A:> appears.
This my seem a convoluted way of doing things -- and to some extent it is,
however what is really nice is that once you have the original CPMLDR working
you can easily and quickly make changes to the operating system by placing a new
CPM3.SYS file on the disk. If you change the name (CPM always looks for
exactly "CPM3.SYS") you can have different hardware arrangements stored on the
same hard disk.
Now let us look at an example of the EPROM boot loader code. My complete
"MASTER.Z80" EEPROM code can be see
here. See the bottom
of this page for all CPM file downloads. The relevant
Hard Disk boot loader code begins at HBOOTCPM:
This is the monitor I use with the S100Computers
Z80 CPU
board. It utilizes the simple command driven interface to read disk
sectors into RAM utilizing the
IDE board.
Other hard disk controller boards would behave essentially the same way.
;-------------- BOOT UP CPM FROM HARD DISK ON S100COMPUTERS IDR BOARD ----------------
;BOOT UP THE 8255/IDE Board HARD DISK/Flash Memory Card
HBOOTCPM:
POP HL ;CLEAN UP STACK
LD HL,SPEAKCPM_MSG ;Announce on speaker
CALL SPEAK$
CALL INITILIZE_IDE_BOARD ;Initialize the 8255 and drive (again just in case)
LD D,11100000B ;Data for IDE SDH reg (512bytes, LBA mode,single drive)
LD E,REGshd ;00001110,(0EH) CS0,A2,A1,
CALL IDEwr8D ;Write byte to select the MASTER device
LD B,0FFH ;Delay time
WaitInit:
LD E,REGstatus ;Get status after initilization
CALL IDErd8D ;Check Status (info in [D])
BIT 7,D
JR Z,SECREAD ;Zero, so all is OK to write to drive
;Delay to allow drive to get up to speed
PUSH BC
LD BC,0FFFFH
DXLAY2: LD D,2 ;May need to adjust delay time to allow cold drive to
DXLAY1: DEC D ;to speed
JR NZ,DXLAY1
DEC BC
LD A,C
OR B
JR NZ,DXLAY2
POP BC
DJNZ WaitInit
IDError:
LD HL,DRIVE_NR_ERR ;Drive not ready
JP ABORT_ERR_MSG
SECREAD: ;Note CPMLDR will ALWAYS be on TRK 0,SEC 1,Head 0
LD A,11111111B ;FLAG PROGRESS VISUALLY FOR DIAGNOSTIC
OUT (DIAG_LEDS),A
CALL IDEwaitnotbusy ;Make sure drive is ready
JR C,IDError ;NC if ready
LD D,1 ;Load track 0,sec 1, head 0
LD E,REGsector ;Send info to drive
CALL IDEwr8D
LD D,0 ;Send Low TRK#
LD E,REGcyLSB
CALL IDEwr8D
LD D,0 ;Send High TRK#
LD E,REGcyMSB
CALL IDEwr8D
LD D,SEC_COUNT ;Count of CPM sectors we wish to read
LD E,REGcnt
CALL IDEwr8D
LD D,CMDread ;Send read CMD
LD E,REGCMD
CALL IDEwr8D ;Send sec read CMD to drive.
CALL IDEwdrq ;Wait until it's got the data
LD HL,CPM_ADDRESS ;DMA address where the CPMLDR resides in RAM
LD B,0 ;256X2 bytes
LD C,SEC_COUNT ;Count of sectors X 512
MoreRD16:
LD A,REGdata ;REG register address
OUT (IDECport),A
OR IDErdline ;08H+40H, Pulse RD line
OUT (IDECport),A
IN A,(IDEAport) ;read the LOWER byte
LD (HL),A
INC HL
IN A,(IDEBport) ;read the UPPER byte
LD (HL),A
INC HL
LD A,REGdata ;Deassert RD line
OUT (IDECport),A
DJNZ MoreRD16
DEC C
JR NZ,MoreRD16
LD E,REGstatus ;Check the R/W status when done
CALL IDErd8D
BIT 0,D
JR NZ,IDEerr1 ;Z if no errors
LD HL,STARTCPM
LD A,(HL)
CP 31H ;EXPECT TO HAVE 31H @80H IE. LD SP,80H
JP Z,STARTCPM ;AS THE FIRST INSTRUCTION. IF OK JP to 100H in RAM
JP ERR_LD1 ;Boot Sector Data incorrect
IDEerr1:
LD HL,IDE_RW_ERROR ;Drive R/W Error
JP ABORT_ERR_MSG
; ----- SUPPORT ROUTINES --------------
INITILIZE_IDE_BOARD: ;Drive Select in [A]. Note leaves selected drive as [A]
LD A,RDcfg8255 ;Config 8255 chip (10010010B), read mode on return
OUT (IDECtrl),A ;Config 8255 chip, READ mode
;Hard reset the disk drive
;For some reason some CF cards need to the RESET line
;pulsed very carefully. You may need to play around
LD A,IDEreset ;with the pulse length. Symptoms are: incorrect data coming
OUT (IDECport),A ;back from a sector read (often due to the wrong sector being read)
;I have a (negative)pulse of 2.7uSec. (10Mz Z80, two IO wait states).
LD B,20H ;Which seem to work for the 5 different CF cards I have
ResetDelay:
DEC B
JP NZ,ResetDelay ;Delay (reset pulse width)
CALL DELAY_32 ;Need to stretch pulse!
XOR A
OUT (IDECport),A ;No IDE control lines asserted (just bit 7 of port C)
IDEwaitnotbusy: ;Drive READY if 01000000
LD B,0FFH
LD C,80H ;Delay, must be above 80H for 4MHz Z80. Leave longer for slower drives
MoreWait:
LD E,REGstatus ;Wait for RDY bit to be set
CALL IDErd8D
LD A,D
AND 11000000B
XOR 01000000B
JR Z,DoneNotbusy
DJNZ MoreWait
DEC C
JR NZ,MoreWait
SCF ;Set carry to indicate an error
RET
DoneNotBusy:
OR A ;Clear carry it indicate no error
RET
;Wait for the drive to be ready to transfer data.
;Returns the drive's status in Acc
IDEwdrq:
LD B,0FFH
LD C,0FFH ;Delay, must be above 80H for 4MHz Z80. Leave longer for slower drives
MoreDRQ:
LD E,REGstatus ;wait for DRQ bit to be set
CALL IDErd8D
LD A,D
AND 10001000B
CP 00001000B
JR Z,DoneDRQ
DJNZ MoreDRQ
DEC C
JR NZ,MoreDRQ
SCF ;Set carry to indicate error
RET
DoneDRQ:
OR A ;Clear carry
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 Read here) is done directly
; in the routine MoreRD16 for speed reasons.
IDErd8D: ;READ 8 bits from IDE register in [E], return info in [D]
LD A,E
OUT (IDECport),A ;drive address onto control lines
OR IDErdline ;RD pulse pin (40H)
OUT (IDECport),A ;assert read pin
IN A,(IDEAport)
LD D,A ;return with data in [D]
LD A,E ;<---Ken Robbins suggestion
OUT (IDECport),A ;Deassert RD pin
XOR A
OUT (IDECport),A ;Zero all port C lines
RET
IDEwr8D: ;WRITE Data in [D] to IDE register in [E]
LD A,WRcfg8255 ;Set 8255 to write mode
OUT (IDECtrl),A
LD A,D ;Get data put it in 8255 A port
OUT (IDEAport),A
LD A,E ;select IDE register
OUT (IDECport),A
OR IDEwrline ;lower WR line
OUT (IDECport),A
LD A,E ;<-- Kens Robbins suggestion, raise WR line
OUT (IDECport),A
XOR A ;Deselect all lines including WR line
OUT (IDECport),A
LD A,RDcfg8255 ;Config 8255 chip, read mode on return
OUT (IDECtrl),A
RET
As I explained above, we have just read in 12 sectors to RAM starting at 100H. Note how the
HBOOTCPM in this case jumps to 100H in RAM once all 12 sectors have been read.
It is here the CPMLDR for our hard disk CPM3 system now resides.
THE CPMLDR LOADER.
Next we will write the CPM Loader program
CPMLDR.COM. This is actually the hardest part of the software to write.
We are is essence writing a primitive CPM3 BIOS for the hard disk. The good news is we don't worry
about memory banking, and only have to take care of two major BIOS functions,
reading sectors from (only) the Hard Disk and writing to the console.
You don't even need console input -- though it is useful for debugging.
In CPM3 each disk has its own Disk Parameter
Block table. Unfortunately
these tables (DPB's) are fairly complex and rather
than repeat everything here, you should read the Digital Research
CPM3
System Guide
mentioned above. They contain byte and word values to define areas in RAM
for sector skew translation, directory buffers and hash tables etc.
We will not worry about any of this now because Digital Research provides a
series of Assembler macros that builds these tables automatically for you.
For our hard disk the DPB macro is:-
HD$DPB: DPB
512,61,256,2048,1024,1,8000H
512 = Bytes per sector
61 = Sectors per track
256 = Tracks per disk
2048 = The
allocation unit size (2K blocks for our hard disk)
1024 = Maximum number of directory entries on
a disk
1 = Number or
tracks reserved for the CPM operating system.
8000H= Special flag to indicate disk is
permanent/hard disk
Tracks start at 0,1,2,3,.. so track 0 is reserved for the operating system. The disk
directory starts on Track 1 in our case.
You will see this macro at the bottom of the above code listing.
Next we need another table which Digital Research calls a
Disk Parameter Header table or
DPH. This table is somewhat simpler than in
the case of the 8" floppy disk DPH table. This time we do not need to use a
sector skew so the first entry. So it is set to 0.
In fact the only thing this table
contains is a pointer to the above DPB table.
Again an assembler macro is supplied.
For our hard disk the DPH macro is:-
DPH0: DPH
0,HD$DPB,0,
0
For floppies this entry points to the "skew" macro that describes how the sectors
are numbered on a track (it is not used here).
HD$DPB Is a word pointer to the
above DPB for the hard disk.
0 This is the maximum size in bytes of the disk checksum vector, lets skip for now
0 This is the maximum size in bytes of the disk allocation vector, lets skip for
now also and just use these values
OK we are almost there. There is still yet one more table. This is the
Disk Drive Table or
DTBL
This one is simple. CPM3 allows for up to 16 different drives. The
DPH table is just a list of pointers to each DPH for each drive. Any entries
with no drive are set to 0. We have only one drive here.
So the DTBL will be:-
@DTBL: DW DPH0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
When you look at the bottom of the code for the CPMLDR "BIOS" (HLDRBIOS.Z80)
you will see all of the above combined. For more efficient use of code space I
have actually hand coded the DPH table.
Now in order to have a functional basic operating system we need to splice in
the Digital Research CPMLDR.REL file. This a
disk operating system file supplied by Digital Research that works with our
basic custom BIOS. Remember all we are trying to do here is read in
the main CPM3.SYS file on the disk. The CPMLDR.REL file is set to run at
100H in RAM. It is constructed such that it expects immediately
above it the Loader BIOS jump vectors exactly as they are at the start of
HLDRBIOS.ASM.
We use the CPM program LINK to splice the two sections together and write them
out as one file CPMLDR.COM.
If you load this program with SID or ZSID and jump to 100H in RAM the above code
should work and come back telling you it cannot locate the CPM3.SYS file on the
disk. (Because we have not done it yet).
However before we get to that we have to do one other thing. We need to get some
way to write the CPMLDR.COM file to tracks 0 of the hard disk.
I have written the program HSYSGEN.Z80
to do this (see below). Its essentially a reverse of the EPROM
monitor code to write (rather than read) multiple sectors to the disk.
Finally we need to splice the HSYSGEN and CPMLDR code together to yield one
"standard" CPM program which I call
HSYSGEN.COM.
This program on any of our IDE disks or CF cards will place the above
CPMLDR.com code on the system tracks.
The code is a little bit more complicate than I described above because it has
the option of placing a Banked or Non-Banked CPMLDR.COM file on the disk. For
now everything will be for a non-banked system.
All of the above assembly and splicing can be done automatically by running the
HSYSGEN.SUB file. This can be see
here.
THE CPM3.SYS FILE.
This file is the heart of the operating system. First we need to decide
how we will layout our hardware. For our first system we will just have a
Console and one hard disk A:
First we will work on the Hard disk portion of the BIOS. This is contained in
the file HIDE3.ASM. The source code can be
seen here. The BIOS contains
essentially the same tables that I described above and uses the same assembler
macros. However there is one more new table structure it is called the
Extended Disk Parameter Table
(XDPH). The XDPH is in fact nothing more than the above DPH table
except that additional bytes and word parameters are placed immediately before
and ( if required), after, the "regular" DPH table described above. However the exact
placement of these extra parameters is very critical. Here is the XDPH
table I use for drive A:
; EXTENDED DISK PARAMETER HEADER FOR DRIVE 0: (A:)
DW HDWRT ;HD SEC WRITE ROUTINE
DW HDRD ;HD SEC READ ROUTINE
DW HDLOGIN ;HD LOGIN PROCEDURE
DW HDINIT ;HD DRIVE INITIALIZATION ROUTINE
DB 0 ;RELATIVE DRIVE 0 ON THIS CONTROLLER
DB 0 ;MEDIA TYPE KNOWN
;HI BIT SET : DRIVE NEEDS RECALIBRATING
DPH0: DPH 0,IDEHD$DPB,0,
IDEHD$DPB DPB 512,61,256,2048,1024,1,8000H
You can see the normal DPH begins at DOH0: Listed above it are two byte
fields (unused here) that the software can use as flags for floppy disk density/format
etc. CPM does not use them. Then immediately above them are four word
pointers to disk the initialization, login, sector read and write routines.
These are the routines you must custom write for each disk. CPM also adds
table values to the bottom of the DPH. In fact from the location DPH0: the next
24 bytes must never be changed by your custom BIOS. You can add any other flags
or pointers you like below that. I use two in all my floppy disk BIOS'es but we
don't need any here.
Take a look over the HIDE3.ASM code to get
comfortable with the above. You should by now be familiar with the disk DPH
and DPB macros.
Next we need to look at the HDRVTBL2.ASM file.
This one is simple. It just contains the DTBL we described above. We have
only one disk whose DPH's are labeled DPH0 so we see:-
@DTBL: DTBL
<DPH0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0>