home *** CD-ROM | disk | FTP | other *** search
- .h1 = main heading
- .h2 = subheading
- italics are delimited by matching underscores
-
- Advanced CP/M
-
- Environmental Programming
-
- Bridger Mitchell
- ------------
- sidebar on Bridger
- PLEASE CHANGE TELEPHONE NUMBER TO: "213-393-6105 (evenings)"
-
- ------------
-
- .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
-
-