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
/
JSAGE
/
ZSUS
/
TCJ
/
TCJ36BMM.WS
< prev
next >
Wrap
Text File
|
2000-06-30
|
26KB
|
712 lines
.h1 = main heading
.h2 = subheading
italics are delimited by matching underscores
Advanced CP/M
Environmental Programming
Bridger Mitchell
The Computer Journal, Issue 36
Reproduced with permission
of author and publisher
.h1 BackGrounder ii Update
BackGrounder ii is like no other CP/M program -- it simply feels
different. A touch of the "suspend" key and you pop into the
background command processor, a touch of a user-defined macro key and
you can switch to a second program, literally in mid-sentence. The
built-in calculator, notepad, screen-dump, and cut-and-paste function
turn out to be extremely handy desk accessories, especially because
results on one screen can be exported to another task. But the magic
of it all -- black magic, perhaps -- is the feeling that comes over
you when you first experience the screen flashing back, cursor in
place, with _no_ trace of having been away!
BGii and the Z-System stand as twin pinnacles of advanced CP/M
operating systems. At a conceptual level, they are orthogonal. By
providing memory buffers for the command processor and applications
and supporting conditional execution, ZCPR 3.4 allows tasks to
_communicate sequentially_. By making the BDOS and command-processor
recursive, BackGrounder ii creates two-way _communication between
simultaneous tasks_, under user control. When combined -- BGii
running in a ZCPR 3.4 system -- they elevate 8-bit computing into another
dimension. The results are awesome.
Bringing BGii fully up-to-date to support the latest ZCPR version 3.4
has been a largely enjoyable task. I had put it off more than once,
wanting to finish up DosDisk and then Z3PLUS.
When I finally returned to the BGii code I was pleasantly surprised to
uncover several new coding shortcuts. They enabled me to squeeze in
almost all of the "Z34" features and add some new conveniences,
including enabling the user to rename the built-in BGii commands.
Expert testing by Cam Cotrill and Jay Sage greatly firmed up several
soft spots. It's now the production version and licensed users can
order an update at low cost.
.h1 Environmental Programming
A customer of long-standing called the other night, as I was drafting this
column. He enthused about BackGrounder ii, but then noted that "it
sometimes finds bugs in _other_ programs!"
Alas, bugs are always with us, even when we think we've got our own
code pretty solid! This column is going to be about writing code that
is respectful of the environment in which it is running.èThe sage (Sage?) advice collected here, and culled from the
programming experience of many old hands, surely won't eliminate bugs.
But it will greatly increase the chances of your programs living more
harmoniously with a wide variety of CP/M systems.
.h2 Make a good start
The command processor starts your program by _calling_ it. This means
that you can speed up the flow of jobs by _returning_ to the CCP when
your program terminates, instead of causing a warm boot that reloads
the CCP. To do this, you must _save the stack pointer_ and stay clear
of the CCP in the 2K of memory just below the BDOS.
_Use a local stack_ for all but the simplest programs. The command
processor's stack may not be deep enough for your functions, BIOS
calls, and interrupts. And that stack could be in the TPA, part of
the CCP that may be overwritten by your program or data.
.h2 Know the Territory
A shockingly large number of programs assume that they will always be
run only in the environment for which they were written. Drop them
into a different world and they almost always injure their host.
So, please, join the environmentalists and take the responsible
programmer's oath: _Do No Harm!_ Survey the territory before plunging
ahead, and pose these questions:
Is our host a Z80? An HD64180? A Z280? That determines which opcodes can
we safely use.
Is our host running CP/M Plus? or ZSDOS? What system calls are available?
Is DateStamper running?
Is one of the drives set to MS-DOS format under DosDisk? If so, we
must not make assumptions about internal data in the file control
blocks on that drive or about the structure of the disk directory.
Is the host running a Z-System? With an extended environment? If so,
we should allow for possible non-standard sized BDOS and CCP modules
and get their addresses from the environment. If it is _not_ a
Z-System, we must avoid any references to Z-environment parameters;
if we are a Z-tool, put out a short message of requirements and quit.
Finally, if we should need to know, can we determine what BIOS and type of
machine our host is?
Figure 1 is a routine called TERRITORY that does these checks.
It should be called at the very beginning of a program. If the host
system has a Z-System command processor (ZCPR 3.3 or later, or
BackGrounder ii) the program will begin with the HL register
containing the address of the Z-System external environment.
TERRITORY first checks for a Z80-compatible processor, and then usesèobscure differences in register operations to identify HD64180 and
Z280 processors. The system addresses (BIOS, BDOS, and CCP) are
determined from a Z-System extended external environment, if there is
one, so that non-standard BDOS and CCP modules can be used correctly.
The BIOS check demonstrates how to detect an NZ-COM system and find
the address of the original CBIOS. Several systems have bios-specific
references to such things as function-key tables, foreign disk
parameter blocks, and extended BIOS functions that cannot be located
from the address at 0001h when NZ-COM is running.
You can use the flags and addresses established by TERRITORY for your
own requirements. You might also want to make a version of it into a
simple diagnostic tool that prints out messages identifying exactly
what the host system consists of.
.h2 Identify yourself
Unless yours should be a silent program, announce yourself to the
user with an appropriate message that includes a version number.
All programs change, get updated, and gain features. You and other
users need to be able to identify which model they're driving.
Most Z-System tools use a standard format, which is well worth
adopting for other programs:
A>PROGNAME Vers. 1.5 -- terse functional description
If you are the silent type, include sufficient version identification
in the data area so that a debugger can be used to inspect the
program. Alternatively, include the information in a help screen.
Z-System tools use the standard "double-slash" command line to request
help, another worthwhile convention:
A>PROGNAME //
.h2 Protect the Environment
_Save the current drive and user number_, so you can restore them on exit.
Explicitly _allocate memory_ and check to prevent overflowing the
available transient program area. The TPA is always the memory from
100h to the value that is stored at 0006, less 1 byte.
If there are no RSX's in memory, that value is the entry address of
the BDOS and is the target of the jump instruction at 0005. This is
the most common case; in CP/M 2.2 the CCP will occupy 6 + 800 hex
bytes below that. But if an RSX has been loaded, its address
will be at 0006. In that case the CCP will already be protected, and
you can use all of the TPA and still return to the CCP.
So, if no RSX is loaded, if you intend to return to the CCP, and if youèare not running under CP/M Plus, allow 2K of space below the BDOS.
Figure 2 gives a routine that makes this calculation. It calculates
the largest usable memory that will still preserve the CCP and returns
the address of the first byte beyond that.
Applications have the right to have their register values treated
systematically when they call on the operating system for services.
CP/M is an 8080-based operating system, and from the beginning it put
programmers on notice that it would not preserve the user's registers.
That was logical, as the OS needed most of them for returning values.
The introduction of the Z80 and subsequent 8080-compatible CPUs led to
more compact and more efficient BIOSes. Unfortunately, more than one
BIOS writer began using the additional _Z80 registers_ without
preserving their values for the user. The consequences have been
erratic havoc -- programs test out flawlessly on a variety of systems,
then fail to start, or die mysteriously on another machine.
The environmentally-conscious rule here is: if your code will become
part of the operating system -- the BDOS, BIOS or an RSX extension --
save and restore all Z80 registers you use. Why? Simply because it's
a far greater burden on an application to preserve IX, IY, AF', BC',
DE', and HL' in order to run on an arbitrary system, than it is for
the system programmer to protect exactly those registers he needs to
use.
The extreme case of environmental wantonness is a rom-based-based BIOS
for the early Osborne Executive, which used alternate Z80 registers
for an interrupt service routine, without preserving them! A moment's
thought should persuade you that there is _no way_ an application
could _ever_ use those registers and run on that machine. Was the
system designer so naive as to think that only 8080-code would ever be
run on an Executive?
.h2 Expect the Worst
_Test for all disk errors_. Proceeding after one error (a full disk,
a non-existent file, ...) can wreak disaster.
_Have a recovery strategy_. Tell the user enough about the problem
that he can alter the environment and try again. Give him a
choice (insert a disk, restart the program, exit, ...).
.h2 Limit the Damage
Before writing a file, check the disk space remaining. If there isn't
enough room, give the user the chance to delete something, or to insert a
new disk.
If a write error occurs, try to close the file to save as much data as
possible. Offer the user a second chance, by changing disks or
drives. Clean up file fragments after recovering.è
Before printing, test to see if the printer is ready. If it isn't,
ask the user to make it ready before retesting. Otherwise, if you
start sending characters, the computer will hang and you can't inform
the user what's wrong. A short routine to do this, derived from
BackGrounder ii, is shown in Figure 3. It's important to send a test
character (a carriage return) in order to actually test the _printer_
itself, because a serial printer channel will normally have a UART
with a one-character buffer. If the UART's buffer is empty, it will
report that _it_ is ready to receive a character, even if the printer
isn't.
.h2 Don't take shortcuts
The TERRITORY routine doesn't check for Z-Systems earlier than ZCPR
version 3.3 (which supplies the external environment address in HL
when it calls each program). It can be extended to do so by searching
memory for the "Z3ENV" string and verifying that the self-reference
address indeed points to the environment area being examined.
Note that if the string is found, but the address test fails, the
search must be continued; it's quite possible to have more than one
"Z3ENV" string in memory. Jay Sage tells me there is a group of
programs that, regrettably, take a shortcut and stop searching on the
first match. They fail to run on his system because it includes,
quite appropriately, a directory named "Z3ENV".
The new Z3PLUS and NZ-COM systems have exposed shoddy programming
practices, bugs that have been hibernating in widely used utilities.
A common one results from the faulty assumption that the Z-System
external environment address began on a memory page (xx00 hex).
When this happens to be the case, then:
ld l,offset
is a shorter route pointing to an environment parameter than:
ld de,offset
add hl,de
But when it's not, the path leads over the cliff.
.h2 Clean up
_Use a common exit_ point. This makes it easier to ensure that
nothing is overlooked as your program grows to include new branches.
_Close open files_ and _delete temporaries_.
_Restore the default drive and user_.
èIf you've used full-screen terminal features, _leave a clean screen
below the cursor_. For some applications you may want to clear the
entire screen. In other cases, having the final lines of data remain
allows the user to make use of the information when she or he enters
the next command; in that case, put the cursor on the bottom line and
send a newline.
Finally, if you have not overwritten the CCP, restore the stack and
return. Otherwise warmboot.
I certainly don't follow these guidelines slavishly, though I do
pay them regard before releasing any software for wide testing.
My own temporary programs, run in a known test environment, are
often rude, slap-dash pasteups to get the job of the moment done
rapidly. But I guard against giving them out to others. It's no
favor to pass on code that may explode a friend's system at some
unsuspecting moment. Which leads me to recall ...
.h1 A Tale of Too-hasty Design
Some months ago a well-known programmer, author of CP/M several
BIOSes, gave me a copy of the BIOS to a particular new computer.
I was developing customized operating-system code for this
box, and had a similar machine for the debugging and testing
cycle. I had re-assembled the BIOS, made several rounds of
modifications that were converging to a stable new system running
on my box when, poof, I exited from a program and the system
went to lunch. It wouldn't reboot, even from power up.
A systems programmer learns (from bitter experiences!) to sit
quietly, write down everything he can remember, and think long
and hard before he touchs anything besides a pencil. Some clues
to the crash may remain behind, on disk or in memory (although in
this case memory was fully reset). In this case, the A: drive
had been a ram disk with uninterruptible power, so I assumed that
its system tracks had somehow been damaged.
I decided to make a systematic tour of the A: disk drive by booting up
from a floppy system, and looking at different tracks with DU. (Yes,
I try always to have a couple of bootable systems stored away on
floppies for that black day that a hard-disk or ramdisk goes bad. You
should too.) Sure enough, I found what appeared to be file data in
the directory tracks. More investigation disclosed that the following
tracks, which should have been the directory, had also been corrupted.
I took a deep breath, made a mental note to write down what I could
still remember of what I had typed in during the last two hours of
iterations since backing up the experimental system code. I continued
checking the disk. Suddenly, several tracks later, the disk appeared normal.
What could cause such systematic damage? Hypotheses poured forth,
only to be discarded. Finally, I saw the glimmer of a pattern.èThe start of the ram disk -- the start of banked memory --contained
data related to the data at the very end of the disk. Wraparound!
The BIOS had literally gone off the deep end of the ram disk and
started writing on the front, clobbering the system, the
directory, and the first files after that. As soon as a warmboot
was attempted, the system loaded the corrupted system track and
was dead.
A hot theory. But where was the bug? I consulted with the
author, and then he remembered -- he'd given me the _large_
ramdisk version of the BIOS. With less ram installed on my
box the addressing had wrapped around.
I wasn't pleased to learn he'd overlooked putting in a _runtime_
check for the amount of memory the system possessed. A simple
test to make, yet because it was omitted, I now had several hours
of reconstructing and testing files ahead of me.
This bug was in hibernation, waiting to byte just when a large
disk filled up. Only extreme testing is likely to have disclosed
it before it zapped a directory on a customer's machine. We were
lucky it happened when it did. Yet it could have been prevented,
by systematic design. Environmentally conscious programming
would have done it.
.h1 Identifying the BIOS
How can a program determine who its host is? What hardware is
available? Digital Research (DRI) had no BDOS function to return a
version number in CP/M 1.4 and never did introduce one for identifying
the BIOS. I have no idea why DRI failed to anticipate this question;
a BIOS call to return version information seems now the obvious way to
do things. And MSDOS is no better.
Anyway, we are stuck with CP/M's warts; there is no _portable_ BIOS
call that will identify an Ampro from a Kaypro from an S-100 box.
Even if _a particular_ manufacturer had the vision to include this
entry point, it's catch-22. We can't safely use the call until we know
that it's available!
The best approach is to identify the BIOS by reading memory bytes in
the BIOS. Every BIOS should include a unique signature and version
information. The signature can be an ascii string, such as "PPS" (in
the Advent/Plu*Perfect TurboRom BIOS) or "XBIOS" (in the XBIOS for
SB180's), at a known offset from the start of the BIOS. Once the
program has identified the type of BIOS, it knows it can make an
extended BIOS call, if one has been provided, to obtain additional
information. The XBIOS system, for example, provides information on
the available hardware devices and their corresponding ascii names in
the system.
The TERRITORY routine includes a ck_bios routine to identify Kayproèsystems with a TurboRom BIOS and systems running NZ-COM. Other checks
can be added to identify other particular systems.
Unfortunately, a number of BIOS's were written by people who
apparently never thought others might write software for their very
computer! In these cases, the safest approach is to tell the user in
a message that the type of BIOS cannot be determined, and ask him to
confirm or enter the model manually before proceeding.
; Figure 1. Determine characteristics of CP/M host's environment
; _______________________________________________________________
bdos equ 5
; Call this routine immediately.
; Enter with HL = value from command processor.
;
TERRITORY:
;
; Test for z80-compatible cpu.
;
sub a ; sets even parity in 8080
jp po,ck_180
ld c,9 ; announce Z80 requirement
ld de,notz80msg
call bdos
rst 0 ; ..and exit to warmboot
;
; Test for HD64180/Z180
;
ck_180:
ld bc,101h ; prepare to multipy B=1 x C=1
db 0EDh,04Ch ; MLT BC opcode
dec b ; if Z80, B will be unchanged
jr z,ck_280 ;
ld a,c ; 180 leaves 16-bit result (1) in BC
ld (z180flag),a
;
; Test for Z280
ck_280: ld a,10 ; Z280 doesn't use refresh register
ld r,a ; load it
ld c,a ; save it
ld b,a ; cause some refreshes
loop: djnz loop
ld a,r ; if value hasn't changed
cp c
jr nz,ck_rest
ld (z280flag),a ; ..it's a 280
;èck_rest:
push hl ; save possible env address from ccp
call ck_dos ; check type of BDOS
pop hl
call ck_z3 ; check for Z-System
call ck_dosdisk ; check for DosDisk
call ck_bios ; check type of BIOS
call ck_bg ; check for BackGrounder ii
ret
;
;
; Check for BDOS version
;
ck_dos: ld c,12 ; get CP/M version number
ld e,'D' ; with DateStamper id request
call bdos
cp 30h
jr c,ck_ds
ld (cpm3flag),a ; set flag if CP/M 3
ret
;
; Check for DateStamper
;
ck_ds:
cp 22h
jr nz,ck_xdos ; .. not CP/M 2.2
ld a,h
cp 'D'
jr nz,ck_xdos ; ..no DateStamper
ld (dsflag),a ; set flag
ld (dsclock),de ; and save clock pointer
;
; Check extended dos version
;
ck_xdos:ld c,48 ; use extended version number call
call bdos
ld (dosvers),hl ; save version number and type
ret
;
; Check for Z-System.
; Enter with HL = value from command processor. If ZCPR 3.3
; or BackGrounder ii, HL -> external environment
;
;
ck_z3: push hl ; save possible ENV address
inc hl ; Offset to 'Z3ENV'
inc hl
inc hl
ld b,Z3ENVLEN
ld de,z3envsigè call match
pop de ; recover de = ENV address
jr nz,set_std
ld hl,1Bh ; Offset to self-reference address
add hl,de
ld a,(hl) ; Check low byte
cp e
jr nz,set_std
inc hl
ld a,(hl) ; Check high byte
cp d
jr nz,set_std
;
ld (zsysflag),a ; set flag
ld (z3env),de ; save environment address
ld hl,08h ; get env. type
add hl,de
ld a,(hl)
and 80h ; test for extended type (>= 80h)
ld (extenvflag),a ; and save result
jr z,set_std
;
; Set system addresses for a Z-System with an extended environment
ld hl,45h ; -> bios address in environment
call addderef
ld (biosbase),hl
ld hl,42h
call addderef
ld (bdosbase),hl
ld hl,3Fh
call addderef
ld (ccpbase),hl
ret
;
; Set system addresses for a standard system.
;
set_std:
ld hl,(0001h)
ld l,0
ld (biosbase),hl
ld de,-0E00h
add hl,de
ld (bdosbase),hl
ld de,-800h
add hl,de
ld (ccpbase),hl
ld a,(cpm3flag) ; if not CP/M Plus
or a,a ; ..all done
ret z
;è ld c,49 ; for CP/M Plus, w/o extended environment...
ld de,getscbpb ; get system control block address
call bdos
ld l,98h ; offset to address of resident bdos
call deref ; dereference pointer
ld l,0
ld (bdosbase),hl
ld hl,100h
ld (ccpbase),hl
ret
;
addderef:
add hl,de ; offset pointer by DE
deref: ld a,(hl) ; dereference HL pointer
inc hl
ld h,(hl)
ld l,a
ret
;
match: ld a,(de) ; match B bytes at DE, HL
cp (hl)
inc hl
inc de
ret nz
djnz match
ret
ck_dosdisk:
ld c,113 ; get DosDisk id
call bdos
cp 0FDh
ret nz ; ..if not DosDisk, quit
ld (dosdiskflag),hl; set flag (L) and drive with MS-DOS format (H)
ret
ck_bios:
ld hl,(0001h) ; -> normal CBIOS warmboot address
ld l,90 ; -> NZ-COM id in NZ-COM pseudobios
ld de,nzname ; if NZ-COM id is exactly there
ld b,NZNAMELEN
call match
jr nz,ck_turbo
ld hl,(z3env) ; ..get CBIOS (page) addr from
inc hl ; ..z3env+2
inc hl
ld h,(hl) ; ..get page
ld l,0
ld (biosbase),hl ; ..and save correct ptr to CBIOS
;
; Check for Kaypro TurboRom
;èck_turbo:
ld hl,0FFF8h ; -> location of TurboRom signature
ld b,TURBOSIGLEN
ld de,turbosig
call match
ret nz
ld (turboromflag),a; set flag
ret
;
; Check for BackGrounder ii
;
ck_bg:: ld hl,(bdosbase) ; -> location of BGii signature
push hl
ld de,-800h+5Bh ; in BGii CCP
add hl,de
ld b,BGSIGLEN
ld de,bgsig
call match
pop hl
ret nz
ld (bgflag),a ; set flag
ld de,-800h+1 ; -> BGii ccp entry +1
call addderef ; get that address
dec hl ; BGii task flag is 1 byte lower
ld (bgtaskptr),hl ; save ptr to task flag
ret
;
notz80msg:
db 'Not Z80.$'
getscbpb:
db 3Ah ; return address of scb
db 0 ; "get" code
;
; Z-System environment signature
;
z3envsig:
db 'Z3ENV'
z3envlen equ $-z3envsig
;
; NZ-COM's signature in the pseudo-bios, at offset 90.
;
nzname: db 'NZ-COM'
nznamelen equ $ - nzname
;
; BackGrounder ii's signature
bgsig: db 'BGii'
bgsiglen equ $ - bgsig
;
; Kaypro TurboRom signature, at FFF8h
;èturbosig:db 'PPS'
turbosiglen equ $ - turbosig
;
;
; System flags (any NZ value means TRUE)
;
z180flag: db 0 ; HD64180/Z180 cpu
z280flag: db 0 ; Z280 cpu
cpm3flag: db 0 ; CP/M Plus
dsflag: db 0 ; DateStamper
zsysflag: db 0 ; Z-System (ZCPR 3.3 or later)
extenvflag: db 0 ; extended Z-System environment
bgflag: db 0 ; BackGrounder ii
turboromflag: db 0 ; Advent/Plu*Perfect Turborom
;
dosvers: db 0 ; } a pair version number (hex)
dostype: db 0 ; } 'S' = ZSDOS, 'D' = ZDDOS, 0 = ZRDOS
dosdiskflag: db 0 ; } a pair DosDisk
dosdrive: db 0 ; } MS-DOS format drive
bgtaskptr: dw 0 ; BGii internal flag pointer: bit 1 set = upper task
;
; System Addresses
;
z3env: dw 0 ; Z-System external environment
biosbase: dw 0 ; BIOS
bdosbase: dw 0 ; BDOS
ccpbase: dw 0 ; CCP
dsclock: dw 0 ; DateStamper clock
; Figure 2. Find Top of Usable Memory that Preserves the CCP
; ____________________________________________________________
; Return HL = top of usable memory + 1
;
top_of_mem:
ld hl,(0006) ; load protect address
ld a,(cpm3flag) ; if CPM Plus
or a ;
ret nz ; ..return it
push hl ; for CP/M 2.2
ld de,(ccpbase) ; if protect address is below CCP
sbc hl,de
pop hl
ret c ; .. return it
ex de,hl ; else return CCP address
ret
è
; Figure 3. A Printer-Ready Test
; _______________________________
; Return: NZ if printer is ready
;
test_list:
call b_lstat ; if printer is busy
ret z ; ..return
ld c,0dh ; send carriage-return
call b_list ; ..to flush UART buffer
call wait ; allow printer to process it
; and re-test status
b_lstat:ld de,2dh-3 ; call BIOS list-status entry
call bcall
or a,a
ret
;
b_list: ld de,0fh-3 ; call BIOS list entry
bcall: ld hl,(0001)
add hl,de
jp (hl)
wait:
; ... ; any 10-20 mS routine
ret
[This article was originally published in issue 36 of The Computer Journal,
P.O. Box 12, South Plainfield, NJ 07080-0012 and is reproduced with the
permission of the author and the publisher. Further reproduction for non-
commercial purposes is authorized. This copyright notice must be retained.
(c) Copyright 1989, 1991 Socrates Press and respective authors]