>>>>>>>>>>>>>>>>>>>>> CP/M-Net News <<<<<<<<<<<<<<<<<<<<<<<< ============================================================ Number 9 September, 1981 Volume 1, Issue 9 ============================================================ In This Issue ============= Assembly Language Interface to PL/I-80 By: Michael J. Karas, MICRO RESOURCES CP/M-Net "Tip-of-the-Month" The Absolute Minimum Typing, to Run a CP/M Submit File... By: Mike Karas and Kelly Smith Printed monthly (at worst quarterly) to inform user's of RCPM Systems to the latest software news, information, and updates of public domain software accessible via telephone/modem transfer. Yearly subscription for copies of the CP/M-Net News may be obtained by mailing $18.00 (check or money orders only) to Kelly Smith, CP/M-Net, 3055 Waco Street, Simi Valley, California 93063. CP/M-Net is a non- profit orginization and all money received on subscriptions are utilized for the sustaining and enhancments of the CP/M- Net System. If you would like to contribute an article, include a column containing your area of interest and expertise, or participate in an open forum for conversation and transfer of ideas, feel free to send it to the CP/M-Net System and indicate that you would like it to be included in the CP/M- Net News...if possible, use WordStar (trademark, MicroPro International) or Electric Pencil (trademark, Micheal Shrayer) in 60 column format. Note: CP/M is a registerd trademark of Digital Research ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Assembly Language Interface to PL/I-80 ====================================== by: Michael J. Karas MICRO RESOURCES 2468 Hansen Court Simi Valley, California 93065 The advent of the PL/I-80 high level programming language for CP/M based machines has opened up the wide wide world of easy, sophisticated, and structured programming for business, commercial and scientific applications. The language features offer the most capable development tools of any microprocessor programming language available in the marketplace today. Through extended use of the language, others like my self, are going to find out that the features of PL/I-80 offer far more than just programming niceity for applications software development. The structure, speed of execution, self documentation features, and full CP/M compatability make PL/I-80 the obvious choice for many systems utility and dedicated function processor programming exercises. The one main disadvantage of any high level language is the isolation from the "guts" of the computer machinery that results for the programmer. Don't get me wrong in that this is an altogether disadvantage. After all, it is the whole idea behind the use of a high level language in the first place. But for certain aspects of systems utility and dedicated function processor programming, more access to the internals of the computer are generally needed. The normal manner to obtain this capability is through development of programs in machine language. But we all know what a pain that can be for very large programs in the thousands of bytes in size. It always seems that when an assembly language program gets over about 2000 bytes, that there is a screaming need for easy access to disk files, the operator interfaces, and simplified development of complex logical structures. PL/I-80 offers all of the things the heavy duty assembly language programmer needs to make life easy. The next obvious question is... How do I handle that special interface?..or How can I do IN and OUT in PL/I-80?..or How can PL/I-80 access the SYSTEM tracks of a CP/M floppy disk?...and the list of questions goes on and on and on. The answer to all these questions is to use the assembly language interface facilities offered through the mechanism of external procedure calls in PL/I-80. CP/M is a registered TRADEMARK and PL/I-80, RMAC, and LINK- 80 are TRADEMARKS of Digital Research Inc. Pacific Grove, California. The Digital Research LINK-80 manual describes how it all works but doesn't make it very easy to understand. Here in example form, I intend to present an easy explanation of how to implement an assembly language interface to PL/I-80. I have chosen to implement "hooks" to allow direct access to the CP/M BIOS vector table of CP/M 2.2 to allow the PL/I-80 programmer to make those special system utility packages with a minimum of pain yet leaving the avenue open to perform all those special I/O functions that the standard CP/M BDOS interface never seems to have enough of. I have chosen to write a diskette copy utility program in PL/I-80 and let the compiler make it easy to write all the operator prompting messages and to allow quick human readable presentation of the logical structure of the program while at the same time allowing direct high speed access to the disk drivers in the BIOS. The start of this project resulted in the assembly language module given below. This module provides interface translation between the external procedure calling mechanisms of PL/I-80 and the entry/exit requirements of the various assembly language hardware interface drivers of the CP/M 2.2 BIOS. Detailed discussion of all aspects of the interface conventions are well beyond the scope of this article but a few general comments are in order. Readers who desire to "understand to the MAX" are encouraged to (1) study the examples given here carefully while (2) consulting the PL/I-80 LINK-80 Users Manual and (3) consulting the CP/M 2.2 System Alteration Guide. As a start, to become the most familiar with the mechanisms, I would suggest implementing the examples given here on your own computer system. It only takes a few days or evenings and the experience can be quite rewarding. The interface to the BIOS, in general, is done as follows: The location of the BIOS vector table of entry points is determinable in any CP/M system by looking at the address in locations 1 and 2 of low memory. This address gives the pointer to the warm boot address of the BIOS vector table. All other BIOS entry points are accessable by adding an offset to the warm boot pointer vector. Some functions are console status, line printer output, and disk track selection. All of the offsets necessary are given in the assembly language source code below. The typical procedure to get to a BIOS subroutine, with 8080 type machine language, would be like this: function: lhld 0001 ;get warm boot vector table pointer lxi d,offset ;get offset for desired BIOS entry point dad d ;make address of entry point pchl ;branch indirect through (HL) to BIOS ;subroutine If the label entry "function" had been CALLed from some main program, then the return at the end of the BIOS subroutine would get program execution back to the calling program assuming we use the callers stack all along the way. If the entry to the interface routine required post processing of BIOS return parameters before returning back to the main calling program, then the above program segment becomes: function: lhld 0001 ;get warm boot vector table pointer lxi d,offset ;get offset for desired BIOS entry point dad d ;make address of entry point lxi d,backhere ;put return address to here on stack push d pchl ;branch indirect through (HL) to BIOS ;subroutine backhere: <.... post processing instructions....> ret ;back to main program call point All parameter information entered at the BIOS follows the following general set of rules: a) If entry parameter is a single byte it is placed in the (C) register. b) If entry parameter is a double byte or an address it is entered in the (BC) register pair. c) Return parameters of one byte values are brought back in the (A) register. d) Return address of double byte parameters are returned in the (HL) register. For now, that should give the reader the information to understand the BIOS part of the assembly language module given below. The full set of PL/I-80 rules are complicated, so I will only touch on the concept here as to the few interface types used by the BIOS module to return parameters to PL/I-80. For character string variables, the return parameter is put on the stack with the length of the string given back in the (A) register. This scheme applies to the CONIN and READER routines which bring a byte character back from the BIOS. These push the character onto the stack and send a 01 length back to the calling PL/I-80 program. If the return parameter is a PL/I-80 data type that is one byte in size, but a numerical type variable, then the value is returned to PL/I-80 in the (A) register. If a numerical type variable of two bytes or an address is being passed back then the PL/I-80 program gets the parameter back in the (HL) register pair. All entry data sent from the PL/I-80 program to the assembly language module comes as follows, if a parameter is defined for passage to the BIOS. The rule is that when called, the assembly language program receives an address in the (HL) register pair that points to a table of memory locations (in our case a single pair). These two bytes of memory contain another address that points right at the data parameter. The type, size, and number of parameters are defined by design of the assembly language program and the statement of external entry point characteristics. No type coding is passed or error check done. In other words both sides have to agree to what is trying to be done before the call and return parameter passing will work properly. The program module "PLIBIOS.ASM", given below in source code form, establishes the names for sixteen small programs that translate procedure calls from within a main PL/I-80 program to the forms required by the BIOS. The subsequent module below, "BIOSCALL.DCL" is a small file the fully defines, in a manner compatible with the assembly language program, the form that the PL/I-80 program must use to call the entry point definitions to make the parameter passing work. The assembly language program is meant to be assembled into a relocatable file of the ".REL" type by use of the Digital Research relocating macro assembler, RMAC. This assembler, seeing the psuedo opcodes "PUBLIC" on the program names builds the .REL file with a table to tell the, later utilized, link program where the relative addresses of the subroutines in the module are located. After the host PL/I- 80 program, like the disk copy utility given later in the article, is compiled into a relocatable object module, that .REL module also contains a table of subroutine call addresses which were known to be external to the PL/I-80 program. The external nature if the labels is defined by the included declare statement from the file "BIOSCALL.DCL" which simply told the compiler that I intended to provide another ".REL" module that had these program names in it, specifically "PLIBIOS.REL". The Link program LINK 80 is used to hook the two modules together and make an absolute object module out of them. The absolute object module happens simply to be the executable CP/M type ".COM" file. File PLIBIOS.ASM ;*********************************************************** ; Direct CP/M 2.2 Bios access Interface Module For PL/I-80 ;*********************************************************** ; ; This module to be asembled with Digital Research RMAC ; provides interface conversion between PL/I-80 calls and ; function references and the Bios vector table entries.The ; file "BIOSCALL.DCL" should be included in your PL/I-80 ; source program with a %include statement to properly ; establish the external entry point names and calling ; format definition. Most bios entry point routines are ; treated as function call modules. The user of these ; routine in PL/I-80 calls should be aware that some ; differences exist between the CP/M 2.2 BIOS ; implementations that are available from various software ; and hardware vendors. In particular, not all BIOS ; routines perform sector translation with the SECTRAN ; routine. Secondly, If the BIOS performs the function of ; physical sector deblocking, and the sector translate ; function is not used, then logical sector numbers ; entered at the BIOS settrk entry point may require ; numbering from zero instead of the usual start with ; locical sector number 1. This assembly language routine ; makes no assumption about the sector number start. Also ; no translation is done. The calling PL/I-80 program must ; anticipate the characteristics of the host system BIOS. ; (BEWARE: especially with tricky utilities being upgraged ; from CP/M 1.4, including Digital Research's SYSGEN ; program). Hopefully CP/M 3.0 coming out soon will fully ; resolve this problem. Note that CP/M 3.0 will probably ; fully eliminate the need to have direct BIOS access at ; all. ; ;*********************************************************** ; ; ; name 'PLIBIOS' title 'Direct CP/M BIOS Vector Calls From PL/I-80' ; ; ;*********************************************************** ;* * ;* bios direct calls from pl/i for direct i/o * ;* * ;*********************************************************** ; public cboot ;cold boot reload of cp/m entry public wboot ;warm boot reload of ccp public cstat ;return byte for console status public conin ;return character input from console public conout ;write character to console public list ;write character to list device public punch ;write character to punch device public reader ;return character input from reader public home ;home selected disk unit public seldsk ;select disk unit and return parameter ;table pointer public settrk ;select track public setsec ;select sector number public setdma ;set disk i/o data buffer pointer public read ;read selected sector and return status public write ;write selected sector and return status public lstat ;return byte for console status public sectran ;translate sector number ; ; ; Bios vector table offsets from the warm boot vector to ; access the vector table entry points off the loc 1 and 2 ; warm boot vector. ; cbooto equ -3 ;cold boot offset wbooto equ 0 ;warm boot offset cstato equ 3 ;console status offset conino equ 6 ;console input offset conouto equ 9 ;console output offset listo equ 12 ;list device output offset puncho equ 15 ;punch device offset readero equ 18 ;reader ddevice offset homeo equ 21 ;disk home offset seldsko equ 24 ;select disk offset settrko equ 27 ;select disk offset setseco equ 30 ;select sector offset setdmao equ 33 ;set dma address offset reado equ 36 ;read logical sector offset writeo equ 39 ;write logical sector offset lstato equ 42 ;list status offset strano equ 45 ;sector translate offset ; ; ; Define position of CP/M 2.2 warm boot vector location ; wbvect equ 0 ;addr of warm boot jump ; ; ;*********************************************************** ;* * ;* general purpose routines used upon entry * ;* * ;*********************************************************** ; ; ; get single byte parameter to register c ; getp1: mov e,m ;low (addr) inx h mov d,m ;high(addr) xchg ;hl = .char mov c,m ;to register c ret ; ; ; get single word value to BC ; getp2: call getp1 ;get low byte of parameter inx h mov b,m ;get high byte as well ret ; ; ; transfer control to indexed bios vector table entry ; enter with DE = entry offset index ; gobios: lhld wbvect+1 ;get cp/m warm boot vector dad d ;add in vector table offset pchl ;go to table entry ; ; ; ;*********************************************************** ; * ; direct bios access routines for pl/i-80 calls * ; * ;*********************************************************** ; ; ; ; routine to enter bios cold boot to completely reload cp/m ; cboot: lxi d,cbooto ;get offset jmp gobios ;on our way ; ; ; routine to enter bios warm boot to reload ccp and bdos ; wboot: lxi d,wbooto ;get offset jmp gobios ;go reload ; ; ; routine to return console status byte ; return byte value to stack ; cstat: lxi d,cstato ;status offset jmp gobios ;return bit(8) in (a) from bios ; ; ; routine to get character from the console for input ; conin: lxi d,conino ;conin offset ; ret1chr: ;entry point for one character ;return to pl/1-80 on stack call gobios ;go get the status to (a) pop h ;return address push psw ;character to stack inx sp ;delete flags mvi a,1 ;character length is 1 pchl ;back to calling routine ; ; ; routine to output one character to the console ; conout: call getp1 ;get single output char to (c) lxi d,conouto ;console output offset jmp gobios ;return to pl/1 direct from bios ; ; ; routine to output one character to the list device ; list: call getp1 ;get single output char to (c) lxi d,listo ;list output offset jmp gobios ;return to pl/i direct from bios ; ; ; output routine to send one character to the punch ; punch: call getp1 ;get single output char to (c) lxi d,puncho ;punch output offset jmp gobios ;return to pl/i direct from bios ; ; ; routine to get character from the reader for input ; reader: lxi d,readero ;reader input offset jmp ret1chr ;let common code do rest ; ; ; routine to get list device status ; lstat: lxi d,lstato ;list status offset jmp gobios ;return bit(8) status in (a) from bios ; ; ; home selected disk entry point ; home: lxi d,homeo ;home offset jmp gobios ;return direct from bios ; ; ; entry point to select disk and return parameter table ; pointer for selected drive in (hl) ; seldsk: call getp1 ;get single byte drive select byte lxi d,seldsko ;select disk offset jmp gobios ;return addr pointer in (hl) from bios ; ; ; routine to send double byte track number to bios ; settrk: call getp2 ;get track number to (bc) lxi d,settrko ;set track offset jmp gobios ;return through bios ; ; ; routine to send double byte sector number to bios ; setsec: call getp2 ;get sector number to (bc) lxi d,setseco ;set sector offset jmp gobios ;return through bios ; ; ; routine to send data buffer pointer to bios ; setdma: call getp2 ;get dma address to (bc) lxi d,setdmao ;setdma offset jmp gobios ;return through bios ; ; ; sector translate routine. ; sectran: call getp2 ;get logsec to (bc) lxi d,strano ;sectran offset jmp gobios ;return through bios ; ; ; routine to read sector of 128 bytes ; read: lxi d,reado ;read sector offset jmp gobios ;return error status through bios ; ; ; write sector routine. entry parameter of write type ; write: call getp1 ;get write type to (c) lxi d,writeo ;write sector offset jmp gobios ;return error status through bios ; ; ;+++...end of file The following short file is a single statement PL/I-80 declare statement that defines in PL/I-80 syntax, all entry points that are given in the preceeding assembly language program. The file is used so that the same set of declared variable names can be used in multiple PL/I-80 source programs without having to type them in all the time, risking making errors or ommissions. The PL/I-80 lanugage contains a feature called the "%include" statement that allows the following file to be put in as part of the source code as simple as typing the following code as part of your program: %include 'bioscall.dcl'; This will insert the following text into the program source code at the point where the %include appears. Note that in the above syntax the file "BIOSCALL.DCL" is expected to be contained upon the same disk drive as the source program. Explanation of the statement formats of the declared entry points will be left for the examples below. An example of the "%include" function appears in the disk copy program below. File "BIOSCALL.DCL" /* Define names and procedure characteristics for the */ /* MICRO RESOURCES Direct BIOS Access Procedures of */ /* "PLIBIOS.ASM" and PL/I-80 linkable module "PLIBIOS.REL'*/ dcl cboot entry, wboot entry, cstat entry returns (bit(8)), conin entry returns (char(1)), conout entry (char(1)), list entry (char(1)), punch entry (char(1)), reader entry returns (char(1)), home entry, seldsk entry (binary fixed(7)), settrk entry (binary fixed(15)), setdma entry (ptr), read entry returns (bin fixed(7)), write entry (bin fixed(7)) returns (bin fixed(7)), lstat entry returns (bit(8)), sectran entry (bin fixed(15)) returns (bin fixed(15)); An example of how to make use of the assembly language BIOS access routines is presented below in the form of a PL/I-80 source program that is a floppy disk copy program. First let me describe the intent of the program and then show a few examples of how the external BIOS entry points are accessed. The diskette copy program allows full copying of a diskette in Drive A: to the diskette in Drive B:. Tracks are buffered in memory in track buffer arrays so that a whole track may be read at a time to make the program run reasonably fast. (Incidently, this copy operation runs almost as fast as most assembly language copy utility programs I've seen except for those that buffer more that one track at a time). Tracks are read sequentially from the HOME position of Drive A: and written to the corresponding positions of Drive B:. After writing, the Track is reread from Drive B: and compared byte for byte with the image read in originally from Drive A:. Any errors are reported as to track and sector number to inform the operator of any difficulties encountered along the way. At the completion of the copy process the operator is prompted to see if another copy is desired before rebooting the system disk in Drive A:. All console interface and program logic structure is implemented in the easy to program PL/I-80 syntax. Speed dependant and hardware specific I/O access in the program makes use of the BIOS access external entry points. The following examples show some of the external procedure references made to the entry points defined in "PLIBIOS". In the following paragraphs some familiarity with the PL/I-80 language structure is assumed. A tutorial on the structure and syntax of the language is somewhat beyond the intended subject of this article. EXAMPLE ONE - Calling The CONSOLE INPUT ENTRY POINT (CONIN) The console input entry point is declared as an external entry point in the BIOSCALL.DCL include file as follows: DCL conin entry returns(char(1)); This defines conin as a function procedure with procedure invocation by name in an expression. The implicit CALL to the routine will return the declared type of value, CHAR(1) in this case, to the point of the call. The example from the disk copy program has the conin call at the point of waiting for an operator response. A character string, declared as follows: DCL resp char(80) /* operator response buf */ ; ...is designated to receive one console character with the following statement. The NULL parentheses reference designates conin as a function procedure with no passed parameters. In this case resp resp=conin(); ...becomes a string of one entered character padded on the right with 79 blanks. EXAMPLE TWO - Calling the SET SECTOR ENTRY POINT (SETSEC) The set sector function queues the BIOS to a sector to read on a subsequent call to the read routine. This procedure is very similar in operation to the set track, select disk, and set dma address entry points in that all are defined in the BIOSCALL.DCL file with declares of the form: DCL settrk entry (bin fixed(15)); This defines settrk as a subroutine procedure that has an entry parameter consisting of a double byte binary fixed point number that must be referenced with the following type of statement: CALL settrk(n); ...where n is a variable of the fixed binary variety that contains the value of the track number to send to the BIOS. Note that no return parameters are involved with the procedure invocation. Makes direct BIOS access quite simple doesn't it ! EXAMPLE THREE - Calling the DISK READ SECTOR ENTRY (READ) The read sector entry point is defined as a function procedure in that it returns a binary fixed(7) value pertaining to the status of the disk read operation. (Defined by standard BIOS entry definition, assembly language structure of the PLIBIOS program, and the declare in the BIOSCALL.DCL file as follows: DCL read entry returns(bin fixed(7)); Since read is a function call, the read entry point is referenced by name and the binary fixed(7) return error code is returned to the point of invocation simply as in the statement below. This one statement causes the read of a previously selected logical 128 byte sector into the buffer defined by a previous "setdma" call and at the same time defines what action to perform if the read was unsuccessful and returned a non-zero error code. if read() ^= 0 then do; put edit('Read Error On Control Disk - Track ', trkno,' Sector ',secno) (col(1),a,f(3),a,f(3)); put skip(2) edit('Restart Copy ', 'Process with new Source Diskette.') (col(1),a,a); go to copyloop; end; Other BIOS entry points are referenced in a manner similar to the examples above. The "write" for instance has an entry parameter as well as a return error code like the read call. The write entry parameter informs the BIOS, if physical sector deblocking is done, of the type of write to perform (see the Digital Research CP/M 2.2 System Alteration Guide for more information on the exact definition of BIOS entry point and return value definitions). A special note concerning the select disk entry point "seldsk" is that it returns a pointer value that defines the position of the disk parameter table for the drive selected in the entry parameter. (Note that a PL/I-80 NULL pointer return value defines an illegal drive designator). Careful examination of the following program will reveal all instances of direct BIOS entry point invocation. The rest of understanding of this program is left as a learning exercise for the interested reader. File dskcpy.pli /*********************************************************** Full disk copy program to demonstrate the direct CP/M Bios access facilities presented in the accompanying article, "Assembly Language Interface to PL/I-80." Track by track copying is utilized through the direct bios access facilities of the linked assembly language program "PLIBIOS". ***********************************************************/ dskcpy: proc options(main); %replace trk_per_disk by 77, /* logical bios tracks/diskette */ sec_per_trk by 26; /* logical cp/m sectors/track */ /* external CP/M bios entry points */ %include 'bioscall.dcl'; /* bring in the bios entry point definition file that defines bios function entry points*/ dcl resp char(80), /* operator response buf */ inbuf(sec_per_trk) char(128) based(p), /* input buffer */ outbuf(sec_per_trk) char(128) based(q), /* output buffer */ trkno bin fixed(15), secno bin fixed(15), (i,j,k,l) bin fixed(15), (p,q,s) pointer, e5var bit(8) based(s), e5char char(1) based(s), e5trk bit(1), skew(sec_per_trk) bin fixed(15) static init( 1,7,13,19,25,5,11,17,23,3,09,15,21, 2,8,14,20,26,6,12,18,24,4,10,16,22), e5string char(128), partab pointer; /* disk parameter table ptr */ /* Print initial message for Diskette copy from drive A: to B: */ put edit('MICRO RESOURCES Diskette Copy program in PL/I-80 ', '^m^j', 'Version 1.1 of August 3,1981') (col(1),3(a)); copyloop: put skip edit('Insert Source Diskette in Drive A:', '^iDepress when ready ')(col(1),a); resp=conin(); newdisk: put skip(1) edit('Insert Destination Diskette in Drive B:', '^iDepress when ready or to quit') (col(1),a); resp=conin(); if resp='^C' then do; put edit('Remember to Re-Insert your CP/M ', 'System Diskette in Drive A:', '^iDepress when ready ') (col(1),a,a); resp=conin(); stop; end; put skip(1) edit('Copy in Progress...')(col(1),a); allocate inbuf set(p); /* setup buffer storage */ allocate outbuf set(q); allocate e5var set(s); /* setup simple 0EH insertion */ partab=seldsk(0); /* select disk a: (0) */ call home; /* restore it */ partab=seldsk(1); /* select disk b: (1) */ call home; /* restore that too */ e5trk='0'b; /* e5 end track scan switch off */ e5var='e5'b4; do i=1 to 128; /* fill e5 record check string */ substr(e5string,i,1)=e5char; end; trklp: do trkno = 0 to trk_per_disk-1 while( ^ e5trk); partab=seldsk(0); /* select source */ call settrk(trkno); j=0; /* set empty sector counter off */ /* read source track loop */ do secno = 1 to sec_per_trk; call setdma(addr(inbuf(secno))); call setsec(skew(secno)); /* sectors are zero based */ /* for some CP/M 2.2 BIOS's */ /* if so replace (secno) with */ /* (secno-1) */ if read() ^= 0 then do; put edit('Read Error On Control Disk - Track ', trkno,' Sector ',secno) (col(1),a,f(3),a,f(3)); put skip(2) edit('Restart Copy ', 'Process with new Source Diskette.') (col(1),a,a); go to copyloop; end; end; partab=seldsk(1); /* select destination */ call settrk(trkno); /* write destination track loop */ do secno = 1 to sec_per_trk; call setdma(addr(inbuf(secno))); call setsec(skew(secno)); /* sectors are zero based */ /* for some CP/M 2.2 BIOS's */ /* if so replace (secno) with */ /* (secno-1) */ if write(2) ^= 0 then do; put edit ('Write Error On Destination', ' Disk - Track ',trkno, ' Sector ',secno) (col(1),a,a,f(3),a,f(3)); put skip(2) edit ('Bad Diskette in Drive ', 'B:, try another.')(col(1),a,a); go to newdisk; end; end; /* read back destination loop */ do secno = 1 to sec_per_trk; call setdma(addr(outbuf(secno))); call setsec(skew(secno)); /* sectors are zero based */ /* for some CP/M 2.2 BIOS's */ /* if so replace (secno) with */ /* (secno-1) */ if read() ^= 0 then do; put edit ('Read Verify Error On Destination', ' Disk - Track ',trkno, ' Sector ',secno) (col(1),a,a,f(3),a,f(3)); put skip(2) edit ('Bad Diskette in Drive ', 'B:, try another.')(col(1),a,a); go to newdisk; end; end; /* data compare loop */ do secno = 1 to sec_per_trk; if inbuf(secno) ^= outbuf(secno) then do; put edit ('Verify Data Compare Error ', trkno) (col(1),a,f(3)); put skip(2) edit ('Bad Data on Diskette ', 'in Drive B:, retry same diskette.') (col(1),a,a); go to newdisk; end; if inbuf(secno) = e5string then j=j+1; /* empty sector */ end; if j=sec_per_trk then do; e5trk='1'b; put edit ('Track by track copy complete ', 'at track number ',trkno) (col(1),a,a,f(3)); end; end trklp; free inbuf; free outbuf; free e5var; put skip edit('Do you wish to Copy This Another Diskette (Y/N) ') (col(1),a); resp=conin(); if (resp = 'Y') ! (resp = 'y') then go to newdisk; else stop; end dskcpy; To compile the PL/I-80 program given above the following operational sequence would be used to invoke the Digital Research PL/I-80 compiler to process source file "DSKCPY.PLI". Lower case characters after the "A>" prompt are those typed by you the operator. Other text is the output of the compiler to the console screen. A>pli dskcpy <== Extent of .PLI is assumed PL/I-80 V1.3 COMPILATION OF: DSKCPY %include 'bioscall.dcl'; <== Complier brings in prev- iously edited DCL file for all external entry points in the "PLIBIOS.REL"module. NO ERROR(S) IN PASS 1 NO ERROR(S) IN PASS 2 <== Compiler informs us of pass completions. CODE SIZE = 05A9 <== Compile statistics for DATA AREA = 044F "REL" file of "DSKCPY". FREE SYMS = 2BBF END COMPILATION A> The next step to get a final disk copy program out of all of this work is to link the "REL" (relocatable object code) modules of "DSKCPY", "PLIBIOS", and the necessary subroutines from the Digital Research PL/I-80 run time library together into a ".COM" file. The LINK 80 linker from Digital Research does the job nicely with the following console operational "look". As before, the text in lower case following the A> prompt was that typed by the operator performing the link process. (Please note that the symbol values given may not necessarily coincide with those for your version of the above program; especially if you slightly modify the text strings and signon messages in the PL/I-80 source code listing). A> A>link dskcpy,plibios <== ".REL" extensions assumed and output to DSKCPY.REL is the default output. LINK 1.3 PLILIB RQST DSKCPY 0100 CONIN 06CC SELDSK 0705 HOME 06FF SETTRK 070E SETDMA 0720 SETSEC 0717 READ 0732 WRITE 0738 CBOOT 06BA WBOOT 06C0 CSTAT 06C6 CONOUT 06D8 LIST 06E1 PUNCH 06EA READER 06F3 LSTAT 06F9 SECTRA 0729 /SYSIN/ 27E7 /SYSPRI/ 280C ABSOLUTE 0000 <== Link prints out module CODE SIZE 266B (0100-276A) size and address statistics DATA SIZE 06E2 (285A-2F3B) COMMON SIZE 00EF (276B-2859) USE FACTOR 95 <== Hex percent of symbol table usage. A> The rest is up to you. I have provided an example of how assembly language routines can be tied to PL/I-80 to implement special functions that are beyond the scope or capability of the language. In this case they were simple examples of interface conversions for BIOS access. Other examples of assembly language routines that could be made include "IN and OUT" functions for direct language access to machine dependant input and output ports, high speed special access drivers for math chips such as the Intel 8087, or tightly coded interrupt service routines to increase program performance where PL/I-80 is used as the host development language in a real-time application. Operation of the above example should be self evident from a detailed study of the source listing. The best way to tackle your own problem is to dive right in. For those who feel that they need a little more structured approach or partly implemented feel for getting started, I am willing to provide the source code for all of the modules described in this article on a single density CP/M compatible 8 inch diskette free of charge if you just send a diskette, mailer, and return postage to the address given at the beginning of this article. Feel free to make use of any new ideas you see here however you see fit. ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ CP/M-Net "Tip-of-the-Month" =========================== The Absolute Minimum Typing, to Run a CP/M Submit File... By: Mike Karas and Kelly Smith On occasion, you may need to run consecutive SUBmit files, under 'attended' operation, and MAY have to do it numerous times...well here is a trick to enter only two characters on your keyboard (S and Carriage Return), to minimize the amount of typing required. Edit your SUBmit file as normally do... end the edit, and then: A>REN .SUB=LAZY.SUB <-- Rename your .SUB to ONLY .SUB A>REN S.COM=SUBMIT.COM <-- Rename SUBMIT.COM to S.COM A>S <-- Run '.SUB'!!! How's it work? Easy...SUBMIT "see's" a temporary FCB entry of the file " . " (eight spaces for the filename and three spaces for the filetype), and goes "searching" for a filetype of ".SUB"...and low-and-behold FINDS IT! Now to really speed things up for your SUBMIT, we use that old trick of fooling the CCP (Command Console Processor with a 'null file'...you remember, 'SAVE 0 @.COM'...and use it to minimize the 'loading time' of some redundant operation. For example, when generating a pile of system disks you might edit a SUBMIT file like this: sysgen <--- Let's SYSGEN... pip b:=pip.*[v] <--- And PIP some goodies we need to B: @ b:=xdir.*[v] <--- And use PIP still in memory @ b:=stat.*[v] <--- And use PIP again... @ b:=asm.*[v] <--- And again... @ b:=ed.*[v] <--- Again.. @ b:=load.[v] <--- Well, you get the point by now! Keep in mind however, that you may only use the LAST file that was loaded into memory...for instance, if you had placed something like ABORTSUB.COM in the middle of the PIP's, and then intended to CONTINUE PIPing, you MUST again invoke PIP explicitly as a filename...THEN you can continue using '@' as illustrated here.