The Computer Journal, Issue 49
Z-System Corner
© Jay Sage
Reproduced with permission of author and publisher.
Putting the NZCOM Virtual BIOS to Work
For this issue we are going to look once more at ways to use the NZCOM virtual BIOS. Ten issues back, in TCJ #39, I talked about how one can take advantage of NZCOM's incredible ability to change the BIOS, statically and dynamically, without requiring any source code for the computer's real BIOS. In that column I described how Cam Cotrill, one of the authors of the Z-System DOS (ZDOS), used the NZCOM virtual BIOS to overcome problems experienced with ZDOS on computers whose real BIOS failed to preserve the Z80 index and/or alternate registers across BIOS function calls, as is required for ZDOS. To Cam's code, I added my own enhancement to implement the Z-System environment's drive vector.
I had hoped that those two examples would inspire others to apply this technique to a wide range of problems, but so far nothing has appeared. It's not for lack of a need, however, for I have seen on Z-Nodes quite a few discussions of issues that could be handled quite nicely in this way. Ever the optimist, I will try a few more examples. Two of the examples will essentially be the solutions to problems asked about recently in messages posted on Z-Nodes.
The starting point will be the modified NZCOM BIOS that I discussed in the earlier TCJ column. The main parts of the code are shown in Listing 1. Several interesting features are worth pointing out before we proceed to our new modifications.
The beginning of the code has the material required for the generalized loading concept developed by Bridger Mitchell and described in detail in TCJ issue #33 (if you don't have all these back issues, you would do well to pick them up). The NAME statement embeds an ID into the REL object code. The NZCOM.COM and JETLDR.COM loaders use this ID to recognize the function of a module they have been asked to load so they can figure out its proper load address. The COMMON statements allow any addresses in the Z-System to be installed into the code by the loader at load time. This is the beauty of Bridger's concept: a single binary file can be used in many different systems.
The next section of the code must adhere to a rigidly defined format. First comes the table of jump vectors as required in the CP/M-2.2 specifications. With the exception of the cold and warm boot routines, these jumps would normally go directly to the real BIOS. Since we want to intercept this code and enhance its functions, we vector to other locations in the virtual BIOS code. Note that the jump vector table has room for 13 extra custom BIOS functions that might be implemented in the user's system.
The block of jump vectors is followed by some special data for NZCOM. First there is an ID string that can be used by programs to determine if an NZCOM version of Z-System is currently running. Then there is a data byte with the number of the user area where the command processor file is stored. In an NZCOM system, the command processor is loaded from a file and not from the system tracks of the disk. Finally, there is the space for the file control block for the CCP file. Then comes the replacement warm boot code that loads this file.
This is an area where changes would be made in a multiuser system (a number of such systems are around, including some manufactured by Televideo). Imagine that there are two terminals running on the system and each user wants to run a different configuration of NZCOM. Clearly, they can't both use the same NZCOM.CCP file. Therefore, each user has to be set up with a different name or user area for the CCP file. One place this change would have to be made is in the virtual BIOS code. I am not going to say any more on this subject, but I hope that some day we will get a TCJ submission dealing with the issues of implementing Z-System on a multiuser system.
Now we finally come to the internal BIOS function routines, such as ICONST and ICONIN. All of these entry points have code that loads the A register with the address offset in the jump table of the real BIOS and then calls the routine DOBIOS, whose function was described in detail in my column in issue #39. Briefly, DOBIOS saves all the alternate and index registers, calls the real BIOS function, restores the registers, and then returns to the calling program.
If we want to incorporate additional functionality, this is the place for it. The code in Listing 1 includes the enhancement to the select-disk code to make it check the drive vector and return an error code if the drive is not enabled. This code, too, was discussed in detail in issue #39.
Changing the Logical Drive Assignments
There are times when you may not like the way the manufacturer of your computer has assigned the logical drives (those things you know as A:, B:, and so on). On my Televideo 803H, the hard drive has the two partitions A: and B:, while the floppy is C:. As an example, I have implemented a special NZCOM BIOS that swaps drives B: and C:, so that the floppy can be referenced as B: and the second hard disk partition as C:. I'm not sure why one would want to do this, but there might be reasons.
The ISELDK routine with the additional code is shown in Listing 2. The code is pretty simple. When the SELDSK BIOS function is invoked, the requested drive is passed in the C register. The code checks sequentially for values of 2 (C:) and 1 (B:), and it changes the value in C to 1 and 2 respectively. If the value is neither 1 nor 2, then the value is left unchanged. Finally, the BIOS routine is called. Thus, virtual BIOS tricks the real BIOS, which still knows the drives by their original names.
Once this code has been entered, say under the name SWAPBC.Z80, then it is assembled to a REL file (Z80ASM SWAPBC/R in the case of the SLR assembler). If you wish, the file can be renamed from SWAPBC.REL to SWAPBC.ZRL just to make it clear that it adheres to the ZRL standard. The loaders don't care which name is used. Now you just enter the command JETLDR SWAPBC.ZRL and, presto, you have a new BIOS with the drive designations reversed.
Some cautions are in order. Drive references that occur within the real BIOS will still use the same physical units and will not know about the swap. External routines that call the BIOS or DOS will see the drives as swapped. Thus swapping the A: drive (assuming that is where NZCOM.CCP is loaded from) will cause the CCP file not to be found. I just tried modifying the code in the listing so that it swapped A: and B:. I also changed the number 1 in the first byte of the NZCOM.CCP file control block to a 2. Now the CCP file will be loaded from the B: drive, which used to be the A: drive. It worked just fine!
In this example, the drives are relogged automatically by the loader. If you try writing a more sophisticated BIOS that has a swap table in it that you intend to change later using a utility, just make sure that the utility forces a relogging of the swapped drives (or all drives) after the swap has been implemented.
Disabling the LIST Device
I think it was our editor Chris McEwen who raised this issue with me. As I recall, he was unable to use a particular utility on his Z-Node because it had a function - not disabled when the wheel byte was off - that engaged the printer.
I had faced a similar problem myself. I have no printer attached to most of my computers, and occasionally I would accidentally hit a key that would initiate a printing operation. On some of the computers this meant instant crash! The BIOS would wait forever for the printer to signal that it was ready, and if I didn't want to wait that long, I had to hit the little red button, losing all my work.
My simple solution to this was to go into the BIOS and replace the jump instruction for the LIST function with a simple RET. Boy could that printer print fast! A little POKE instruction in my startup alias could handle this for me very nicely.
When Chris presented his problem, I told him he could use the BIOS in his NZCOM to take care of things. Just make up two versions of the BIOS, one normal version and one with the list routine RET'd out. Then just use NZCOM or JETLDR to install the one needed.
The code shown in Listing 3 is a more elegant solution. It looks at the printer configuration data stored in the environment. If the width is set to zero, then the print output is disabled. Otherwise it functions normally. As you see, the code is short and sweet.
Console Input/Output Enhancements
George Worley asked on Z-Node Central for a suggestion as to how he could get his system to send some escape sequences to his terminal whenever he pressed certain keys. Again, the NZCOM BIOS can solve the problem.
I originally wrote a virtual BIOS that would remap some keys on the keyboard. I make it interchange the 'a' and 'b' keys - not likely to be very useful, but it illustrated the point. I'm not going to show you that code; it is quite similar to the code for swapping drives except for one detail that will be covered in the example I will present.
The interesting thing about George's problem is that something going on in the console input routine is supposed to initiate an activity with the console output routine. The notion of mixing up the BIOS functions caught my interest. I decided to write a BIOS that would change the cursor on my Televideo terminal to a blinking block when I typed a tilde and back to a blinking underline when I typed a back apostrophe. See Listing 4 for the result.
The thing that is different here from the earlier examples with the SELDSK function is that in those cases the action was taken with input data, before the function was called. Here we must take action on data returned by the function, after the function has executed. Instead of jumping to DOBIOS, we call it and then continue with our code on return from it.
Once we have the character returned by CONIN, we check to see if it is either of our trigger characters. If not, we just return to the calling program with the character. If we do detect one of the trigger characters, then we send a string of characters to the screen using the CONOUT function. When that operation is complete, we then get the typed character back into its proper register and return.
I hope these examples will get you thinking about new ways to use the virtual BIOS. I have shown only very simple code. NZCOM allows one to declare as much space as one wants for a virtual BIOS, and someday I would like to see someone write a version of BYE that can be loaded as a virtual BIOS.
Z-System for MS-DOS
I'd like to finish with a brief announcement. I had originally hoped to discuss this in more detail, but there just is not time or space, so I will leave it for the next issue. But I do want you to know about it now.
I'm sure I'm not the only Z-System user who also uses MS-DOS computers and finds DOS's primitiveness annoying and frustrating. Well, the new version 2 release of PCED (Professional Command line EDitor) is a DOS enhancement product that comes as close as any I have yet found to bringing the features we love in Z-System to MS-DOS. This is not entirely accidental, as I made the author aware of our Z-System work. As a result, PCED gives one most of the functionality of LSH and ARUNZ: full command history, both line and screen oriented, with editing and searching; multiple commands on a line; command scripts with advanced parameter parsing. There are some Z-System features that PCED does not add to DOS, but there are also many very powerful features it does bring that are probably only possible with the larger memory available on a DOS machine.
As is my wont with products like this that I use myself and really like, I got Sage Microsystems East to add it to the product line. PCED is now available at a very attractive price of only $50. I'll try to tell you more about it next time.
Listing 1
The modified NZCOM BIOS source code that protects the Z80 alternate and index registers and that checks the ENV drive vector when selecting a disk.
; Program: ZSNZBIO
; Author: Joe Wright / Cameron W. Cotrill
; Version: 1.2
; Date: 26 January 1989
; Derivation: NZBIO.Z80 version 1.5
; This version has been modified to check the drive vector
; in the disk select routine.
; ... copyright notice and comments
NAME ('BIOZ12') ; NZCOM needs 'BIO' as first
; ..three characters
COMMON /_ENV_/
Z3ENV: ; Address of ENV module
CCP EQU Z3ENV+3FH ; Where CCP address stored
DOS EQU Z3ENV+42H ; Where DOS address stored
DRVEC EQU Z3ENV+34H ; Drive vector address
COMMON /_CBIO_/
CBIOS: ; Address of real BIOS
CSEG
; ... section with equates omitted
; Beginning of NZBIO. The header structure is absolutely
; crucial to the correct operation of NZ-COM.
; ---> DO NOT CHANGE IT <---
START: JP BOOT ; Cold boot
WBOOTE: JP WBOOT ; Warm boot
JP CONST ; Console status
JP CONIN ; Console input
;... rest of jump table
JP IWRITE ; Write
JP LISTST ; List status
JP ISECTR ; Sector translation
DS (30-17)*3 ; Room for 13 extra jumps
SIGN: DB 'NZ-COM' ; ID for NZCOM BIOS
USER: DB 0 ; User area for CCP file
ZCFCB: DB 1,'NZCOM CCP',0,0,0,0
DS 17
; ...auxillary jumps for IOP (omitted)
; END OF HEADER
; The following code is free-form and may be moved around
; ... coldboot code (omitted)
; Warm Boot Entry
WBOOT:
;... some of code omitted
LD DE,(USER) ; Log into user area where
LD C,GSUSR ; NZCOM.CCP is kept
CALL NZDOS
XOR A
LD (ZCFCB+32),A ; Clear current record
LD C,OPENF
CALL NZFIL ; Open NZCOM.CCP
LD HL,(CCP) ; Load it at CCP
; Read NZCOM.CCP to (CCP) until end of file (code omitted)
; ... BDOS service routines (omitted)
; The following calls build a shell around BIOS calls and
; preserve the IX, IY, and alternate registers as required
; by ZSDOS and ZDDOS (and common sense).
ICONST: LD A,6
JR DOBIOS
ICONIN: LD A,9
JR DOBIOS
; ... similar code for other functions omitted
ISECTR: LD A,48
DOBIOS: LD HL,CBIOS
ADD A,L
LD L,A ; Never a carry from this
EXX ; Swap to alternate reg's
LD (HLP),HL ; Save alternate registers
LD (DEP),DE
LD (BCP),BC
LD (IXREG),IX ; Save index registers
LD (IYREG),IY
EXX ; Swap back
EX AF,AF' ; Save alternate PSW also
PUSH AF
EX AF,AF'
CALL JPHL ; Do BIOS call
EXX ; Swap to alternates
LD HL,(HLP) ; Restore them
LD DE,(DEP)
LD BC,(BCP)
LD IX,(IXREG) ; Restore index registers
LD IY,(IYREG)
EXX
EX AF,AF'
POP AF ; Restore alternate PSW
EX AF,AF'
RET ; Return to caller
; Special routines are coded here.
ISELDK: LD HL,(DRVEC) ; Get drive vector
LD A,16 ; Get 16-drive into B
SUB C
LD B,A
ISELDSK1:
ADD HL,HL
DJNZ ISELDSK1
LD HL,0 ; Value for invalid drive
RET NC ; Return if invalid drive
LD A,27 ; Otherwise, use CBIOS
JR DOBIOS
; ... area for saving register contents (not shown)
; End of NZBIO
Listing 2
Modified select-disk BIOS routine that also swaps logical drives B and C.
ISELDK: LD HL,(DRVEC) ; Get drive vector
LD A,16 ; Get 16-drive into B
SUB C
LD B,A
ISELDSK1:
ADD HL,HL
DJNZ ISELDSK1
LD HL,0 ; Value for invalid drive
RET NC ; Return if invalid drive
LD A,C ; Get disk requested
CP 2 ; See if it's C:
JR NZ,ISELDSK2 ; Skip if not
LD C,1 ; If so, change to B:
ISELDSK2:
CP 1 ; See if it's B:
JR NZ,ISELDSK3 ; Skip if not
LD C,2 ; If so, change to C:
ISELDSK3:
LD A,27 ; Now log in drive
JR DOBIOS
Listing 3
Modified list output routine. It checks the printer width byte in the environment. If the value is 0, printer output is disabled by simply returning without calling the real BIOS list output routine.
PCOL EQU Z3ENV + 37H ; Address of width data
ILIST: LD A,(PCOL) ; See if printer width is
OR A ; ..set to 0
RET Z ; If so, just return
LD A,15 ; Else, call BIOS
JR DOBIOS
Listing 4
Modified CONIN routine. When particular characters are typed at the keyboard, escape sequences are sent to the screen. In this particular example, typing a tilde causes the escape sequence to select a blinking block cursor to be sent to the screen, while typing a back apostrophe sets the cursor to a blinking underline (for my Televideo terminal). Thanks to Howard Goldstein for this improvement to my original code.
ICONIN: LD A,9 ; Perform the BIOS call
CALL DOBIOS
CP '~' ; If tilde, send out
JR Z,SEQ1 ; ..sequence 1
CP '`' ; If not back apostrophe,
RET NZ ; ..return to calling
; .. program
; Back apostrophe was typed
PUSH AF ; Save input character
LD A,'3'
JR SEQ
SEQ1: PUSH AF ; Save input character
LD A,'1'
SEQ: LD (POKE+1),A ; Poke in final character
PUSH BC
LD C,1BH ; Send escape to screen
CALL ICONOT
LD C,'.' ; Send period to screen
CALL ICONOT
POKE: LD C,$-$ ; Filled in from above
CALL ICONOT ; Send last char to screen
POP BC
POP AF ; Get input character back
RET
[This article was originally published in issue 49 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 1991 Socrates Press and respective authors]