home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
ftp.barnyard.co.uk
/
2015.02.ftp.barnyard.co.uk.tar
/
ftp.barnyard.co.uk
/
cpm
/
walnut-creek-CDROM
/
CPMINFO
/
CPM.DOC
< prev
next >
Wrap
Text File
|
2000-06-30
|
44KB
|
888 lines
--------- CP/M Documentation as of 1-July-80 ---------
Although CP/M is not the ultimate in operating systems it is an
extremely useful software development tool which is implementable on a
wide variety of 8080 and Z80 based computers. As a result it has
become, by default if not by design, the standard of users of these
microprocessors.
CP/M's implementability lays mainly in the portability afforded
to it through the use of the BIOS (Basic I/O System). This concept is
a simple but very effective solution to the problem of hardware
variability inherent in microprocessor-based computer systems. The
S-100 bus not withstanding, there seems to be as many hardware
configurations as there are designers (as an example, my own computer
has a 56-pin backplane and uses memory-mapped I/O). It is a
surprising and welcome gesture for a software manufacturer to supply
the exact procedures necessary to bring up its software. By the way
of contrast, I know a very compulsive programmer who spent the better
part of a month bringing up ISIS on a non-MDS hardware, whereas it
took less than a week for this same programmer to bring up CP/M on the
same hardware.
The other major factor in CP/M's success must be the
configurability. How many microcomputer users start with 64K (which
ISIS requires to do anything useful)? With the ability to turn a 16K
toy into a "real" computer, CP/M has ensured a very large following of
devotees.
As mentioned in the users manual, CP/M is divided into four
logically distinct but interacting parts:
BDOS (Basic Disk Operating System)
BIOS (Basic Input/Output System)
CCP (Console Command Processor)
TPA (Transient Program Area)
As there isn't a whole lot to say about the TPA other than that
is where the transient programs are executed, and the users manual is
very explicit on the use of the CCP, I will deal mainly with the BDOS
and BIOS, which I consider to be the heart of the system.
THE BASIC DISK OPERATING SYSTEM (BDOS)
The BDOS is the file manager of CP/M. Though it an application
program can:
o Open a file
o Close a file
o Search for the first and subsequent entries in the
directory for a file
o Erase a file from the directory
o Read and Write logical records
o Create entries in the directory
o Rename entries in the directory
as well as various support functions. These functions mainly add a
level of abstraction to the disk hardware as implemented in the BIOS
allowing the application program(mer) to deal with the stored data
without having to know where it is physically located. The BDOS also
acts as a conduit between the application program and the character
I/O entry points in the BIOS as well as providing some macro-functions
for string I/O to and from the console. Thus through the use of the
BDOS, CP/M is able to transform a computer and its peripherals into a
generalized system with no particular hardware characteristics machine
language itself. (and through the use of one of the many "hi-level"
languages which have been implemented to run under CP/M, it is possibe
to solve problems with out even knowing or caring what kind of CPU is
running the show). To my way of thinking, this is a rather
significant development in micro-computing.
The BDOS functions are listed and explained quite throughly in
the programmer's guide so I will not bore the reader (and make myself
type anyone than I have to) by reiterating them here. However, I
would like to address myself to a couple of points which are
associated with the BDOS. First, while monkeying around one day, I
discovered that there are some strange locations at the beginning of
the BDOS which can be (and most definitely are) used by transient
programs even though they are supposedly secure inside the operating
system. Here is an outline of these locations:
ORG BDOS
SERNUM: DS 6
ENTRY: JMP COMMAND
DW BADIO
DW SELERROR
DW ROERROR
MAP: LXI H,MAPTABLE ; POINT HL AT LOG TO PHYS MAPPING
MVI B,0 ; SET BC TO LOGICAL SECTOR (IN C)
DAD B ; INDEX INTO MAPPING TABLE
MOV C,M ; CONVERT LOGICAL TO PHYSICAL SECTOR
JMP SETSECTOR ; DO BIOS SET SECTOR FUNCTION
NOP
MAPTABLE:
DB 1,7,13,19
DB 25,5,11,7 ; THIS TABLE MAPS LOGICAL ONTO
DB 23,3,9,15 ; PHYSICAL DISK SECTORS
DB 21,2,8,14
DB 20,26,6,12
DB 18,24,4,10
DB 16,22,0,0
DB 0,0,0,0 ; THE NULLS ARE ADDRESS ALINGMENT
CONFIG:
DB SECPT ; SECTORS PER TRACK(26 ON IBM DISKS)
DB LASTDIR ; NUMBERS OF LAST DIRECTORY ENTRY (63)
DB RPB ; 2**RPB=RECORDS PER BLOCK (3)
DB LASTSEC ; LAST SECTOR IN BLOCK (7)
DB LASTBLOCK ; LAST BLOCK ON THE DISK (242)
DB DIRALLOC ; DIRECTORY ALLOCATION MASK (C0 HEX)
DB DIRTACK ; TRACK THAT DIRECTORY BEGINS ON (2)
The first 6 bytes of the BDOS contain the serial numbers which
supposedly prevent copyright infringments. The next 3 contain the
actual entry point to the BDOS command decoder. The next 9 bytes are
the addresses of routines inside the BDOS which are executed when one
of the three fatal errors occur. Following the error addresses is the
routine which the BDOS uses to stagger its data on a track followed by
the mapping table. After the mapping table are some constants which
define the particular implementation of CP/M on specific size disks.
The error address can be modified by an application program to
recover somewhat more gracefully from a fatal error (I ran across this
in a screen-oriented text editor which made possible to save the
memory image of the program after an I/O error). The configuration
table data can be used (and is used by STAT in version 1.4) to find
out what's going on with disk space.
As far as I can tell, it is not serendipitious that this
information exists and is located where it is. It seems that the
people at Digital Research left room for certain easy modifications
and enhancements to CP/M.
The second point I would like to address is the technique CP/M
uses to do housekeeping on its files. All of the file management
functions in the BDOS use the address of an FCB (file control block)
as their parameter. Like the concept of the I/O byte the FCB is a
rather elegant solution to the housekeeping problem in a file
management system. It accomplishes two things for the operations
system. First it "decentralizes" the process of maintaining and
second, it gives application programs access to the same information
about a file that the BDOS uses. These two factors together allow an
application to get as close as it requires to the file management
process. There are some problems with the FCB, as implemented. First
there is the question of whether or not an application OUGHT to have
access to housekeeping information as it is possible to louse things
up pretty badly if things are not done correctly. This I think is a
matter of taste. Since CP/M, as implemented, is a single-user system
there isn't the problem of messing other people's files up and it is a
foolish programmer indeed who messes with something that is not
understood.
The other problem is a bit more serious (rumor has it that
Digital Research is dealing with it). Since the largest number that
an allocated block can have (see below) is 255(D) and with a block
size of an even 2k, the maximum number of bytes of bytes of storage
CP/M can address on one disk is approximately 510 kilobytes. This
presents serious impediments (due to program standardization) to
implementing the operating system on the larger disk systems becoming
available.
Be that as it may, within the scope of single (or perhaps double)
density floppy disks, the FCB is in my opinion, a stroke of genius.
Because an application, through the use of the FCB, has such
flexibility it behooves the assembly language programmer to understand
as best he or she can how to use it. The programmer's guide describes
the format and I'll elaborate a bit on it.
The FCB consists of seven fields of information each having a
mnemonic associated with it. They are:
FIELD FCB POSITIONS
ET 0
FN 1-8
FT 9-11
EX 12
NOT USED 13-14
RC 15
DM 16-31
NR 32
The FN and FT fields are only logically distinct. The BDOS uses
all eleven bytes as a fundamental unit of information when opening,
closing, creating, erasing, searching and renaming files. (The EX
field is included during OPEN, CLOSE, CREATE and SEARCH operations).
It is the CCP and transient programs which make a distinction between
file name and file type.
Since a file can be of any length up to the capacity of the disk
and since a single FCB describes only 16k bytes of a file, there must
be a way to link multiple sections of a file each described by its own
FCB. This is done via the EX field. The first extent of a file has
an EX value of 0, the second a value of 1 and so on.
The ET field is an interesting mixture of usefulness and
ambiguity. When the FCB is stored in the directory the ET filed may
contain a 0, indicating that the entry is used, or, an E5(H)
indicating that the entry has been deleted (or never used). However,
when the FCB is used as a parmeter for one of the file management
functions, the ET field serves an entirely different purpose. If ET=0
then then the BDOS will assume that the command pertains to the
currently selected disk. If the ET field is not zero then the BDOS
assumes that it contains the disk number+1 to which the function
pertains. In this case the BDOS will temporarily select disk number
ET-1 and then clear ET to zero before proceeding with the requested
function. When the file operation is complete, the BDOS will restore
ET to its original value. Thus, an application program need never
concern itself with remembering or selecting specific disks as these
values are retained throughout processing from the time that the CCP
sets them up in the default FCBs.
The RC field is essentially an "end of extent" pointer. It is
"pushed" along by the NR field when writing is used to limit the NR
field during reading to prevent the reading unwritten data.
The DM field is an array of 16 bytes, each representing a logical
block of data within the extent. The value of each of these bytes
represents the physical area of disk space allocated to the logical
block unless the value is 0, in which case the block has not been
allocated any disk space.
Together, the RC and DM fields form a "current" description of
the locations on the disk used by the data contained with the extent.
The NR field is used to specify which record, relative to the
beginning of the extent, is to be read or written. The BDOS will
automatically increment this number during read and write operations,
making sequential file access virtually automatic.
The BASIC I/O SYSTEM (BIOS)
===========================
This section of the system concerns itself with the hardware
dependent aspects of I/O. There are two types:
1) Disk I/O, which is block oriented
2) Character I/O, which is byte oriented
It is convenient to consider these two aspects seperately as they do
not interact directly.
Looking at the BIOS jump table (as described in the CP/M
documentation), the first two entries are paths to system
initialization routines. The next six are entry points to the
character I/O routines and the rest are entry points to disk I/O and
disk support routines. The section called "BIOS Entry Points" in the
System Alteration Guide describes the function of each of these 15
entry points better than I could. However, what the guide does not do
(as it is only a manual) is point out the importance of the I/O byte.
I consider this to be deserving of special attention.
Experience (mostly my own) has shown that until one makes
concrete use of the I/O byte concept, it is difficult to appreciate
the elegance of this technique. It does have its limits, but it is
very simple and effective solution to CP/M's character I/O device
standardization problems.
I first ran across this concept on the MDS MOD-80 development
system which did not have disk drives and used paper tape for its
off-line storage. The Intel Monitor used a standard jump table which
allowed programs to do character I/O without necessarily having to
worry about the actual hardware devices. Perhaps you've seen it, but
in case you haven't here it is.
ORG MOINTOR
JMP MAINLINE
JMP CI ; CONSOLE INPUT
JMP RI ; READER INPUT
JMP CO ; CONSOLE OUTPUT
JMP PO ; PUNCH OUTPUT
JMP LO ; LIST OUTPUT
JMP CSTS ; RETURN CONSOLE STATUS
JMP IOCHK ; RETURN I/O BYTE
JMP IOSET ; CHANGE I/O BYTE (NEW VALUE IN C)
JMP MEMCK ; RETURN TOP OF USER MEMORY IN A AND B
As you can see, this does basically the same that the BIOS jump
table does. IOCHK, IOSET and MEMCHK are not needed in the BIOS since
the information returned by these routines are located in the zero
page of CP/M's memory.
As the alteration guide is not explicit on the subject of
implementing an I/O byte, I'll outline in assembly language code the
techniques I've found useful for a generalized implementation.
But first notice that each of the six character I/O routines must
decode out the path to the specific I/O device "currently assigned".
The way this is done (in English) is as follows. The I/O byte
contains four fields, each as consisting of low bits. Each field is
associated with one of the four "logical" I/O devices (List, Punch,
Reader and Console) and may take on the value of (in binary) 00,
01,10, or 11. Thus, up to four different physical I/O devices may be
associated with each of the four "logical" devices. For example, the
logical device "List." By manipulating the value of these 2 bits
(presumably) without affecting the rest of the byte) one may "assign"
a specific hardware driver (and the device itself) to the list device.
In PASCALese this is the decoder:
VAR IOBYTE(4): PACKED ARRAY OF (0..3)
DO CASE IOBYTE(4)
0: TTYOUT;
1: LPTOUT;
2: CRTOUT;
3: USERLIST;
END
The alteration guide and documentation on PIP and STAT describe
what these physical device might be. The command:
STAT VAL:
produces essentially a menu of the nominal physical devices assignable
in the CP/M system.
Here is some software:
IOBYTE EQU 3 ; LOCATION OF IOBYTE
CMASK EQU 03H ; CONSOLE MASK
RMASK EQU 0CH ; READER MASK
PMASK EQU 30H ; PUNCH MASK
LMASK EQU 0C0H ; LIST MASK
RD2 EQU 08H ; READER DEVICE 2 MASK
PD2 EQU 40H ; PUNCH DEVICE 2 MASK
CONTIN:
LDA IOBYTE ; GET IOBYTE
ANI CMASK ; LOOK AT CONSOLE FIELD
JZ TTYIN ; CONSOLE 0:
JPE UCIN1 ; CONSOLE 3:
RAR ; CONSOLE1:
JC KBDIN ; CONSOLE 1:
JMP BACHIN ; CONSOLE 2
ONOUT:
LDA IOBYTE ; GET IO BYTE
ANI CMASK ; LOOK AT CONSOLE FIELD
JZ TTYOUT ; CONSOLE 0:
JPE UCOUT1 ; CONSOLE 3:
RAR ; LOOK FOR CONSOLE 1
JC CRTOUT ; CONSOLE 1:
JMP BACHOUT ; CONSOLE 2
CONSTAT:
LDA IOBYTE
ANI CMASK
JZ TTYSTAT ; CONSOLE STATUS 0
JPE UCSTAT1 ; CONSOLE STATUS 3
RAR
JC KBDSTAT ; CONSOLE STATUS 3
RET ; DON'T KNOW WHAT STATUS 2 IS
READER:
LDA IOBYTE
ANI RMASK ; LOOK AT READER BITS ONLY
JZ TTYIN ; READER 0
JPE UR2 ; READER 3:
ANI RD2 ; SEE IF EITHER READER 1 OR 2
JZ PT ; READER 1 IF ZERO
JMP UR1 ; READER 2 IF NOT
PUNCH:
LDA IOBYTE
ANI PMASK
JZ TTYOUT ; PUNCH 0
JPE UP2 ; PUNCH 3
ANI PD2 ; SEE IF EITHER READER 1 OR 2
JZ PTP ; PUNCH 1 IF ZERO
JMP UP1 ; PUNCH 2 IF NOT
LIST:
LDA IOBYTE
ANI LMASK
JZ TTYOUT ; LIST 0:
JPE UL1 ; LIST 3:
JM LPTOUT ; LIST 2:
JMP CRT ; LIST 1:
The logical device "BATCH" is a hangover from the Intel I/O byte
definition. It was originally intended to allow console input to be
some continuous input device, such as papertape, tape, and the console
output be a hard copy device such as a line printer. This was to make
it possible to create "jobs" offline and then run them unattended.
CP/M's SUBMIT command is the counterpart of this function in
floppy disk land, though it seems to have lost a little in
translation.
What all this buys us as CP/M users is this: If you can figure
out how to make a piece of hardware accept or produce data, one byte
at a time, then it can be assigned to one or more of these logical
devices. You are then able to treat it in application software as you
would any of the standard character I/O devices such as a CRT or
keyboard.
As for the disk I/O, things are not quite as elegant but are
standarized so as not to ruin CP/M's implementability. It looks as
though Digital Research asked themselves, "What does all floppy disk
hardware need from the processor to perform disk I/O?" Again the
alteration guide is a better place than here to answer that question.
However, I'd like to discuss something that was discovered during the
implementation of CP/M on my hardware.
By the way of background, the disk hardware on my computer is
totally bizarre. It uses a 256 byte, hard sectors. 16 of them on a
track, and writes the bytes bitwise, backwards with respect to
literally everyone else. I did everything short of making a pact with
Methistopheles (the devil's agent. Ed.) in order to make my equipment
look like IBM compatible hardware. In experimenting with various
techniques, both my own and from suggestions of others, I finally
implemented a full-track (26 IBM sectors) buffering concept which
works very well for me. I can provide listings of this if anyone
cares to see it.
The important thing that I discovered was that most
implementations of the BIOS do not have provisions for read after
write, or similar way of verifing that the data was written correctly.
Obviously, with the low error rate floppy disks supposedly have
(something 1 in 6 million bytes) the overhead of doing a verify after
write may not be justified. Since I was writing software which, if a
bit were dropped, could unpleasantly affect all future development
(including modifications to CP/M itself) I felt that I could not
afford even that small chance of error or at least not knowing about
the error if it should occur. But here's the catch: In order to
verify one sector after writing you have absolutely no choice but to
wait for the disk to bring it around again. This means that in order
to write one whole track this way, it will take 26 revolutions of the
disk! At a sixth of a second per revolution (on 8 inch floppy drives)
that amounts to 4 1/3 seconds which is obviously unacceptable and
totally unjustifiable. Using a full track buffer, I was able to solve
that problem very neatly. It took one revolution to write the data
and one revolution to verify that it was written correctly. This is
approximately 300 milliseconds or a savings of over 400 percent.
The following is a detailed explanation of disk space allocation
and what the BDOS does with the FCB during file I/O.
Disk Space Allocation
=====================
First number all of the sectors on the disk from 1 to 2002
starting at sector 1 track zero. I'll refer to this number as the
"INTEGER SECTOR NUMBER" or ISECTOR. CP/M reserves the first two
tracks (ISECTOR 1-52) for holding image of the system as well as the
bootstrap program (see the Alteration Guide section of the CP/M
documentation). Thus there are 1950 sectors available on a single
density dsk starting at ISECTOR 53.
For reasons which should become apparent, space is not allocated
on a sector-by-sector basis. Instead, space is allocated in 8 sector
blocks (I'm told that the double density version allocates in 16
sector blocks). That means there are 1950/8 or 243 (remainder of 6)
allocatable blocks of disk space. Blocks zero and 1 are pre-allocated
(when a disk is logged on) for the directory and the 6 sectors in the
partial block are not used. Thus there are 241 blocks (241*1024
bytes) of disk space available on the disk for data, starting at block
2.
In order to keep track of which blocks have been allocated and
which have not, the BDOS maintains an "allocation VECTOR" for each
logged-on disk. This data structure is an array of 256 bits (packed
into 32 bytes), where each bit is associated by position with the
corresponding block of disk space. For example the first two bits in
the array are always turned on indicating that the first two blocks
are allocated to the directory. The allocation vector is created when
a disk is selected for the first time (logged on) after re-booting
(booting), by examining the DM (Disk Map) field of all non-empty
(ET=0) FCBs (File Control Blocks) in the directory. Since the DM
field of an extent contains the numbers of all the blocks belonging
(allocated) to the extent, the collection of all DM fields in the
directory describe all space that is currently used on the disk. It
is a relatively simple task to look at a byte in a DM field and turn
on its associated bit in the allocation vector, which is exactly what
the BDOS does.
After the allocation vector is created, it is updated whenever a
new block is allocated, as are the DM byes for the extent. It should
be noted, I think that this technique for space management does away
with specific file and free space linking as well as having to
explicitly store a bit map on the disk.
It is an easy task to translate block numbers into their
corresponding physical track and sector numbers on the disk. By
including in the process, a relative record number (from the beginning
of an extent) it is not much more work to map the logical records of a
file onto exact physical disk locations. This is, I believe, the
virtue of CP/M's file structure.
11.5 Disk Mapping Process
There are three steps to the mapping process: let:
ISECTOR = integer sector number
TRACK = track containing sector
LSECTOR = logical sector (before staggering)
PSECTOR = physical sector (after staggering)
BLOCK = block containing logical record
1. The first step is to calculate the integer sector number.
This is done using:
ISECTOR = (BLOCK*8) + (NR MOD 8) +52
We multiply BLOCK by 8 because there are 8 sectors per
block. NR MOD 8 produces the displacement into the block,
and 52 must be added because block zero begins at ISECTOR 52
(remember that the System image is stored in the first 52
disk sectors). Note: if you're unfamiliar with the term
"MOD", it means "Use the remainder produced by the division".
E.G.:
25 MOD 5 = 0 (5*5=25 remainder 0)
25 MOD 7 = 4 (3*7=21 remainder 4)
25 MOD 27 = 25 (27*0=0 remainder 25)
2. Next we calculate the physical track and logical sector.
TRACK = ISECTOR/26
LSECTOR = ISECTOR MOD 26
(26 because there are 26 sectors on a track.)
3. Finally we map the logical sector using the routine "MAP" at
the beginning of the BDOS:
PSECTOR = MAPTABLE(LSECTOR)
There is an algorithm which will produce "MAPTABLE":
DIMENSION MAPTABLE(26)
MAP(1)=1
FOR N=2 TO 26
IF N > 13 THEN J=1
ELSE J=0
MAP(N)=((MAP(N-1)+6 MOD 26) +J
NEXT N
END
What this really does is stagger the logical sectors around
the track so that they are actually 5 physical sectors apart.
This allows CP/M five sectors or about 35 milliseconds of
processing time between disk accesses in order to keep up
with disk latency.
11.6 FILE CONTROL BLOCK USAGE DURING I/O
In general the following explains what happens when a read or
write operation is requested by an application. There are a series of
exceptions which the BDOS checks for, including "READ PAST EOF" and
"DISK FULL".. Foor the sake of this discussion let us assume that a
write operation is being requested. Reading is very similar.
The BDOS will first increment the NR field and determine whether
or not the next record is within a previously allocated block.
NR := NR+1
IF DM(NR/8) = 0
THEN BLOCK NOT ALLOCATED
ELSE BLOCK IS ALLOCATED
"NR/8" determines which of the DM bytes (logical blocks) contains the
logical record in question. If a physical block needs to be
allocated, the BDOS will search through the allocation vector to find
an unallocated block (which can be anywhere on the disk). If none
exists (i.e. no zero bits in map) then a "DISK FULL" condition is
returned to the application (calling program). If a block is found
its bit is turned on and its number is deposited at location DM(NR/8).
After this, the absolute sector is determined and is converted
into the physical track and sector numbers. Then the data is written.
The read operation is similar except that no space allocation is
performed. Instead, if the record being requested to read falls
within an unallocated block a "READ PAST EOF" condition is returned to
the application (Calling program).
For both the read and write operations, if the NR field goes to
128 decimal when incremented, the BDOS will close the current extent,
increment the EX field and attempt to open the next extent of the
file. If the operation was a write and the next extent doesn't exist,
the BDOS will create it and then open it. If the operation was a read
and the next extent doesn't exist, a "READ PAST EOF" condition is
generated as it will in the case that either NR is greater that 128 or
NR is greater than RC is true.
Here is an example:
LET:
NR = 12
RC = 17
DM(0) = 50
DM(1) = 51
DM(2)-DM(15) = ANYTHING.
1. NR := NR+1 (NR=13)
2. LBLOCK = NR/8
= 12/8
= 1
3. BLOCK = DM(LBLOCK)
= DM(1)
= 51
4. ISECTOR = (BLOCK*8)+(NR MOD 8) + 52
= (51*8)+(13 MOD 8) + 52
= 408+4+52
= 464
5. TRACK = ISECTOR/26
= 464/26
= 17
6. LSECTOR = ISECTOR MOD 26
= 464 MOD 26
= 22
7. PSECTOR = MAPTABLE(LSECTOR)
= MAPTABLE(22)
= 24
Thus the sector being accessed is 24 on track 17.
Miscellaneous Stuff
To conclude, there are some interesting tid-bits which the reader
may (or may not for that matter) find useful.
The SUBMIT processor in the CCP (Console Command Processor) uses
a very interesting programming technique which I found worthwhile
understanding. Recall that the transient program "SUBMIT" uses as its
input a text file of CP/M commands and produces a file called $$$.SUB
which the CCP will use as a command file. For example consider the
following submit file:
A:PIP B:PROG.ASM=B:PO.SRC,B:P1.SRC(CR)(LF)
A:ASM PROG.BBB(CR)(LF)
A:LOAD B:PROG(CR)(LF)
A:PIP LST:=B:PROG,PRN(T8P50)(CR)(LF)
The Submit program turns the file into a series of 128 byte
records arranged such that the first line of the original file is the
last record of the new file, the second line the second to the last
record and so on. Each record has the following form:
byte 1: length
bytes 2-length+1: command string
bytes length+2-128: undefined
The above file would look like this when converted to $$$.SUB
(the numbers in decimal and brackets are included here just for
clarity):
record 1: (28)A:PIP LST:=B:PROG,PRN(T8P50)
record 2: (13)A:LOAD B:PROG
record 3: (14)A:ASM PROG.BBB
record 4: (33)A:PIP B:PROG.ASM=B:PO.SRC,B:P1.SRC
Why, you ask, is $$$.SUB backwards? Well that's part of the
trick. Remember that the RC field in the FCB is an end of extent
pointer. If the $$$.SUB file is on the disk the CCP will open it and
set the NR field of the FCB to RC-1 and read the file. What this does
is read in the last record of the file (as determined by the RC
field). After the read the BDOS will decrement the RC field and close
the file, which will cause the FCB and specifically the RC field to be
updated. The CCP will use the data just read as a command as if it
had been typed in from the console. The next time around, the CCP
will do the same thing except that the RC field is now pointing at the
record whose number is one less than that of the previous operation.
In other words, the RC field is used as an implicit record pointer.
Very neat and it works too!
Sometimes it is desirable to bypass the BDOS and communicated
directly with certain BIOS functions. For example MICROSOFT's BASIC
interpreter does not use the BDOS character I/O functions as it does
its own line editing and the USCD Pascal system completely overlays
the BDOS. There is a technique for accessing the BIOS that is general
enough so as not to be considered a kludge. An application can always
find the page boundary on which the BIOS begins by examining the high
order address byte of the warm boot entry point. Using that as the
high order byte of the address the low order byte is set to an offset
into that pags determined by:
OFFSET=FUNCTION * 3
as each entry is three bytes. For example here is a short routine
which causes data to be written to the list device:
LOFF EQU 0FH ; BIOS+LOFF=LIST ENTRY
LIST:
PUSH H ; SAVE HL
LHLD 1 ; GET ADDRESS OF WARM BOOT
MVI L,LOFF ; SET LOW ORDER BYTE TO LIST OFFSET
XTHL ; RESTORE HL, LIST ADDRESS ON STACK
RET ; EXECUTE LIST ROUTINE IN BIOS
This technique works as long as the BIOS begins on a page boundary.
The more general technique would be:
LOFF EQU 0FH-3 ; OFFSET FROM WARM BOOT ENTRY
LIST:
PUSH H ; SAVE HL
LHLD 1 ; GET ADDRESS OF WARM BOOT
PUSH D ; SAVE DE
LXI D,LOFF
DAD D ; GET TO LIST ENTRY POINT
POP D ; RESTORE DE
XTHL ; RESTORE HL
RET ; EXECUTE LIST ROUTINE
There is one more thing and then I'll quit. If you remember from
above, I mentioned that while poking around inside a screen-oriented
text editor, I found that it modified the error address fields at the
beginning of the BDOS. It also does another clever thing. In the
editor there is a command to save the rest of the file, exit the
editor and automatically process it with an entirely different
program, such as an assembler or text formatter. There is an
interesting technique here which could be generally useful. What
happens is this: First, modify the address of the console input
routine in the BIOS jump table to cause a routine inside the
application program to supply data to the CCP. This is done as
follows:
LHLD 1 ; GET THE BIOS PAGE ADDRESS IN H
MVI L,CI+1 ; HL IS THE ADDRESS OF THE ADDRESS
; OF CONSOLE INPUT ROUTINE
MOV E,M ; GET THE DEVICE ADDRESS IN DE
INX H
MOV D,M
XCHG
SHLD SAVE ; SAVE IT OR LATER
LXI D,ALT ; DE IS ADDRESS OF ALTERNATE ROUTINE
MOV M,D ; POKE JUMP ADDRESS IN BIOS JUMP TABLE
DCX H
MOV M,E
JMP 0 ; AND RE-BOOT
SAVE: DS 2 ; LOCATION USED TO SAVE
; CONSOLE INPUT DEVICE ADDRESS
After doing this, everytime the CCP requests a character from
what it thinks is the console input device, it will be handed a
character from inside the original application program. Just before
handing the CCP a carriage return, the application will restore the
original address of the console input routine:
LHLD SAVE ; GET ORIGINAL DEVICE ADDRESS
XCHG ; PUT IN DE
LHLD 1 ; GET ADDRESS OF ADDRESS FIELD OF
; CONSOLE INPUT ENTRY
MVI L,CI+1
MOV M,D ; RESTORE ORIGINAL ADDRESS
INX H
MOV M,E
; ... AND CONTINUE
The routine that does character handling is essentially this:
LHLD POINTER ; GET ADDRESS OF NEXT CHARACTER
MOV A,M ; GET NEXT CHARACTER
INX H ; ADVANCE POINTER
SHLD POINTER ; SAVE POINTER
CPI CR ; END OF DATA?
RNZ ; IF NOT THEN JUST RETURN IT
; ELSE RESTORE CONSOLE
; INPUT ADDRESSES
This works for two reasons: obviously the BIOS jump table can be
considered data as well as code (hail to John Von Neumann) and since
the original program will remain intact until the next program is
actually loaded on top of it, the routine simulating the console will
function normally. This technique suggests a viable method for
chaining a series of programs together without having to specifically
build a submit file for each chain.
CP/M QUICK REFERENCE; compiled by Steve Stolen
===========================================================================
I Func. I Func. I Function I Entry Parameter I Return Value I
I Code I Code I (C) I (DE) (E) I (A) I
I (Dec) I (Hex) I I I I
==========================================================================
I 1 I 1 I Read Console I - I ASCII Char. I
---------------------------------------------------------------------
I 2 I 2 I Write Console I ASCII Char. I - I
----------------------------------------------------------------------------
I 3 I 3 I Read Console I - I ASCII Char. I
----------------------------------------------------------------------------
I 4 I 4 I Write Punch I ASCII Char. I - I
----------------------------------------------------------------------------
I 5 I 5 I Write List I ASCII Char. I - I
---------------------------------------------------------------------------
I 6 I 6 I - I - I - I
----------------------------------------------------------------------------
I 7 I 7 I Get I/O Status I - I I/O Status Byte I
----------------------------------------------------------------------------
I 8 I 8 I Set I/O Status I I/O Status Byte I - I
----------------------------------------------------------------------------
I 9 I 9 I Print Buffer I Buffer Address I - I
----------------------------------------------------------------------------
I 10 I A I Read Buffer I Buffer Address I - I
----------------------------------------------------------------------------
I 11 I B I Check Console Rdy I - I - I
----------------------------------------------------------------------------
I 12 I C I Lift Head I - I - I
----------------------------------------------------------------------------
I 13 I D I Initialize BDOS I - I - I
----------------------------------------------------------------------------
I 14 I E I Log in Drive I 0...N I - I
----------------------------------------------------------------------------
I 15 I F I Open File I FCB I 255=not present I
----------------------------------------------------------------------------
I 16 I 10 I Close File I FCB I 255=not present I
--------------------------------------------------------------------------
I 17 I 11 I File Search I FCB I 255=no match I
----------------------------------------------------------------------------
I 18 I 12 I File Search Next I FCB I Adr.next entry I
----------------------------------------------------------------------------
I 19 I 13 I Delete File I FCB I - I
----------------------------------------------------------------------------
I 20 I 14 I Read Next Record I FCB + I 0=OK 1=EOF 2=ND I
----------------------------------------------------------------------------
I 21 I 15 I Write Next Record I FCB + I See Notes I
----------------------------------------------------------------------------
I 22 I 16 I Make File I FCB I 255=DIR Full I
----------------------------------------------------------------------------
I 25 I 19 I Drive Number ? I - I Drive Number I
----------------------------------------------------------------------------
I 26 I 1A I Set DMA Address I DMA Address I - I
----------------------------------------------------------------------------
============================================================================
HOW TO PATCH CP/M TO BACKSPACE ON RUBOUT
----------------------------------------
The patches listed below will allow CP/M to echo the delete function
as a backspace. Since one of the patches takes advantage of the jump
relative capability of the Z80, THE PATCHES WILL NOT WORK AS DESCRIBED
ON AN 8080 SYSTEM. However with the info given it will be simple for
an 8080 user to make the appropriate patch that will function.
CAUTION: I am currently using this patch and to date it has
proven to work well. It does not work within the 'Insert' mode of the
Editor, but it does work at command level. I'll leave it to someone
else to figure out why. Anyway use it at your own risk.
PATCH ONE: The console input routine (12F3H) gets a char and, after
testing for carriage return, tests for 'delete'. If the test for 7FH
is successful, CP/M gets the counter from Reg B and tests it for zero.
If true (i.e. we're at start of buffer) a jump is executed back to the
console input routine.
If we are not at the start of the buffer, CP/M loads the previous char
(pointed to by HL) into the Acc and jumps to the CONOUT rout. Since
we want to echo a backspace instead of the previous char, replace the
MOV A,M with MVI A,08 (or whichever character it is that your terminal
treats as a backspace). The extra memory location required by the MVI
instruction is recovered by replacing the JZ XX73 with JR Z,E9. More
precisely:
LOC IN TPA AFTER ORIGINAL MACHINE CODE REPLACED BY
SYSGEN
---------------- --------------------- -----------
1308 CA XX 73 7E 28 E9 3E 08
PATCH TWO: CP/M will convert any character less than 20 hex to its
Ascii equivalent preceeded by '^'. All except CR, LF, and that is.
To enable our backspace character to be echoed unchanged, we insert a
patch at the start of the conversion routine to test for '08' and exit
if true. Again in the TPA:
LOC IN TPA AFTER ORIGINAL MACHINE CODE REPLACED BY
SYSGEN
---------------- --------------------- -----------
12B0 F5 3E 5E C3 <PATCH>
AND:
<PATCH, LOC N/A FE 08 CA 3A
YOUR CHOICE> XX F5 3E 5E
C3 XX 33
NOTE: The 'XX's above must be replaced by the appropriate page
boundaries if your operating CP/M system. Also since we are only
echoing one backspace, the control characters which CP/M echoed as two
characters will not be completely removed (we leave the '^'). Again I
will leave it to you to come up with a more glamorous fix.
--- EOF ---