LINKPRL - or Making RSXes Easy by Harold F. Bower The September 1982 issue of the now-defunct LIFELINES magazine carried an article on a simple program to create a small RAM disk from TPA memory. This appeared to be a neat thing to program at the time, so armed with my trusty old version of MicroSoft's M80 assembler, I attacked it. The one bugaboo in the whole episode was determining what a "PRL"-type file was, and how to generate one. Since none of my tools contained documentation on "PRL" files, the only answer was to count bytes in the program and manually generate the bit map characteristic of a PRL file type. Having survived one episode of this nature, I do NOT recommend it as a normal programming technique. The beauty of the simple, yet not totally practical, resident module was again applied in early 1983 when I was preparing for a transfer to Germany. Not wanting to suffer keyboard withdrawal while waiting for my household goods to arrive, I had prepared one of my computers for shipment in a suitcase. Unfortunately, only one disk drive would fit. For those who have used a single- floppy system, you know how much wear and tear is placed on the mechanical parts. The solution was to minimize all unnecessary mechanical movements on the drive by using..you guessed it..a resident module! The technique was to copy the disk directory information to a resident module, and access the memory image instead of the real directory for disk reads. The normal load sequence of; seek to the directory, read directory, seek to first extent, read first extent, seek directory, etc was thereby reduced to seek to the first extent, read, seek second, etc. An updated version of this module will be used to demonstrate the linker program described here. Having designed the resident module, the problem once again became one of how to generate the bit map. The remembered pain of the RAM disk program convinced me that there was a better way. Since I had become a little smarter in the meantime, I examined the three basic alternatives; first, assemble and link two versions of the program ORGed at different addresses and run a special program to build a map from the differences (a kludge), second, buy an upgraded linker which would generate PRL files (pay money???), and third, write a linker to generate PRL files directly from the MicroSoft REL file. This latter course was selected since it cost the least and had future potential. The recent articles in these pages by Bridger Mitchell and others on RSX modules, and Bridger's release of the RSX standard have done much to lend a sense of coherency to the topic of resident modules. In return, I would like to donate this little linker to the same purpose. Size constraints on the amount of source code in these pages prevent listing of the complete code, but LINKPRL.LBR has been made available on the Ladera Z-Node (213/670-9465) and Sage MicroSystems East (617/965-7259). The Disk Directory Buffer program used as an example in the second part of this article is also available on the same systems in SPEEDUP.LBR. LINKPRL is not intended to replace any of the bigger linkers, and will not link multiple modules or other fancy things. Adding these features is left as an exercise for determined readers. LINKPRL will, however, produce either a standard PRL or COM type file from a MicroSoft format REL file produced by M80, Z80ASM, ZAS, or any number of other assemblers. Furthermore, it is only 2k small! Before delving into the details of how this program works, some background on PRL files is in order. Bit Maps produced by LINKPRL contains one bit for each byte in the combined Code and Data areas. Loaders for PRL files read a byte from the Code/Data area, and a bit from the Map. If the bit is a "1", the desired offset is added to it. For example, the following examples show what assemblers and LINKPRL produce in response to source code instructions: Source statement Assembler out/ Map bits LINKPRL input from LINKPRL ---------------- -------------- -------- LD HL,(0006H) 2A 00 06 000 CALL SOMETHING CD 00 00 001 CP 32 FE 20 00 LD DE,DATA 11 00 00 001 SOMETHING is assumed to be a routine in the Code area, and DATA is assumed to be a location in the Data area. The High order address byte in both cases is denoted as relocatable by the "1" bit in the Map. The "LD HL,(0006H)" instruction refers to a fixed address, and is not relocatable as shown by "0" in all Map bits. While the original intent of the PRL files appears to have been to relocate files on page, or 256-byte boundaries, relocation to any byte boundary is often performed by using 16-bit addition on the byte corresponding to a "1" Map bit and the preceeding byte. DESIGN. Writing LINKPRL was a learning experience in the MicroSoft REL format. As such, not all link items are implemented, nor are they needed for the generation of simple RSX modules. When link items are encountered in the REL file which cannot be handled, the linker prints a message identifying the item with any other associated information. From this display, you can easily identify the problem statement in the source listing. I selected the Z80 processor as the target for this little effort since it was, and still is, among the most popular. Since the Z80 is a register-oriented device, and I have a fetish for fast programs (see ZSDOS), LINKPRL makes maximum use of the internal CPU registers. The specific register selections used here have only two known side-effects; no bounds checking is performed on the output code (for speed reasons), and the BDOS/BIOS system must preserve or not use the alternate nor index registers. This constraint is in effect for other programs as well (see "ZSDOS, anatomy of an OS" in issues #37 and #38) and is only known to be a problem on a few systems. For most programs, the lack of bounds checking on output code should also be no problem. In bit-oriented files, such as the MicroSoft REL format used by LINKPRL, individual bits, rather than bytes, have distinct meaning. This is in contrast with the byte-oriented structure of the Z80 microprocessor and character-oriented (seven or eight bit) text and HEX files. The Z80's expanded instruction set with bit rotates, tests and sets proved invaluable in efficiently handling the bit-oriented files. As previously mentioned, maximum use is made of the Z80 registers to obtain the smallest code and fastest possible execution times. Register usage within the main body of LINKPRL is: A = General Purpose B = Bit Count for Source Byte C = Source byte shifted (B7 is current) D = General Purpose Counter E = Bit Map Output Byte HL = Pointer to Input File AF' = General Purpose and Byte Transfer BC' = Program "ORG" Location DE' = Data "ORG" Location HL' = 16-bit accumulator for Displacement Calculations IX = Physical Load Location IY = Points to FLAGS Byte Much of LINKPRL consists of standard routines to Read a file, write a file, receive your input from the console, and print messages to you. These sections will not be covered in detail since numerous examples are available in other documents and programs. The unique parts of LINKPRL are those dealing with processing of the bit-oriented REL files and we will concentrate on these parts. HOW IT WORKS As with most well-behaved programs, LINKPRL begins with code to set a local stack, check for a Help request instead of execution, and check for the presence of a valid filename argument. If no file type is entered, REL is assumed. The specified file is then opened. The next action taken is to clear all available memory from the program end to the base of the BDOS to zeros. This initilizes all data areas to a known value (0) as an aid in debugging, and to assist your code reduction efforts by possibly reducing the need to initialize data areas within your relocatable module. After clearing memory, you are prompted for the mode (COM or PRL), and the starting address which defaults to 100H for each mode if only a Carriage Return is entered. With that, the preliminaries are complete and LINKRL gets down to work at label DEFAULT (Figure 1) where the first file read occurs. LINKPRL reads a byte at a time from the input file to the C register and shifts the byte, bit-by-bit to recover the command and data elements from the file. GETBIT is the basic routine to get a bit from the file. The Most Significant bit (Bit 7) of register C always reflects the current bit of concern. When a new byte from the source file is required, the byte is loaded into C and an eight count is loaded into the B register to serve as a control to indicate when a fresh byte is required. Each call to GETBIT decrements the counter, and rotates the byte in C one position to the left. When the file and the BC registers are loaded, the main portion of the program is entered at label LOOP (Figure 1). This entry point expects a basic identification structure of one or three bits. If the first bit is a "0", the next eight bits form a byte to be loaded immediately into the file. If the first bit is a "1", then the next two bits must be loaded to determine the specific action needed. The three bit field at this point is interpreted as: 100 Special Link item 101 Load 16-bit word, Program Relative 110 Load 16-bit word, Data Relative 111 Load 16-bit word, Common Relative Special Link items are of particular concern to LINKPRL, even though only a few of the 16 possible items are used. These items specify such things as Names for various program parts, Load addresses (due to assembler ORG directives), and Sizes of the Data and Program areas. These last two items must be recognized before any bytes are sent to the destination file. When both Data and Code size statements have been received, the remaining registers and values are set and program loading begins in earnest. LINKPRL builds a memory image of the output file, writing it to disk when the entire REL file is read, or either the End of Program or Module End Special Link items are encountered. The image begins with a 256-byte header record (only two bytes used) if a PRL file is being generated. The header record is absent for a COM file. The next section (the first for a COM file) is Program code, followed by Data specified by the DSEG assembler pseudo-op. For PRL files, the byte after the end of the Data area marks the beginning of the Relocation Bit Map and this address relative to the start of the Code area is placed in the first two bytes of the header record. In this manner, loaders can determine the size of the file Code and Data area, and the address of the Relocation Bit Map. For COM files, the end of the Data area marks the end of the file. As bits are read from the input file, various routines are accessed to write output bytes as required. The principal ones for the purpose of generating PRL files are BYTE0V and BYTE1V (Figure 2) which save a byte value with a "0" and "1" Map bit respectively. For word addresses relative to Code, Data or Common Segments, the single entry point OUTV (Figure 1) is used to send the low-order byte with a "0" Map bit followed by the high-order byte with a "1" Map bit. CHKWRT (Figure 2) is the routine which sets Map bits indicated by BYTE0V and BYTE1V and writes a byte to the Bit Map area when eight bits have been accumulated. One of the more difficult things to handle in a simple linker such as LINKMAP is the Set Load Location directive resulting from assembler ORG and DEFS (reserve space) directives. The easy path was taken in which a pseudo program counter is maintained in memory, and used to calculate the displacement values resulting from Load Location directives. Null bytes with "0" Map bits are then written until the desired location is achieved. As you can deduce, there is one "gotcha" -- an ORG resulting in a negative displacement will probably fill the entire memory space, including the operating system! Consequently, do not attempt to use LINKPRL on routines which use the ORG directive in a negative direction. The remainder of the code consists mostly of general routines performing utility functions, and interfacing to the operating system. If you are particularly interested in the internal functioning, download the source and examine away! For the rest of you, let's cover the operational details. LINKPRL operation is simple (how else could it be kept to 2K?) requiring that a REL file name be entered as an argument to the LINKPRL invocation as: LINKPRL filename An opening banner will be displayed, and LINKPRL will check for a filename entry in the default File Control Block. If a double- slash (//) is detected instead of a filename, LINKPRL displays a brief Help message summarizing the syntax and operation returns to the Command Processor. Any other entry is assumed to be a filename, and LINKPRL will try to open the file in the current User area. Any error in opening the file will return you to the Command Processor. Assuming that a REL file is successfully opened, you will then be prompted for the remaining two items needed to start the linkage with: Produce .COM or .PRL file (C/P) : Enter Hex load addr (Default = 100H) : If you enter a "C" or "c" at the first prompt, the specified file will be linked to a file of the same name with an extension of directly executed as any other COM file. Entry of a "P" or "p" will create a file of the same name with an extension of .PRL. This file will contain the header record and Bit Map described earlier. For most applications, the default load address requested in the second prompt will be selected with a simple Carriage Return. Special uses where LINKPRL may be used with different values include generating a ROM image, or linking for execution in high memory such as for ZCPR3 Type 3 modules. For these cases, select the "C" option, and the appropriate starting location. When you have entered a terminating Carriage Return after the load address prompt, LINKPRL takes off to "do its thing" and will display some informative status such as Module Name, Program Size and Data Size. If linking to a PRL file, the relative starting and ending addresses of the Bit Map will also be displayed. Other than these items, any unrecognized link items will also be displayed. Since this article is already becoming quite lengthy, I refer you to the source code for descriptions of such items. When the link is completed, you will be returned to the Command Processor prompt. In the next part of this article, we will demonstrate how LINKPRL is used in both COM and PRL modes to generate a Disk Directory Buffer RSX module. In the meantime, enjoy your new mini-linker. ---------------------------------------------------------------- Figure 1, Part I: DEFAULT: POP HL ; Get address from stack LD (ORGADR),HL LD HL,BUFF CALL READ ; Set bit position JR LOOP0 ; ..and jump to test bit ;===============================================; ; M a i n P r o g r a m L o o p ; ;===============================================; LOOP: CALL GETBIT ; Get a source bit into position LOOP0: JR NZ,LOOP1 ; ..jump if 1x form and test next CALL BYTE0 ; 0 = load 8 bits absolute JR LOOP ; ..and back for more ; We have 1x form. Check the second bit LOOP1: CALL GETBIT ; Get a source bit into position JR Z,LOOP2 ; ..jump if it is 10x form CALL GETBIT ; We have 11x, Check 3rd bit JR NZ,COMREL ; 1 = Common, 0 = Data ; We have 110 = Data Relative. CALL ADDR16 ; Get 16 bits, data relative EXX ; Do the math in alternate regs LD HL,(TEMP) ; Load the offset ADD HL,DE ; ..and add DSEG base from DE' JR OUTV ; Write a 01 to Bit Map ; We have 111 = Common Relative. COMREL: CALL ADDR16 ; Get 16 bits, common rel PUSH HL ; Write 01 to map PUSH DE LD DE,(COMMAD) ; Add Common Base address LD HL,(TEMP) ; ..to accumulated offset ADD HL,DE POP DE EX (SP),HL EXX POP HL JR OUTV ; Save value & write 01 to Bit Map ; We have 10x form. Check the third bit LOOP2: CALL GETBIT ; Get a bit in position JR Z,SPECL ; ..jump if 100 (Special Link Item) ; We have 101 = Program Relative. CALL ADDR16 ; Get 16 bits, prog relative EXX ; ..writing 01 in bit map LD HL,(TEMP) ADD HL,BC OUTV: LD A,L ; Vector here to output EXX ; ..relative addresses CALL BYTE0V ; Low byte has 0 Map Bit EXX LD A,H ; Get Hi byte EXX CALL BYTE1V ; ..write with 1 Map Bit JR LOOP ; Arrive here if special link (100xxxxxxxx) ; We don't do much with these, most just print information SPECL: CALL GETTYP ; Get 4 bit type EXX ; Swap to alternates to get free HL' LD HL,SPLTBL ; Offset from table start ADD A,A ; Double value for 2-byte entries ADD A,L LD L,A JR NC,SPECL0 ; Bypass next if no Overflow INC H SPECL0: LD A,(HL) ; Addr.low to A.. INC HL LD H,(HL) ; Addr.high to H LD L,A ; Complete address to HL PUSH HL ; Address to stack to simulate CALL EXX ; ..back to primary registers RET ; Jump to Address on stack SPLTBL: DEFW ENTRY ; 0 = Entry Symbol DEFW COMNAM ; 1 = Common Block Name DEFW PGNAME ; 2 = Program Name DEFW SEARCH ; 3 = Library Search DEFW UNDEF0 ; 4 = (undefined) DEFW COMMON ; 5 = Common Size DEFW CHNEXT ; 6 = Chain External DEFW ENTRPOINT ; 7 = Entry Point DEFW UNDEF1 ; 8 = (undefined) DEFW EXTOFF ; 9 = External + offset DEFW DATSIZ ; 10 = Data Size DEFW LODLOC ; 11 = Load Location DEFW CHNADDR ; 12 = Chain Address DEFW PRGSIZ ; 13 = Program Size DEFW FINI ; 14 = End of Program DEFW FINI ; 15 = Module End ------------------------------------------------------------ Figure 2, Part I: ;..... ; Output a byte with a 1 Map Bit BYTE1V: SCF ; Set Carry flag for 1 in Bit Map JR CHKWRT ; ..and do it ;..... ; Accumulate 8 bits into a byte and output with a 0 Map Bit BYTE0: CALL GETBYT ; Gather 8 bits into a byte BYTE0V: OR A ; Reset Carry for 0 in Bit Map ;..fall thru to.. ;..... ; Check for output write status on Map Bit ; Carry Flag unaffected until shifted into E Register CHKWRT: BIT 2,(IY+0) ; Check ok-to-load JR Z,CHKWR2 ; ..Error if flag = 0 LD (IX+0),A ; Save Code byte INC IX ; ..and bump code pointer PUSH HL ; Preserve regs LD HL,(PCNTR) INC HL ; Increment Pseudo-Program Counter LD (PCNTR),HL RL E ; Rotate Map from Carry into E LD HL,COUNT INC (HL) ; Bump count.. BIT 3,(HL) ; ..check = 8? JR Z,CHKWR1 ; Exit if < 8 LD (HL),0 ; ..else reset counter to 0 LD HL,(BITMAP) ; Write 8 Map bits out LD (HL),E INC HL ; ..bumping address LD (BITMAP),HL LD E,0 ; Preset next map byte to 0 CHKWR1: POP HL ; Restore regs RET CHKWR2: CALL ERRV ; Print message & Abort DEFB CR,LF,BELL,'Write attempt before areas sized !$' ================================================================= Part II: LINKPRL -- or Making RSXes Easy by Harold F. Bower The first part of this article described a simple MicroSoft REL linker and detailed its operation. This linker, LINKPRL, produces either a standard COM file for execution under CP/M and compatible operating systems, or a "Page-Relocatable" PRL file suitable for making Resident System Extensions (RSXes) or ZCPR 3 Type 4 modules. In this second part of the article, both modes of will be demonstrated using an example of a disk directory buffer RSX following the Plu*Perfect standard definition. Before proceeding to the detailed "how to" instructions for linking and forming the SPEEDUP RSX, as we shall call it, let us first examine the "how" of its operation. SPEEDUP is a routine which caches part or all of a disk directory in memory, and accesses the image instead of the real disk directory for read operations. Writes to the cached disk update both the memory image and the actual disk directory to protect against data loss. The advantage of caching the directory is to obtain greater speed when reading files. For example, loading needed system files for ZCPR 3 becomes a lightning-fast series of reads, and reading large files is no longer punctuated by seeks to the directory when opening new extents. Memory for the cache is obtained from the Transient Program Area (TPA) in high memory, and will function under CP/M 2.2, ZRDOS 1.x and ZSDOS 1.x. Architecture. SPEEDUP is comprised of three logical segments grouped in two physical modules. The first module, SPEEDLDR.Z80, is a loader which performs the functions of validating certain system parameters, determining selected addresses to be passed to the relocated module, and performing needed address relocations for the Page Relocatable portion. All program-dependant parts of the loader, except for a couple of configuration options and built-in Help, are added to the second physical module. With this architecture, only minimal changes are needed to adapt SPEEDLDR to other RSX applications. SPEEDLDR is linked to a .COM file for execution at 100H. It is exactly 1024 bytes (1k) long to make combination with the next module easier. The next few bytes after the end of the loader are reserved for the RSX header structure which appears at the beginning of the module to be relocated. The second physical module, SPEED22.Z80, is relocated to high memory immediately below the Console Command Processor, or lowest existing RSX in its entirety. Two logical sections of code exist in SPEED22; a final installation section, and a resident core of code. Final installation consists of; determining the remaining addresses and values needed, patching the BIOS jump table, resident module and Page 0 addresses, and exitting via the resident module Warm Boot entry point. Installation is only needed once, so the code storage space in high memory is reclaimed for use by the resident portion of the RSX. Since both SPEEDLDR and the installation portion of SPEED22 are relatively self-explanatory, they will not be described in detail here, but may be obtained in source code form from at least the two Z-Nodes cited at the end of this article. The final logical portion of code is the resident part of SPEED22 and is the heart of this RSX. Fewer than 650 bytes of code and data make up this segment. This functional division was selected to provide the maximum possible Transient Program Area and maximum re-usable source code for other projects. I hope you too find this structure applicable in RSX generation. How SPEEDUP Works. In order to understand how SPEEDUP works, you must understand the table-driven nature of CP/M and the interface between the Basic Disk Operating System (BDOS) which works on a logical file basis, and the Basic IO System (BIOS) which contains the physical device drivers. CP/M and many programs which access the BIOS directly for disk functions specify Track Number, Sector Number, DMA Transfer Address and Disk Selection before performing a physical Read or Write. The resident portion of SPEEDUP traps each of these BIOS entry points. A few of them (Set Track, Set Sector and Set DMA Transfer Address) merely store the specified values locally before passing them to the real BIOS. Other entry points are acted on locally and may not, in the case of a Sector Read, access the real BIOS at all. The program listing contains an extract of the SPEED22 source code and will be used to explain the inner workings of this RSX. The BIOS Select Disk function is the first unit of SPEEDUP which takes local action. BIOS calls are routed to the RSX at label SELDSK where the disk number is compared against the one we specified when loading SPEEDUP. If the drive is not ours, we de- select the RSX and go directly to the real BIOS. If it is for us, we then check a cue flag which the BDOS provides as Bit 0 of the E-register. If this bit is Non-Zero (Set), the BDOS has already logged this disk and we again exit to the real BIOS just to keep everything synchronized. Only when a new mount request is received signified by this bit being reset (Zero) do we act locally by calling the real BIOS Disk Select function and returning. In response to a Select Disk function, BIOS returns a pointer to the 16-byte Disk Parameter Header (DPH) for the subject drive which contains a table of pointers to various drive parameters. Two of these, the address of the Skew Translation routine, and the address of another table - the Disk Parameter Block (DPB), are used by the RSX. The DPB provides us with the number of logical 128-byte Sectors Per Track on the drive, the number of Directory entries, and the number of reserved tracks on the disk before the the beginning of the Directory. Values obtained from the BIOS Select Disk call are used to detect when a Read or Write request reference a Directory sector, and to determine whether the requested sector is in the memory buffer. When SPEEDUP is first loaded, it allocates memory in 1k increments as specified by calling parameters, with each 1k representing 32 directory entries. Actually, slightly more than that is allocated since one extra byte for the logical sector number is needed for every 4 Directory entries representing a logical sector. These logical sector numbers are placed in a table immediately below the resident module base, with sector storage extending downward from there. This sector number table incorporates any needed skew by calling the Sector Translate routine obtained from the DPH as it is built. After the table is built, Directory sectors are read into memory until either the allocated memory is full, or the entire Directory is read. Execution then returns to the calling program, or the BDOS. Requests for BIOS Writes and Reads are similarly vectored through the resident module at labels WRITE and READ respectively in the listing. Write requests, as with Select Disk calls, are also furnished a flag clue by the BDOS. In this case, the clue is the byte in the C-register which will be 01H if a Directory write is being requested. any writes other than those for the Directory are simply routed directly to the real BIOS for processing. Those which involve the Directory first call the real BIOS, then check to see if the subject sector is in the memory cache (subroutine SETUP). If the Sector is present, the cache contents are also updated before returning to the calling program. Read requests are not furnished with any special clues by the BDOS as in Write and Disk Select routines. A determination of whether or not the request involves cached sectors involves simply checking to see if the desired Track and Sector is in memory (subroutine SETUP). If not, we go directly to the real BIOS. If we have the sector cached, we merely move the sector to the location specified by the DMA address. If you carefully think about the way this all works, you will detect the one pitfall in caching the Directory. A disk change without a Relog (Control-C or Dos functions 13 or 37) preceeding a Write will, in all probability, trash the disk. Even the advanced Disk Change logic in ZSDOS will not detect a swap under these circumstances, so ALWAYS REMEMBER TO DO A WARM BOOT IMMEDIATELY AFTER CHANGING DISKS! How To Make SPEEDUP. Assuming that you have, by now, obtained LINKPRL from one of the available Z-Nodes, and the source code library for SPEEDUP, you are ready to form the executable RSX. First assemble both SPEEDLDR.Z80 and SPEED22.Z80 with M80, ZAS or any other assembler which produces a standard MicroSoft REL file. Next link SPEEDLDR with LINKMAP in the COM mode. The following interaction should be displayed where signifies the "Enter" or "Return" key on your keyboard: >SPEEDLDR <-- Invoke LINKPRL Bit-map Linker V3.2 13 Aug 89 (C) H.F.Bower Link to .COM or .PRL (C/P) :C <-- Link to COM Enter Hex load addr (Default = 0100H) : <-- Use Default Program Name : SPEEDL Data Area Size : 0000 Program Size : 0400 If you examine your current directory at this point, you should have generated the file SPEEDLDR.COM. Next, we use LINKPRL to generate a PRL file for the segment which will be relocated to high memory. The following interaction should be displayed for this operation: >LINKPRL SPEED22 <-- Invoke LINKPRL Bit-map Linker V3.2 13 Aug 89 (C) H.F.Bower Link to .COM or .PRL (C/P) :P <-- Link to PRL Enter Hex load addr (Default = 0100H) : <-- Use Default Program Name : SPEED2 Data Area Size : 0000 Program Size : 03D2 Load Location : 01B3 Bit Map begins @ ORG + 03D2 Bit Map ends @ ORG + 0444 As before, a check of your current directory will show that you have created SPEED22.PRL. See the first part of this article for a description of what special characteristics are exhibited by this type of file. For now, we merely want to use it. We do this by combining SPEEDLDR.COM and SPEED22.PRL into a single module called SPEEDUP.COM. The simplest approach is to use DDT, ZDMH, ZBUG or any similar debugger. The following interaction will occur with ZDMH: >ZDMH SPEEDLDR.COM <-- Load SPEEDLDR at 100H ZDMH VERS 1.2 NEXT PC 0500 0100 -ISPEED22.PRL <-- Load SPEED22.PRL -R400 <-- ..to 500H (100H+400H) NEXT PC 0A80 0100 -G0 <-- Return to CP/M, image still in memory >SAVE 10 SPEEDUP.COM <-- Save 100H to 0AFFH to Disk You now have an executable SPEEDUP.COM file in your current directory. Operation is quite simple with a brief help message covering usage available which may be displayed by entering: SPEEDUP // I hope you found this article to be of some help in understanding the finer points of PRL files, and will be able to use these tools. Complete source code for the programs described here may be obtained in LINKPRL.LBR and SPEEDUP.LBR from the Ladera Z-Node operated by Al Hawley at (213) 670-9465 and from Jay Sage's Z- Node #3 at (617) 965-7259. I thank both Al and Jay for allowing me to furnish these programs on their systems. Any comments/bugs etc for me may be addressed to: ------------------------------------------------------------------ Figure 1, Part II: ;@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ;@ M O D U L E B O D Y @ ;@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ; Modified BIOS Select function loads buffer on New Login SELDSK: LD A,(LDISK) ; Is it this drive ? CP C LD A,0 ; Deselect first... LD (FSEL),A JR NZ,BSDSKE ; ..jump if not here DEC A ; Else select with 0FFH LD (FSEL),A BIT 0,E ; Is it a logon request? JR NZ,BSDSKE ; ..jump if no to BIOS CALL BSDSKE ; Else Do BIOS & return PUSH HL ; Save Regs for routine PUSH BC PUSH AF LD E,(HL) ; Get skew addr INC HL LD D,(HL) LD (SKWTBL),DE LD DE,9 ADD HL,DE ; DPB is at DPH+10 LD E,(HL) INC HL LD D,(HL) EX DE,HL LD DE,SPTRK ; Move SPB to local area LD BC,15 LDIR LD HL,(DIRMAX) ; Load # Dir entries - 1 INC HL ; Correct dirmax SRL H ; Conv DIRMAX to # Sctrs. RR L ; ..by dividing by 4 SRL H RR L EX DE,HL ; Save in DE while we.. LD HL,(DMAX) ; ..compare to avail # LD H,0 PUSH HL OR A SBC HL,DE POP HL JR C,SMALR ; Jump if this won't fit EX DE,HL ; ..else save whole Dir SMALR: LD A,L ; Get LS Byte LD (NDIRS),A ; ..and save it LD DE,-1 ; Preset counter LD BC,(SPTRK) ; ..get Sectors per Track CALCLP: INC DE XOR A SBC HL,BC ; How many trks for Dir? JR NC,CALCLP ; Fall thru if DE = #Trks LD HL,(OFFSET) ; Get reserved trk count ADD HL,DE ; .add # of Dir tracks LD (TOPTRK),HL ; ..and save top Dir Trk LD HL,(SECTBL) LD A,(DMAX) ; Store this many entries LD B,A XOR A ; Fill table with Zero CLRLP: LD (HL),A INC HL DJNZ CLRLP LD (CURSEC),A ; Set Curr Log. Sctr to 0 LD HL,(OFFSET) LD (TRK),HL ; Set starting Track LD HL,(SECTBL) ; Get Sctr ID Table start LD (TEMP),HL ; ..and save LD HL,(DIR) ; Get start of Sctr Buff LD A,(NDIRS) ; .# of Dir Sctrs therein LD B,A ; ..to count register ; Load translated sector table and sector data buffer SETTBL: PUSH BC ; Save Cnt and Addr regs PUSH HL LD DE,(SKWTBL) ; Set up for translation LD A,(CURSEC) ; ..on current sector LD C,A LD B,0 INC A ; Bump Log. Sctr number LD (CURSEC),A CALL BSCTRN ; Find translated sector LD A,L LD HL,(TEMP) LD (HL),A ; Store translated sector INC HL ; ..and bump table Addr LD (TEMP),HL LD C,A CALL BSETSC ; Set Sector Number POP BC ; Retrieve Buffer Address PUSH BC ; ..and keep on stack CALL BSTDMA ; DMA addr to Buffer Sctr LD BC,(TRK) CALL BSTTRK ; Set Physical Trk Number CALL BREAD ; ..and read Disk Sector POP HL ; Restore registers POP BC LD DE,128 ; Increment cache address ADD HL,DE LD A,(CURSEC) LD DE,(SPTRK) CP E ; Check Track overflow JR C,SETTB0 ; ..jump if same Track LD DE,(TRK) INC DE ; Else bump track #.. LD (TRK),DE XOR A ; ..and zero Sctr Number LD (CURSEC),A SETTB0: DJNZ SETTBL JR WEXIT ; Restore parms & Exit ;..... ; BIOS Write Sector routine. If sector resident, ; update memory image in addition to disk data WRITE: LD A,C DEC A ; directory write? JP NZ,BWRITE ; ..jump if not CALL BWRITE PUSH HL ; save status PUSH BC PUSH AF CALL SETUP JR NZ,WEXIT OR 0FFH ; Show this is a Write JR RAMW ;..... ; BIOS Read sector routine. If sector in memory, simply ; move, else jump to real BIOS and get data from disk READ: LD DE,(TRK) ; Directory track? LD HL,(TOPTRK) ; See if trk <= toptrk OR A SBC HL,DE JP C,BREAD ; if not dir, read disk CALL SETUP JP NZ,BREAD LD HL,0 ; Set "good read" status PUSH HL ; ..once for HL PUSH HL ; ...and once for BC XOR A ; Show this is a Read PUSH AF ; ..with good status ; Calculate address of desired sector in RAM buffer ; ENTRY: Reg C has relative sector number ; EXIT: HL register pair has buffer absolute address RAMW: PUSH AF ; Save flag status PUSH DE LD D,C LD E,0 SRL D RR E LD HL,(DIR) ADD HL,DE POP DE POP AF LD DE,(DMADDR) OR A ; Check which operation JR Z,RAM0 ; ..jump if read EX DE,HL ; Swap direction if Write RAM0: LD BC,128 LDIR WEXIT: POP AF POP BC POP HL RET ;-------------------------------------------------------; ; S U P P O R T R O U T I N E S ; ;-------------------------------------------------------; ; Check for presence of requested sector in memory buffer ; ENTRY: None ; EXIT: Found - Zero flag set ; C has relative 128-byte Sctr offset ; Not Found - Zero flag cleared (reset) ; Reg A is Non-Zero SETUP: LD A,(FSEL) ; Is this drive selected? OR A JR Z,SETUP8 ; ..error return if not LD BC,(OFFSET) ; Calc # tracks from 1st LD HL,(TRK) XOR A ; Get offset from Direc. SBC HL,BC LD A,L ; ..should be < 255 LD HL,0 ; Make # of Sctrs offset LD DE,(SPTRK) SETUP2: OR A JR Z,SETUP3 ADD HL,DE DEC A JR SETUP2 ; ..loop til finished SETUP3: LD C,L ; Copy # sectors to BC LD B,H EX DE,HL ; ..and store in DE LD HL,(NDIRS) ; Get # Dir Sctrs stored LD H,0 XOR A ; Subtract offset sectors SBC HL,BC JR C,SETUP8 ; Not Found if overflow LD B,L ; Put 8-bit Sctr cnt in B ; ..Sector offset is in C LD HL,(SECTBL) ; Get addr fm table start ADD HL,DE LD A,(SCTR+1) ; Is Sctr # > 256? OR A JR NZ,SETUP8 ; ..not here if so LD A,(SCTR) ; See if sectors match RL1: CP (HL) JR Z,SETUP9 ; ..jump if found INC C INC HL DJNZ RL1 SETUP8: DEFB 0F6H ; Set Error w/"OR 0AFH" SETUP9: XOR A ; Return Ok conditions RET ;---------------------------------------; ; D A T A A R E A ; ;---------------------------------------; ; Storage for data passed to BIOS SCTR: DEFS 2 ; Sector passed to BIOS TRK: DEFS 2 ; Track passed to BIOS DMADDR: DEFS 2 ; DMA Addr passed to BIOS ; DPB and Skew Table address for active disk SPTRK: DEFS 2 ; Sectors per track DEFS 5 ; Room for BSH, BLM etc DIRMAX: DEFS 2 ; Max # of Dir entries DEFS 4 ; Room for CHK & ALV OFFSET: DEFS 2 ; Track offset SKWTBL: DEFS 2 ; Skew table address ; General Purpose pointer and buffer area FSEL: DEFS 1 ; drive selected flag LDISK: DEFS 1 ; Disk # active in RAM DIR: DEFS 2 ; Start of Dir Buffer RAM SECTBL: DEFS 2 ; Addr of Sector #s Table TOPTRK: DEFS 2 ; Top track of Directory CURSEC: DEFS 1 ; Current working sector DMAX: DEFS 1 ; Maximum # Dir sectors NDIRS: DEFS 1 ; # of DIR Sectors stored TEMP: DEFS 2 ; Temp pointer area DEFS 32 ; Local stack space STACK: DEFS 2 ; Storage for entry stack END