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

; ... 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)


; The following code is free-form and may be moved around

; ... coldboot code (omitted)

; Warm Boot Entry

        ;... 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

        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)
        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
        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
        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:
        CP      1               ; See if it's B:
        JR      NZ,ISELDSK3     ; Skip if not
        LD      C,2             ; If so, change to C:
        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

[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]